WebAPI返回HttpResponseMessage时启用Gzip压缩

33

我有一个返回 HttpResponseMessage 的 WebAPI 控制器,我想添加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

enter image description here

使用Fiddler查看时,我看到的是这样的:

enter image description here

我需要更改什么才能使Web服务返回客户端正常处理的gzip压缩内容?我知道也可以通过HttpModule或IIS上的一些设置来完成此操作,但两个选项都不符合主机场景:

enter image description here

请注意,我不寻求IIS设置,因为我无法访问该设置(托管)。


1
你看过这个吗?https://dev59.com/pmkv5IYBdhLWcg3wqSf1#10446108 - Jeow Li Huan
请查看http://weblog.west-wind.com/posts/2012/Apr/28/GZipDeflate-Compression-in-ASPNET-MVC。 - Code Uniquely
@JeowLiHuan:我本来希望能够用更少的步骤完成这个。 - frenchie
1
检查这个 - https://dev59.com/jVDTa4cB1Zd3GeqPH0qd#3653766 并尝试使用建议的包装器以避免不良行为。还请参阅此答案 https://dev59.com/OFPTa4cB1Zd3GeqPl7gK#7629079 - Ivan Samygin
1
看起来你可以直接在IIS上完成。链接有点旧了 --- https://dev59.com/S3RB5IYBdhLWcg3wJklC - Tasos
显示剩余3条评论
4个回答

53
请添加以下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()));

那会起作用的!

详情请见:

**在@JCisar的评论之后更新

针对ASP.Net Core的更新

Nuget包为

Microsoft.AspNetCore.ResponseCompression


4
这比启用动态 IIS 压缩要好得多。 - Brain2000
7
谢谢!尽管包装上写着已经过时了(我猜是从2016年1月28日开始)。有人知道这个东西是否被替换成了什么新的东西吗?我没有看到任何提及。 - JCisar
4
根据软件包作者的说法,你应该使用由他维护的那些新软件包。链接在此:https://github.com/azzlack/Microsoft.AspNet.WebApi.MessageHandlers.Compression - Zignd
1
注意,我无法通过这种方法使其工作,实际上必须在“Register”方法中使用config.MessageHandlers.Insert(0, new ServerCompressionHandler(new GZipCompressor(), new DeflateCompressor())); - Chris
如果你正在使用一个“传统”的ASP.NET网站,大约是.NET Framework 4.5/4.6/4.7等版本,你会需要这些包,但是请确保在nuget上搜索时加上“.StrongName”,否则它们将无法加载。我不知道为什么他们将这些拆分成不同的版本,而不只发布强名称版本,但是他们没有这样做:Microsoft.AspNet.WebApi.Extensions.Compression.Server.StrongName System.Net.Http.Extensions.Compression.Client.StrongName(实际上,这个不存在,但你可以fork这个仓库并创建它) - undefined
显示剩余4条评论

26

如果您可以访问IIS配置

您不能仅应用头文件并希望它被压缩 - 响应将不会被压缩。

您需要删除您添加的头信息,并确保您的IIS服务器已启用动态压缩和静态内容压缩。

评论者中的一位提到了 stackoverflow 上的一个很好的资源链接,展示了如何做到这一点:

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

还要将新的消息处理程序添加到应用程序的配置中。

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)创建的。

还有更好的示例实现了入站流的压缩,您可以在以下示例中查看:

此外,我在GitHub上发现了一个非常好的项目,支持所有这些功能。

请注意,虽然我是自己找到这个答案的,但是根据您在评论中的建议,Simon在两天前提出了这种方法。


1
我不寻求一种IIS解决方案,因为我无法访问。这就是为什么我需要在WebAPI外进行压缩的原因。 - frenchie
3
请参考 http://benfoster.io/blog/aspnet-web-api-compression 上的示例,在 IIS 外实现网页压缩。 - dmportella
每个人都说“这很简单,看看这个链接”。你的回答有很多链接,但没有代码。 - frenchie
我找到了一些更好的示例,你可以使用。现在检查我的答案。 - dmportella
1
谢谢您指出这个问题,我猜您的意思是accept-encoding而不是accept-enconding! - The Senator
显示剩余19条评论

5

一个不需要编辑任何IIS设置或安装任何Nuget包的解决方案是在您的WEB API中添加一个MessageHandler

这将捕获带有“AcceptEncoding”标头的请求,并使用内置的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();
            }
        });
    }
}

将此处理程序添加到Global.asax.cs文件中。
GlobalConfiguration.Configuration.MessageHandlers.Insert(0, CompressHandler.GetSingleton());

感谢Ben Foster。 ASP.NET Web API压缩

谢谢!正是我需要的,用来替换一些过时的 NuGet 包。 :) - BeanFlicker

2
只是关于通过 applicationHost.config 文件在 IIS 中启用压缩的一个补充说明。
使用 IIS 配置管理器 进行更改或使用 notepad.exe 编辑文件。我使用的是 Notepad++,即使文件保存了,实际上也没有保存。
这与 32/64位环境、配置和编辑它们的程序有关。毁掉了我的下午!!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接