code it

Martins Tech Blog

GZip für WebAPI 2 aktivieren

Mit GZip-Komprimierung kann man einiges an Netzwerklast minimieren - insbesondere, wenn größere Datenmengen übertragen werden sollen, in denen häufig ähnliche Worte vorkommen. Auch wenn das meist genutzte JSON-Format nicht ganz so geschwätzig ist wie beispielsweise SOAP, so kommen doch auch hier beispielsweise die Eigenschaftsnamen in jedem Objekt wieder vor. Wenn man dann eine Liste von Objekten überträgt, so ergibt sich hier ein Einsparungspotenzial.

Im  Gegensatz zu Content-Negotiation, die die Web-API selbst übernimmt, gibt es offenbar keinen Automatismus für die Gzip-Komprimierung. Eine Suche ergab, verschiedene Lösungsansätze - z.B. den von Ben Foster oder den von Radenko Zec. Letzterer ist Vorlage für meine jetzige Lösung geworden.

Schritt 1 - Erzeugen eines ActionFilters, der die Response ändert
public class CompressionAttribute : ActionFilterAttribute
{
    public override async Task OnActionExecutedAsync(HttpActionExecutedContext context, CancellationToken cancellationToken)
    {
        var acceptEncoding = context.Request.Headers.AcceptEncoding;
        var acceptsGzip = acceptEncoding.Contains(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip"));

        if (!acceptsGzip)
        {
            return;
        }

        var content = context.Response.Content;
        if (content == null)
        {
            return;
        }

        var headers = context.Response.Content.Headers;
        var bytes = await content.ReadAsByteArrayAsync();

        var zlibbedContent = (await Compress(bytes)) ?? new byte[0];
        context.Response.Content = new ByteArrayContent(zlibbedContent);

        foreach (var header in headers)
        {
            if (header.Key.Equals("Content-Length", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }
            context.Response.Content.Headers.Add(header.Key, header.Value);
        }
        context.Response.Content.Headers.Add("Content-Encoding", "gzip");
    }

    private static async Task Compress(byte[] value)
    {
        if (value == null)
        {
            return null;
        }

        using (var output = new MemoryStream())
        {
            using (var gzipStream = new GZipStream(output, CompressionMode.Compress, CompressionLevel.BestSpeed))
            {
                gzipStream.FlushMode = FlushType.Finish;
                await gzipStream.WriteAsync(value, 0, value.Length);
                   
            }
            return output.ToArray();
        }
    }
}
Um die Komprimierung selbst kümmert sich Ionic.Zlib

Die Implementierung ist an sich recht einfach. Zunächst wird geprüft, ob der Client Gzip-Encoding akzeptiert. Ist das nicht der Fall, dann verändert das Attribut die Ausgabe nicht. Anderenfalls wird die Ausgabe komprimiert und die Response neu aufgebaut. Durch die Neuzuweisung der Response werden auch alle bisherigen Content-Type-Header verworfen, weswegen diese im Nachgang wieder gesetzt werden müssen.

Im Anschluss wird dann dem Client noch mitgeteilt, dass es sich um komprimierten Inhalt handelt.

Schritt 2 - ActionFilter anwenden
Der ActionFilter kann nun verwendet werden, indem eine Web-API-Methode damit annotiert wird oder man aktiviert ihn global für alle Anfragen.
public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        ...

        config.Filters.Add(new CompressionAttribute());

        ...
    }
}
Das passiert in der WebApiConfig.
Sie benötigen Unterstützung bei der Umsetzung Ihrer Projekte?
Ich bin käuflich. Nehmen Sie mit mir Kontakt auf.

Kommentar schreiben

Loading