WebAPI Gzip when returning HttpResponseMessage

后端 未结 4 543
心在旅途
心在旅途 2021-01-30 09:13

I have a WebAPI controller that returns an HttpResponseMessage and I want to add gzip compression. This is the server code:

using System.Net.Http;
u         


        
相关标签:
4条回答
  • 2021-01-30 09:36

    If you have access to IIS configuration

    You cant just apply the header and hope it will be gzipped - the response will not be zipped.

    You need remove the header you added and ensure you have the dynamic compression and static content compression are enabled on your IIS server.

    One of the commenter's mentioned a good resource link here at stakoverflow that show how to do that:

    Enable IIS7 gzip

    Note it will only work setting the value in web.config if dynamic compression is already installed (which is not in a default install of IIS)

    You can find the information about this on MSDN documentation: http://www.iis.net/configreference/system.webserver/httpcompression

    Simple compression

    Below is using a simple example of doing your own compression this example is using the Web Api MVC 4 project from visual studio project templates. To get compression working for HttpResponseMessages you have to implement a custom MessageHandler. See below a working example.

    See the code implementation below.

    Please note that I tried to keep the method doing the same as your example.

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

    Also add the new message handler to the config of your app.

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

    The Custom handler was put together by - Kiran Challa (http://blogs.msdn.com/b/kiranchalla/archive/2012/09/04/handling-compression-accept-encoding-sample.aspx)

    There are better examples that implement deflating of inbound streams too you can see examples of that below:

    • 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

    Additionally I found a really nice project that supports all of this on github.

    • https://github.com/azzlack/Microsoft.AspNet.WebApi.MessageHandlers.Compression

    Note while I arrived to this answer by myself Simon in your comments suggested this approach 2 days ago from the date of this answer.

    0 讨论(0)
  • 2021-01-30 09:39

    Add these NuGet packages:

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

    Then and add one line of code to App_Start\WebApiConfig.cs:

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

    That will do the trick!

    Details at:

    • NuGet package page
    • GitHub

    Hope that helps.

    **Updated after comment from @JCisar

    Update for ASP.Net Core

    Nuget Package is

    Microsoft.AspNetCore.ResponseCompression

    0 讨论(0)
  • 2021-01-30 09:55

    Just an addendum to enabling compression in IIS via the applicationHost.config file.

    Use the IIS config manager to make the changes or notepad.exe to edit the file. I was using Notepad++ and even though the file was saving, it actually was not.

    Something to do with 32/64bit environments, configs and the programs that edit them. Ruined my afternoon!!

    0 讨论(0)
  • 2021-01-30 10:03

    One Solution without editing any IIS Setting or Installing any Nuget package is to add a MessageHandler to your WEB API.

    This will catch requests with the "AcceptEncoding" Header and compress them using the Build in System.IO.Compression libraries.

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

    And add this handler to your Global.asax.cs

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

    Kudos to Ben Foster. ASP.NET Web API Compression

    0 讨论(0)
提交回复
热议问题