返回HttpResponseMessage时的WebAPI Gzip
我有一个WebAPI控制器,它返回一个HttpResponseMessage
,我想添加gzip压缩。 这是服务器代码:
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); }
这是使用jquery的ajax调用的客户端代码:
$.ajax({ url: "/SomeRoute", type: "POST", cache: "false", data: SomeData, beforeSend: function (jqXHR) { jqXHR.setRequestHeader('Accept-Encoding', 'gzip'); }, success: function(msg) { ... }
当我运行它时,服务器代码返回没有错误,但客户端错误:
(failed) net::ERR_CONTENT_DECODING_FAILED
当我和Fiddler一起看时,这就是我所看到的:
我需要更改什么才能使Web服务返回客户端正常处理的gzip压缩内容? 我知道我也可以使用HttpModule或通过IIS上的某些设置来执行此操作,但这两种选项都不适合托管的情况:
请注意,我不是在寻找IIS设置,因为我无权访问(托管)。
如果您有权访问IIS配置
你不能只应用标题,希望它将被gzip压缩 – 响应将不会被压缩。
您需要删除添加的标头,并确保在IIS服务器上启用了动态压缩和静态内容压缩。
其中一位评论者在stakoverflow上提到了一个很好的资源链接,展示了如何做到这一点:
启用IIS7 gzip
请注意,如果已安装动态压缩(不在IIS的默认安装中),它将仅在web.config中设置值
您可以在MSDN文档中找到有关此内容的信息: http : //www.iis.net/configreference/system.webserver/httpcompression
简单的压缩
下面是使用一个简单的自己进行压缩的例子,这个例子是使用visual studio项目模板中的Web Api MVC 4项目。 要使压缩工作HttpResponseMessages,您必须实现自定义MessageHandler。 请参阅下面的工作示例。
请参阅下面的代码实现。
请注意,我试图保持方法与您的示例相同。
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 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 SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return base.SendAsync(request, cancellationToken).ContinueWith ((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> 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(); } }); } } } }
还要将新消息处理程序添加到应用程序的配置中。
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(); } } }
自定义处理程序由 – Kiran Challa( http://blogs.msdn.com/b/kiranchalla/archive/2012/09/04/handling-compression-accept-encoding-sample.aspx )组合在一起。
有更好的示例可以实现入站流的缩小,您可以看到以下示例:
- http://www.codeproject.com/Articles/557232/Implementing-a-Custom-DelegatingHandler-in-ASP-NET
- http://ronaldrosiernet.azurewebsites.net/blog/2013/07/16/implement_compression_in_aspnet_web_api
另外,我发现了一个非常好的项目,它支持github上的所有这些。
请注意,当我自己到达这个答案时,Simon在你的评论中提出了这个方法,从这个答案发布之日起2天前。
添加这些NuGet包:
Microsoft.AspNet.WebApi.Extensions.Compression.Server System.Net.Http.Extensions.Compression.Client
然后向App_Start\WebApiConfig.cs
添加一行代码:
GlobalConfiguration.Configuration.MessageHandlers.Insert(0, new ServerCompressionHandler(new GZipCompressor(), new DeflateCompressor()));
那就行了!
详细信息:
- NuGet包页面
- GitHub上
希望有所帮助。
** @JCisar发表评论后更新
ASP.Net Core的更新
Nuget包是
Microsoft.AspNetCore.ResponseCompression
一个没有编辑任何IIS设置或安装任何Nuget包的解决方案是将MessageHandler添加到您的WEB API。
这将使用“AcceptEncoding”标头捕获请求,并使用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 SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { return base.SendAsync(request, cancellationToken).ContinueWith ((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> 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(); } }); } }
并将此处理程序添加到Global.asax.cs
GlobalConfiguration.Configuration.MessageHandlers.Insert(0, CompressHandler.GetSingleton());
感谢Ben Foster。 ASP.NET Web API压缩
只是通过applicationHost.config
文件在IIS中启用压缩的附录。
使用IIS配置管理器进行更改或使用notepad.exe
编辑文件。 我使用的是Notepad++
,即使该文件正在保存,但实际上并非如此。
与32/64位环境,配置和编辑它们的程序有关。 毁了我的下午!!