WebAPI Gzip bei der Rückgabe von HttpResponseMessage

Lesezeit: 8 Minuten

Benutzer-Avatar
Franzose

Ich habe einen WebAPI-Controller, der eine zurückgibt HttpResponseMessage und ich möchte die gzip-Komprimierung hinzufügen. Dies ist der Servercode:

using System.Net.Http;
using System.Web.Http;
using System.Web;
using System.IO.Compression;

[Route("SomeRoute")]
public HttpResponseMessage Post([FromBody] string value)
{
    HttpContext context = HttpContext.Current;

    context.Response.Filter = new GZipStream(context.Response.Filter, CompressionMode.Compress);

    HttpContext.Current.Response.AppendHeader("Content-encoding", "gzip");
    HttpContext.Current.Response.Cache.VaryByHeaders["Accept-encoding"] = true;

    return new SomeClass().SomeRequest(value);
}

Und dies ist der Client-Code für den Ajax-Aufruf mit jquery:

$.ajax({
    url: "/SomeRoute",
    type: "POST",
    cache: "false",
    data: SomeData,
    beforeSend: function (jqXHR) { jqXHR.setRequestHeader('Accept-Encoding', 'gzip'); },
    success: function(msg) { ... }

Wenn ich dies ausführe, kehrt der Server-Code ohne Fehler zurück, aber der Client-Fehler:

(failed)
net::ERR_CONTENT_DECODING_FAILED

Geben Sie hier die Bildbeschreibung ein

Wenn ich mit Fiddler schaue, sehe ich Folgendes:

Geben Sie hier die Bildbeschreibung ein

Was muss ich ändern, damit der Webdienst gezippte Inhalte zurückgibt, die der Client normal verarbeitet? Ich weiß, dass ich dies auch mit einem HttpModule oder durch eine Einstellung auf IIS tun könnte, aber keine der Optionen passt zum Szenario des Hostings:

Geben Sie hier die Bildbeschreibung ein

Bitte beachten Sie, dass ich nicht nach einer IIS-Einstellung suche, da ich darauf keinen Zugriff habe (Hosting).

  • Hast du dir das angeschaut? stackoverflow.com/a/10446108/263003

    – Jude Li Huan

    20. Juli 2014 um 3:29 Uhr

  • schau mal weblog.west-wind.com/posts/2012/Apr/28/…

    – Eindeutig codieren

    20. Juli 2014 um 7:27 Uhr

  • @JeowLiHuan: Ich hatte gehofft, ich könnte es in viel weniger Schritten machen.

    – Franzose

    20. Juli 2014 um 16:08 Uhr

  • Überprüfen Sie dies – stackoverflow.com/a/3653766/2164198 und versuchen Sie, den vorgeschlagenen Wrapper zu verwenden, um unerwünschtes Verhalten zu vermeiden. Siehe auch diese Antwort stackoverflow.com/a/7629079/2164198

    – Iwan Samygin

    22. Juli 2014 um 18:01 Uhr


  • Wie es aussieht, können Sie es direkt auf IIS tun. Der Link ist etwas alt — stackoverflow.com/questions/702124/enable-iis7-gzip

    – Tasos

    24. Juli 2014 um 9:52 Uhr

Benutzer-Avatar
Pooran

Fügen Sie diese NuGet-Pakete hinzu:

Microsoft.AspNet.WebApi.Extensions.Compression.Server System.Net.Http.Extensions.Compression.Client

Dann und fügen Sie eine Codezeile hinzu App_Start\WebApiConfig.cs:

GlobalConfiguration.Configuration.MessageHandlers.Insert(0, new ServerCompressionHandler(new GZipCompressor(), new DeflateCompressor()));

Das wird den Trick tun!

Einzelheiten unter:

Ich hoffe, das hilft.

**Aktualisiert nach Kommentar von @JCisar

Update für ASP.Net Core

Nuget-Paket ist

Microsoft.AspNetCore.ResponseCompression

  • Das ist so viel besser, als die dynamische IIS-Komprimierung einzuschalten

    – Brain2000

    30. Juni 2015 um 19:02 Uhr

  • Vielen Dank! Obwohl das Paket sagt, dass es veraltet ist (ich vermute ab dem 28.01.2016). Weiß jemand, ob dies durch etwas ersetzt wird, das ich stattdessen verwenden sollte? Ich sehe keine Erwähnung davon.

    – JCisar

    29. Februar 2016 um 18:05 Uhr


  • @JCisar Laut dem Paketautor sollen Sie verwenden diese neuen Pakete die von ihm gepflegt werden.

    – Zign

    3. April 2016 um 13:46 Uhr

  • Beachten Sie, dass ich dies mit dieser Methode nicht zum Laufen bringen konnte und tatsächlich verwenden musste config.MessageHandlers.Insert(0, new ServerCompressionHandler(new GZipCompressor(), new DeflateCompressor())); innerhalb der Register Methode

    – Chris

    12. September 2016 um 9:34 Uhr

  • Dies ist ein veraltetes Paket, es installiert nur die Pakete Microsoft.AspNet.WebApi.Extensions.Compression.Server und System.Net.Http.Extensions.Compression.Client.

    – Thulani Chivandikwa

    24. Mai 2017 um 11:55 Uhr

Wenn Sie Zugriff auf die IIS-Konfiguration haben

Sie können den Header nicht einfach anwenden und hoffen, dass er gezippt wird – die Antwort wird nicht gezippt.

Sie müssen den hinzugefügten Header entfernen und sicherstellen, dass die dynamische Komprimierung und die statische Inhaltskomprimierung auf Ihrem IIS-Server aktiviert sind.

Einer der Kommentatoren hat hier bei stakoverflow einen guten Ressourcenlink erwähnt, der zeigt, wie das geht:

Aktivieren Sie IIS7 gzip

Beachten Sie, dass das Festlegen des Werts in web.config nur funktioniert, wenn die dynamische Komprimierung bereits installiert ist (was nicht in einer Standardinstallation von IIS enthalten ist).

Informationen dazu finden Sie in der MSDN-Dokumentation: http://www.iis.net/configreference/system.webserver/httpcompression

Einfache Komprimierung

Unten finden Sie ein einfaches Beispiel für Ihre eigene Komprimierung. Dieses Beispiel verwendet das Web Api MVC 4-Projekt aus Visual Studio-Projektvorlagen. Damit die Komprimierung für HttpResponseMessages funktioniert, müssen Sie einen benutzerdefinierten MessageHandler implementieren. Siehe unten ein funktionierendes Beispiel.

Siehe die Codeimplementierung unten.

Bitte beachten Sie, dass ich versucht habe, die Methode so zu halten wie in Ihrem Beispiel.

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;

namespace MvcApplication1.Controllers
{
    public class ValuesController : ApiController
    {
        public class Person
        {
            public string name { get; set; }
        }
        // GET api/values
        public IEnumerable<string> Get()
        {
            HttpContext.Current.Response.Cache.VaryByHeaders["accept-encoding"] = true;

            return new [] { "value1", "value2" };
        }

        // GET api/values/5
        public HttpResponseMessage Get(int id)
        {
            HttpContext.Current.Response.Cache.VaryByHeaders["accept-encoding"] = true;

            var TheHTTPResponse = new HttpResponseMessage(System.Net.HttpStatusCode.OK); 
            TheHTTPResponse.Content = new StringContent("{\"asdasdasdsadsad\": 123123123 }", Encoding.UTF8, "text/json"); 

            return TheHTTPResponse;
        }

        public class EncodingDelegateHandler : DelegatingHandler
        {
            protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
            {
                return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) =>
                {
                    HttpResponseMessage response = responseToCompleteTask.Result;

                    if (response.RequestMessage.Headers.AcceptEncoding != null &&
                        response.RequestMessage.Headers.AcceptEncoding.Count > 0)
                    {
                        string encodingType = response.RequestMessage.Headers.AcceptEncoding.First().Value;

                        response.Content = new CompressedContent(response.Content, encodingType);
                    }

                    return response;
                },
                TaskContinuationOptions.OnlyOnRanToCompletion);
            }
        }

        public class CompressedContent : HttpContent
        {
            private HttpContent originalContent;
            private string encodingType;

            public CompressedContent(HttpContent content, string encodingType)
            {
                if (content == null)
                {
                    throw new ArgumentNullException("content");
                }

                if (encodingType == null)
                {
                    throw new ArgumentNullException("encodingType");
                }

                originalContent = content;
                this.encodingType = encodingType.ToLowerInvariant();

                if (this.encodingType != "gzip" && this.encodingType != "deflate")
                {
                    throw new InvalidOperationException(string.Format("Encoding '{0}' is not supported. Only supports gzip or deflate encoding.", this.encodingType));
                }

                // copy the headers from the original content
                foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
                {
                    this.Headers.TryAddWithoutValidation(header.Key, header.Value);
                }

                this.Headers.ContentEncoding.Add(encodingType);
            }

            protected override bool TryComputeLength(out long length)
            {
                length = -1;

                return false;
            }

            protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
            {
                Stream compressedStream = null;

                if (encodingType == "gzip")
                {
                    compressedStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true);
                }
                else if (encodingType == "deflate")
                {
                    compressedStream = new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true);
                }

                return originalContent.CopyToAsync(compressedStream).ContinueWith(tsk =>
                {
                    if (compressedStream != null)
                    {
                        compressedStream.Dispose();
                    }
                });
            }
        }
    }
}

Fügen Sie auch den neuen Nachrichtenhandler zur Konfiguration Ihrer App hinzu.

using System.Web.Http;
using MvcApplication1.Controllers;

namespace MvcApplication1
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            config.MessageHandlers.Add(new ValuesController.EncodingDelegateHandler());

            config.EnableSystemDiagnosticsTracing();
        }
    }
}

Der Custom Handler wurde zusammengestellt von – Kiran Challa (http://blogs.msdn.com/b/kiranchalla/archive/2012/09/04/handling-compression-accept-encoding-sample.aspx)

Es gibt bessere Beispiele, die auch das Deflationieren eingehender Streams implementieren. Beispiele dafür finden Sie unten:

Außerdem habe ich auf Github ein wirklich nettes Projekt gefunden, das all dies unterstützt.

Beachten Sie, dass Simon, während ich selbst zu dieser Antwort gekommen bin, diesen Ansatz vor 2 Tagen nach dem Datum dieser Antwort in Ihren Kommentaren vorgeschlagen hat.

  • Ich suche keine IIS-Lösung, weil ich keinen Zugriff darauf habe. Deshalb muss ich die Komprimierung aus der WebAPI heraus durchführen.

    – Franzose

    24. Juli 2014 um 14:31 Uhr

  • Verwenden Sie das Beispiel auf benfoster.io/blog/aspnet-web-api-compression das tut es außerhalb von iis.

    – Dmportella

    24. Juli 2014 um 14:32 Uhr


  • Das funktioniert nicht in einer gehosteten Umgebung:

    – Franzose

    24. Juli 2014 um 14:35 Uhr

  • Wenn die dynamische Komprimierung nicht installiert ist, können Sie sie nicht verwenden

    – Dmportella

    24. Juli 2014 um 14:40 Uhr

  • Danke dafür, vermute, du meinst Accept-Encoding statt Accept-Enconding!

    – Der Senator

    30. Mai 2016 um 16:11 Uhr

Eine Lösung ohne Bearbeiten einer beliebigen IIS-Einstellung oder Installieren einer beliebigen Nuget Paket ist, a hinzuzufügen MessageHandler zu Ihrer WEB-API.

Dadurch werden Anfragen mit dem „AcceptEncoding“-Header abgefangen und mithilfe von Build in komprimiert System.IO.Komprimierung Bibliotheken.

public class CompressHandler : DelegatingHandler
{
    private static CompressHandler _handler;
    private CompressHandler(){}
    public static CompressHandler GetSingleton()
    {
        if (_handler == null)
            _handler = new CompressHandler();
        return _handler;
    }
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        return base.SendAsync(request, cancellationToken).ContinueWith<HttpResponseMessage>((responseToCompleteTask) =>
        {
            HttpResponseMessage response = responseToCompleteTask.Result;
            var acceptedEncoding =GetAcceptedEncoding(response);
            if(acceptedEncoding!=null)
                response.Content = new CompressedContent(response.Content, acceptedEncoding);

            return response;
        },
        TaskContinuationOptions.OnlyOnRanToCompletion);
    }
    private string GetAcceptedEncoding(HttpResponseMessage response)
    {
        string encodingType=null;
        if (response.RequestMessage.Headers.AcceptEncoding != null && response.RequestMessage.Headers.AcceptEncoding.Any())
        {
            encodingType = response.RequestMessage.Headers.AcceptEncoding.First().Value;
        }
        return encodingType;
    }


}

    public class CompressedContent : HttpContent
{
    private HttpContent originalContent;
    private string encodingType;

    public CompressedContent(HttpContent content, string encodingType)
    {
        if (content == null)
        {
            throw new ArgumentNullException("content");
        }

        if (encodingType == null)
        {
            throw new ArgumentNullException("encodingType");
        }

        originalContent = content;
        this.encodingType = encodingType.ToLowerInvariant();

        if (this.encodingType != "gzip" && this.encodingType != "deflate")
        {
            throw new InvalidOperationException(string.Format("Encoding '{0}' is not supported. Only supports gzip or deflate encoding.", this.encodingType));
        }

        // copy the headers from the original content
        foreach (KeyValuePair<string, IEnumerable<string>> header in originalContent.Headers)
        {
            this.Headers.TryAddWithoutValidation(header.Key, header.Value);
        }

        this.Headers.ContentEncoding.Add(encodingType);
    }

    protected override bool TryComputeLength(out long length)
    {
        length = -1;

        return false;
    }

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        Stream compressedStream = null;

        if (encodingType == "gzip")
        {
            compressedStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true);
        }
        else if (encodingType == "deflate")
        {
            compressedStream = new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true);
        }

        return originalContent.CopyToAsync(compressedStream).ContinueWith(tsk =>
        {
            if (compressedStream != null)
            {
                compressedStream.Dispose();
            }
        });
    }
}

Und fügen Sie diesen Handler Ihrer Global.asax.cs hinzu

GlobalConfiguration.Configuration.MessageHandlers.Insert(0, CompressHandler.GetSingleton());

Ein großes Lob an Ben Foster.
ASP.NET-Web-API-Komprimierung

  • Vielen Dank! Genau das, was ich brauchte, um einige veraltete NuGet-Pakete zu ersetzen. 🙂

    – David

    29. Juni 2018 um 14:16 Uhr

Nur ein Nachtrag zur Aktivierung der Komprimierung in IIS über die applicationHost.config Datei.

Verwenden Sie den IIS-Konfigurationsmanager um die Änderungen vorzunehmen bzw notepad.exe um die Datei zu bearbeiten. Ich benutzte Notepad++ und obwohl die Datei gespeichert wurde, wurde sie tatsächlich nicht gespeichert.

Hat etwas mit 32/64-Bit-Umgebungen, Konfigurationen und den Programmen zu tun, die sie bearbeiten. Meinen Nachmittag ruiniert!!

1187700cookie-checkWebAPI Gzip bei der Rückgabe von HttpResponseMessage

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy