2012-05-04 122 views
17

我目前正致力於將少數MVC3控制器遷移到MVC4 Api控制器。 我已經實現了MVC3控制器獲取方法響應的壓縮機制,通過調用ActionFilterAttribute並覆蓋OnActionExecutiong方法。經過一些研究後,我發現我需要使用從System.Web.HttpFilters。這將是巨大的,如果有人可以分享一段示例代碼,讓我開始了使用gzip壓縮HTTP GET響應

+0

我有同樣的問題,雖然在我的情況我已經啓用IIS壓縮。在你的情況下,它是IIS壓縮,還是你創建自定義處理程序? – Carvellis

+0

是的,我已經使用自定義處理程序,就像Darin在這裏提到的方式。 –

回答

39

最簡單的是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()); 
+0

我認爲這個代碼中存在一個錯誤(以及在網上找到的類似例子):內容長度標題設置不正確,因爲內容長度標題是從壓縮內容複製的。通過傳遞一個StringContent到壓縮處理程序,可以很容易地重現這一點。爲了解決這個問題,帶有'originalContent.Headers'的行需要像這樣修復:'originalContent.Headers.Where(x => x.Key!=「Content-Length」)' –

+0

如果沒有Accept-Encoding被提供。 '如果(response.RequestMessage.Headers.AcceptEncoding!= NULL)'應該是'如果(response.RequestMessage.Headers.AcceptEncoding.Any())' –

+0

我會建議增加在SendAsync編碼類型和分配的分配之間的以下response.Content的允許誤差響應無壓縮返回'如果(!response.StatusCode = HttpStatusCode.OK || response.Content == NULL || string.IsNullOrWhiteSpace(編碼類型))返回響應;' – Paul

6

如果您使用的是IIS 7+這個壓縮HTTP響應,我會說離開壓縮到IIS,因爲它支持gzip壓縮。只需turn it on

在另一方面,壓縮是太靠近金屬爲控制器。理想情況下,控制器應該比字節和數據流工作在更高的水平。

+0

總的來說,我同意,但是IIS級別的壓縮需要配置使用它的任何服務器。 – samosaris

3

使用一個類,並用下面的代碼

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

現在使用下面的屬性,控制器或任何API操作方法是這樣

[Route("GetData")] 
[CompressFilter]   
public HttpResponseMessage GetData() 
{ 
} 
+0

我的Web API上配置了OWIN中間件,這是唯一適用於我的解決方案。另外,你可以真正地定位你想要壓縮的東西。好解決方案! – Elferone