Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 22 de diciembre de 2015

ASP.NET CoreA veces, en nuestras aplicaciones ASP.NET Core y MVC puede ser interesante manipular los encabezados retornados desde el servidor al cliente en todas las peticiones, ya sea añadiendo información personalizada o eliminando encabezados generados por otros componentes o middlewares que participan en el proceso de la petición.

Un ejemplo muy típico puede ser la eliminación del header "Server" para no dar pistas sobre el software usado en el servidor, que, como se comentaba en la propia RFC del protocolo, "podría hacer nuestro servidor más vulnerable a ataques".

Obviamente, el tratarse de una tarea absolutamente transversal e independiente de las aplicaciones en las que vayamos a aplicar esta técnica, es la misión ideal para un middleware personalizado. Así pues, crearemos un componente de este tipo y lo insertaremos al principio del pipeline, capturando todas las respuestas enviadas al cliente y aprovechando ese momento para manipular los encabezados a nuestro antojo.

Introducing HeaderTransformMiddleware

Comenzando por el final, nuestra intención es implementar un middleware personalizado que podamos añadir al pipeline de la siguiente forma:
var transforms = new Dictionary<string, string>()
{
    ["X-Author"] = "José M. Aguilar",
    ["Server"] = null
};
app.UseHeaderTransform(transforms);
En ese diccionario de transformaciones indicaremos los encabezados que deseamos establecer (si existen previamente se sobreescribirán), y los que queremos eliminar (estableciéndolos a null).

En principio, la cosa debería haber sido tan sencilla como crear un middleware como el que sigue:
// Ojo: no funciona bien, más abajo se explica el por qué y cómo solucionarlo
public class HeaderTransformMiddleware
{
    private readonly RequestDelegate _next;
    private readonly IDictionary<string, string> _transforms;

    public HeaderTransformMiddleware(
                 RequestDelegate next, IDictionary<string, string> transforms)
    {
        _next = next;
        _transforms = transforms;
    }

    public async Task Invoke(HttpContext context)
    {
        await _next(context);
        TransformHeaders(context.Response.Headers, _transforms);
    }

    private void TransformHeaders(IHeaderDictionary headers, 
                                  IDictionary<string, string> transforms)
    {
        if (transforms == null)
            return;

        foreach (var entry in transforms)
        {
            if (string.IsNullOrWhiteSpace(entry.Value))
                headers.Remove(entry.Key);
            else
                headers[entry.Key] = entry.Value;
        }
    }
}
Sin embargo, las cosas no son nunca tan sencillas, y si lo probamos veremos que en muchos casos no funcionará bien. El problema que tiene el código anterior es quedamos a la espera de que el resto de middlewares procesen la petición (en la llamada a await _next(context)) para modificar los encabezados, pero en el momento que volvemos a tomar el control es posible que éstos hayan sido enviados ya al cliente, por lo que cualquier transformación que les apliquemos sencillamente no serán tenidas en cuenta.

Afortunadamente, ASP.NET Core permite registrar delegados que se ejecutarán justo antes de que se comiencen a enviar los encabezados al cliente, por lo que el enfoque en el método Invoke() debería ser el siguiente:
public async Task Invoke(HttpContext context)
{
    context.Response.OnStarting(() =>
    {
        TransformHeaders(context.Response.Headers, _transforms);
        return Task.FromResult(0);
    });
    await _next(context);
}
Con esto ya tenemos solucionado gran parte del problema. Si ejecutamos una petición a nuestra aplicación, los encabezados que obtenemos son los siguientes (fijaos que ha desaparecido el encabezado "Server" y que aparece "X-Author":
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
X-Author: Variable not found
X-Powered-By: ASP.NET
Date: Sun, 20 Dec 2015 11:21:11 GMT
Content-Length: 8011

Añadir el middleware UseHeaderTransform al pipeline

Con la definición del middleware que hemos visto ya podríamos añadirlo al pipeline usando los métodos genéricos Use() de IApplicationBuilder, de la siguiente forma:
app.UseMiddleware<HeaderTransformMiddleware>(transforms);
Obviamente no nos íbamos a quedar ahí ;) A continuación se muestra el código que permite llamar a UseHeaderTransform()  sobre IApplicationBuilder, simplificando la inserción de nuestro middleware al pipeline de ASP.NET:
namespace Microsoft.AspNet.Builder
{
    public static class HeaderTransformMiddlewareExtensions
    {
        public static IApplicationBuilder UseHeaderTransform(
            this IApplicationBuilder app, IDictionary<string, string> headerChanges)
        {
            app.UseMiddleware<HeaderTransformMiddleware>(headerChanges);
            return app;
        }
    }
}
De esta forma, ya podemos añadir el middleware como señores:
app.UseHeaderTransform(transforms);

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!