web-dev-qa-db-fra.com

WebAPI Gzip lors du retour de HttpResponseMessage

J'ai un contrôleur WebAPI qui retourne un HttpResponseMessage et je veux ajouter une compression gzip. Voici le code du serveur:

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);
}

Et voici le code client pour l'appel ajax, en utilisant jquery:

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

Lorsque j'exécute cela, le code du serveur revient sans bug, mais les bugs du client:

(failed)
net::ERR_CONTENT_DECODING_FAILED

enter image description here

Quand je regarde avec Fiddler, voici ce que je vois:

enter image description here

Que dois-je changer pour que le service Web renvoie du contenu compressé que le client traite normalement? Je sais que je pourrais également le faire avec un HttpModule ou via un paramètre sur IIS mais aucune des options ne correspond au scénario de l'hébergement:

enter image description here

Veuillez noter que je ne recherche pas un paramètre IIS car je n'y ai pas accès (hébergement).

25
frenchie

Si vous avez accès à IIS

Vous ne pouvez pas simplement appliquer l'en-tête et espérer qu'il sera compressé - la réponse ne sera pas compressée.

Vous devez supprimer l'en-tête que vous avez ajouté et vous assurer que la compression dynamique et la compression de contenu statique sont activées sur votre serveur IIS.

Un des commentateurs a mentionné un bon lien de ressource ici sur stakoverflow qui montre comment faire:

Activer gzip IIS7

Notez que cela ne fonctionnera qu'en définissant la valeur dans web.config si la compression dynamique est déjà installée (qui n'est pas dans une installation par défaut d'IIS)

Vous pouvez trouver les informations à ce sujet dans la documentation MSDN: http://www.iis.net/configreference/system.webserver/httpcompression

Compression simple

Vous trouverez ci-dessous un exemple simple de création de votre propre compression. Cet exemple utilise le projet Web Api MVC 4 à partir de modèles de projet Visual Studio. Pour que la compression fonctionne pour HttpResponseMessages, vous devez implémenter un MessageHandler personnalisé. Voir ci-dessous un exemple de travail.

Voir l'implémentation du code ci-dessous.

Veuillez noter que j'ai essayé de garder la méthode identique à votre exemple.

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();
                    }
                });
            }
        }
    }
}

Ajoutez également le nouveau gestionnaire de messages à la configuration de votre application.

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();
        }
    }
}

Le gestionnaire personnalisé a été créé par - Kiran Challa ( http://blogs.msdn.com/b/kiranchalla/archive/2012/09/04/handling-compression-accept-encoding-sample.aspx =)

Il existe de meilleurs exemples qui implémentent la déflation des flux entrants, vous pouvez en voir des exemples ci-dessous:

De plus, j'ai trouvé un projet vraiment sympa qui prend en charge tout cela sur github.

Notez que lorsque je suis arrivé à cette réponse par moi-même, Simon dans vos commentaires a suggéré cette approche il y a 2 jours à compter de la date de cette réponse.

20
dmportella

Ajoutez ces packages NuGet:

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

Ensuite, ajoutez une ligne de code à App_Start\WebApiConfig.cs:

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

Cela fera l'affaire!

Détails sur:

J'espère que cela pourra aider.

** Mis à jour après le commentaire de @JCisar

Mise à jour pour ASP.Net Core

Le package Nuget est

Microsoft.AspNetCore.ResponseCompression

47
Pooran

Une solution sans aucune modification IIS Configuration ou installation de Nuget le package consiste à ajouter un MessageHandler à votre API WEB.

Cela interceptera les demandes avec l'en-tête "AcceptEncoding" et les compressera à l'aide des bibliothèques Build.In System.IO.Compression .

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();
            }
        });
    }
}

Et ajoutez ce gestionnaire à votre Global.asax.cs

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

Bravo à Ben Foster. Compression API Web ASP.NET

4

Juste un addendum pour activer la compression dans IIS via le applicationHost.config fichier.

tilisez le IIS config manager pour apporter les modifications ou notepad.exe pour modifier le fichier. J'utilisais Notepad++ et même si le fichier était en cours d'enregistrement, ce n'était pas le cas.

Quelque chose à voir avec les environnements 32/64bit, les configurations et les programmes qui les éditent. Ruiné mon après-midi !!

2
jenson-button-event