В настоящее время я работаю над переносом нескольких моих MVC3-контроллеров в контроллеры MVC4 Api. Я реализовал механизм сжатия для контроллера MVC3. Получить ответы метода по умолчанию ActionFilterAttribute
и переопределить метод OnActionExecutiong
. После некоторых исследований я обнаружил, что мне нужно использовать ActionFilterMethod
от System.Web.HttpFilters
. Было бы здорово, если бы кто-нибудь мог поделиться куском кода примера, чтобы начать работу с этим сжатием HTTP-ответа, используя GZipСжатие HTTP GET Response
ответ
Самый простой способ - enable compression непосредственно на уровне IIS.
Если вы хотите сделать это на уровне приложения можно написать пользовательское делегирование обработчика сообщений, как показано на following post:
public class CompressHandler : 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)
{
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.AddWithoutValidation(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();
}
});
}
}
Все, что осталось теперь зарегистрировать обработчик в Application_Start
:
GlobalConfiguration.Configuration.MessageHandlers.Add(new CompressHandler());
Я думаю, что в этом коде есть ошибка (как и в аналогичных примерах, найденных в Интернете): Заголовок Content-Length задан неправильно, потому что заголовок Content-Length Header копируется из содержимого gzipped. Это можно легко воспроизвести, передав StringContent через обработчик сжатия. Чтобы исправить это, строка с 'originalContent.Headers' должна быть исправлена следующим образом:' originalContent.Headers.Where (x => x.Key! = "Content-Length") ' –
Код не будет работать, если нет Accept-Encoding предоставлен. 'if (response.RequestMessage.Headers.AcceptEncoding! = null)' должно быть 'if (response.RequestMessage.Headers.AcceptEncoding.Any())' –
Я бы рекомендовал добавить следующее в SendAsync между назначением encodingType и присваиванием ответа.Content, чтобы позволить ответы об ошибках возвращаться без сжатия 'if (response.StatusCode! = HttpStatusCode.OK || response.Content == null || string.IsNullOrWhiteSpace (encodingType)) return response;' – Paul
Если вы используете IIS 7+, я бы сказал, что вы должны отключить сжатие до IIS, поскольку оно поддерживает сжатие GZIP. Просто turn it on.
С другой стороны, сжатие слишком близко к металлу для контроллера. В идеале контроллер должен работать на гораздо более высоком уровне, чем байты и потоки.
В общем, я согласен, однако для сжатия уровня IIS потребуется настройка любых серверов, использующих его. – samosaris
Используйте класс и написать следующий код
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class CompressFilter : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext context)
{
var acceptedEncoding = context.Response.RequestMessage.Headers.AcceptEncoding.First().Value;
if (!acceptedEncoding.Equals("gzip", StringComparison.InvariantCultureIgnoreCase)
&& !acceptedEncoding.Equals("deflate", StringComparison.InvariantCultureIgnoreCase))
{
return;
}
context.Response.Content = new CompressedContent(context.Response.Content, acceptedEncoding);
}
}
Теперь создайте еще один класс и напишите следующий код.
public class CompressedContent : HttpContent
{
private readonly string _encodingType;
private readonly HttpContent _originalContent;
public CompressedContent(HttpContent content, string encodingType = "gzip")
{
if (content == null)
{
throw new ArgumentNullException("content");
}
_originalContent = content;
_encodingType = encodingType.ToLowerInvariant();
foreach (var header in _originalContent.Headers)
{
Headers.TryAddWithoutValidation(header.Key, header.Value);
}
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;
switch (_encodingType)
{
case "gzip":
compressedStream = new GZipStream(stream, CompressionMode.Compress, true);
break;
case "deflate":
compressedStream = new DeflateStream(stream, CompressionMode.Compress, true);
break;
default:
compressedStream = stream;
break;
}
return _originalContent.CopyToAsync(compressedStream).ContinueWith(tsk =>
{
if (compressedStream != null)
{
compressedStream.Dispose();
}
});
}
}
Теперь используйте следующий атрибут в контроллере или в любом методе апите действия как этот
[Route("GetData")]
[CompressFilter]
public HttpResponseMessage GetData()
{
}
У меня есть промежуточное ПО OWIN, настроенное в моем веб-API, и это единственное решение, которое сработало для меня. Кроме того, вы можете настроить таргетинг на то, что вы хотите сжать. Хорошее решение! – Elferone
У меня та же проблема, хотя в моем случае я уже включен сжатие IIS. В вашем случае это было сжатие IIS, или вы создали пользовательский обработчик? – Carvellis
Да, я использовал пользовательский обработчик для этого точно так же, как здесь упоминал Дарин. –