Подтвердить что ты не робот

WebAPI Gzip при возврате HttpResponseMessage

У меня есть контроллер 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);
}

И это код клиента для вызова ajax, используя jquery:

$.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

Что мне нужно изменить, чтобы заставить веб-службу возвращать gzipped-контент, который клиент обрабатывает нормально? Я знаю, что я мог бы сделать это с помощью HttpModule или с помощью некоторых параметров в IIS, но ни один из вариантов не соответствует сценарию хостинга:

enter image description here

Обратите внимание, что я не ищу настройки IIS, потому что у меня нет доступа к этому (хостинг).

4b9b3361

Ответ 1

Если у вас есть доступ к конфигурации IIS

Вы не можете просто применить заголовок и надеяться, что он будет gzipped - ответ не будет заархивирован.

Вам нужно удалить добавленный заголовок и убедиться, что на вашем сервере IIS включено динамическое сжатие и сжатие статического содержимого.

Один из комментаторов упомянул ссылку на хороший ресурс здесь, в stakoverflow, который показывает, как это сделать:

Включить IIS7 gzip

Обратите внимание, что это будет работать только при настройке значения в файле web.config, если динамическое сжатие уже установлено (которое не находится в установке IIS по умолчанию)

Информацию об этом вы можете найти в документации MSDN: http://www.iis.net/configreference/system.webserver/httpcompression

Простая компрессия

Ниже приведен простой пример выполнения собственного сжатия. В этом примере используется проект 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.

Обратите внимание, что я пришел к этому ответу сам Саймон в ваших комментариях предложил этот подход 2 дня назад с даты этого ответа.

Ответ 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()));

Это сделает трюк!

Подробности:

Надеюсь, что это поможет.

** Обновлен после комментария от @JCisar

Обновление для ядра ASP.Net

Пакет Nuget

Microsoft.AspNetCore.ResponseCompression

Ответ 3

Просто добавление к включению сжатия в IIS через файл applicationHost.config.

Используйте конфигурационный менеджер IIS, чтобы внести изменения или notepad.exe, чтобы отредактировать файл. Я использовал Notepad++, и хотя файл сохранялся, на самом деле это не так.

Что-то делать с 32/64-битными средами, конфигурациями и программами, которые их редактируют. Разрушенный мой день!

Ответ 4

Одно решение без редактирования любого параметра IIS или установки любого пакета Nuget - это добавить MessageHandler к вашему веб-интерфейсу.

Это вызовет запросы с заголовком "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<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());

Престижность Бен Фостер. Сжатие веб-API ASP.NET