c# jquery asp.net asp.net-web-api gzip

c# - WebAPI Gzip al devolver HttpResponseMessage



jquery asp.net (4)

Tengo un controlador WebAPI que devuelve HttpResponseMessage y quiero agregar compresión gzip. Este es el código del servidor:

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

Y este es el código de cliente para la llamada ajax, usando jquery:

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

Cuando ejecuto esto, el código del servidor vuelve sin molestar, pero los errores del cliente:

(failed) net::ERR_CONTENT_DECODING_FAILED

Cuando miro con Fiddler, esto es lo que veo:

¿Qué debo cambiar para que el servicio web devuelva contenido comprimido que el cliente procesa normalmente? Sé que también podría hacer esto con un HttpModule o mediante alguna configuración de IIS, pero ninguna opción se ajusta al escenario del hosting:

Tenga en cuenta que no estoy buscando una configuración de IIS porque no tengo acceso a eso (hosting).


Una solución sin editar ninguna configuración de IIS o instalar ningún paquete Nuget es agregar un MessageHandler a su API WEB.

Esto capturará las solicitudes con el encabezado "AcceptEncoding" y las comprimirá utilizando las bibliotecas 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(); } }); } }

Y agregue este controlador a su Global.asax.cs

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

Felicitaciones a Ben Foster. Compresión API web ASP.NET


Solo una adición para habilitar la compresión en IIS a través del archivo applicationHost.config .

Utilice el administrador de configuración de IIS para realizar los cambios o notepad.exe para editar el archivo. Estaba usando Notepad++ y aunque el archivo se estaba guardando, en realidad no lo era.

Algo relacionado con los entornos de 32/64 bits, las configuraciones y los programas que los editan. Arruinó mi tarde !!


Si tiene acceso a la configuración de IIS

No puedes aplicar el encabezado y esperar que se gzipee, la respuesta no se comprimirá.

Debe eliminar el encabezado que agregó y asegurarse de tener la compresión dinámica y la compresión de contenido estático habilitadas en su servidor IIS.

Uno de los comentadores mencionó un buen enlace de recursos aquí en stakoverflow que muestra cómo hacerlo:

Habilitar IIS7 gzip

Tenga en cuenta que solo funcionará establecer el valor en web.config si la compresión dinámica ya está instalada (que no está en una instalación predeterminada de IIS)

Puede encontrar la información sobre esto en la documentación de MSDN: http://www.iis.net/configreference/system.webserver/httpcompression

Compresión simple

A continuación se muestra un ejemplo simple de cómo hacer su propia compresión: en este ejemplo, se utiliza el proyecto Web Api MVC 4 de las plantillas de proyectos de Visual Studio. Para que la compresión funcione para HttpResponseMessages, debe implementar un MessageHandler personalizado. Vea a continuación un ejemplo de trabajo.

Vea la implementación del código a continuación.

Tenga en cuenta que traté de mantener el método haciendo lo mismo que su ejemplo.

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

También agregue el nuevo controlador de mensajes a la configuración de su aplicación.

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

El controlador personalizado fue creado por Kiran Challa ( http://blogs.msdn.com/b/kiranchalla/archive/2012/09/04/handling-compression-accept-encoding-sample.aspx )

Hay mejores ejemplos que implementan el desinflado de flujos entrantes, también puedes ver ejemplos de eso a continuación:

Además, encontré un proyecto realmente bueno que admite todo esto en github.

Nota: mientras yo llegaba a esta respuesta, Simon en sus comentarios sugirió este enfoque hace 2 días a partir de la fecha de esta respuesta.


Agregue estos paquetes NuGet:

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

A continuación, agregue una línea de código a App_Start/WebApiConfig.cs :

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

Eso hará el truco!

Detalles en:

Espero que ayude.

** Actualizado después del comentario de @JCisar

Actualización para ASP.Net Core

El paquete Nuget es

Microsoft.AspNetCore.ResponseCompression