HttpClient與APS.NET Web API:請求內容的壓縮與解壓


首先說明一下,這里的壓縮與解壓不是通常所說的http compression——那是響應內容在服務端壓縮、在客戶端解壓,而這里是請求內容在客戶端壓縮、在服務端解壓。

對於響應內容的壓縮,一般Web服務器(比如IIS)都提供了內置支持,只需在請求頭中包含 Accept-Encoding: gzip, deflate ,客戶端瀏覽器與HttpClient都提供了內置的解壓支持。HttpClient中啟用這個壓縮的代碼如下:

var httpClient = new HttpClient(new HttpClientHandler { AutomaticDecompression = 
    System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate });

對於請求內容的壓縮,.NET中的HttpClient並沒有提供內置支持,IIS也沒有提供對解壓的內置支持,需要自己寫代碼實現,本文也是由此而生。

為什么要對請求內容進行壓縮呢?目前我們在2種應用場景下遇到:1)用HttpClient調用第三方Web API;2)或者iOS App調用自己的Web API時需要提交大文本數據。

對於壓縮與解壓,System.IO.Compression中提供了對應的類庫——GZipStream與DeflateStream,我們只需要在HttpClient與Web API中應用它們即可。

先來看看客戶端HttpClient的實現。我們需要實現一個支持壓縮的HttpContent——CompressedContent,實現代碼如下:

public enum CompressionMethod
{
    GZip = 1,
    Deflate = 2
}

public class CompressedContent : HttpContent
{
    private readonly HttpContent _originalContent;
    private readonly CompressionMethod _compressionMethod;

    public CompressedContent(HttpContent content, CompressionMethod compressionMethod)
    {
        if (content == null)
        {
            throw new ArgumentNullException("content");
        }

        _originalContent = content;
        _compressionMethod = compressionMethod;

        foreach (KeyValuePair<string, IEnumerable<string>> header in _originalContent.Headers)
        {
            Headers.TryAddWithoutValidation(header.Key, header.Value);
        }

        Headers.ContentEncoding.Add(_compressionMethod.ToString().ToLowerInvariant());
    }

    protected override bool TryComputeLength(out long length)
    {
        length = -1;
        return false;
    }

    protected async override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        if (_compressionMethod == CompressionMethod.GZip)
        {
            using (var gzipStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true))
            {
                await _originalContent.CopyToAsync(gzipStream);
            }
        }
        else if (_compressionMethod == CompressionMethod.Deflate)
        {
            using (var deflateStream = new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true))
            {
                await _originalContent.CopyToAsync(deflateStream);
            }
        }
    }
}

主要就是重載HttpContent.SerializeToStreamAsync()方法,在其中使用相應的壓縮算法進行壓縮。

HttpClient使用這個CompressedContent的方法如下:

var json = JsonConvert.SerializeObject(bookmark);
var content = new CompressedContent(
    new StringContent(json, Encoding.UTF8, "application/json"), 
    CompressionMethod.GZip);
var response = await _httpClient.PostAsync("/api/bookmarks", content);

再來看看服務端ASP.NET Web API中的實現,需要實現一個DelegatingHandler——DecompressionHandler:

public class DecompressionHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, 
        CancellationToken cancellationToken)
    {
        if (request.Method == HttpMethod.Post)
        {
            bool isGzip = request.Content.Headers.ContentEncoding.Contains("gzip");
            bool isDeflate = !isGzip && request.Content.Headers.ContentEncoding.Contains("deflate");

            if (isGzip || isDeflate)
            {
                Stream decompressedStream = new MemoryStream();

                if (isGzip)
                {
                    using (var gzipStream = new GZipStream(await request.Content.ReadAsStreamAsync(),
                        CompressionMode.Decompress))
                    {
                        await gzipStream.CopyToAsync(decompressedStream);
                    }
                }
                else if (isDeflate)
                {

                    using (var gzipStream = new DeflateStream(await request.Content.ReadAsStreamAsync(),
                        CompressionMode.Decompress))
                    {
                        await gzipStream.CopyToAsync(decompressedStream);
                    }
                }

                decompressedStream.Seek(0, SeekOrigin.Begin);

                var originContent = request.Content;
                request.Content = new StreamContent(decompressedStream);

                foreach (var header in originContent.Headers)
                {
                    request.Content.Headers.Add(header.Key, header.Value);
                }
            }
        }

        return await base.SendAsync(request, cancellationToken);
    }
}

重載DelegatingHandler.SendAsync()方法,在其中用GZipStream或DeflateStream完成解壓操作。

然后在WebApiConfig中應用這個DecompressionHandler,代碼如下:

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.MessageHandlers.Add(new DecompressionHandler());
    }
}

最后用這個支持請求內容壓縮的HttpClient調用一下這個支持請求內容解壓的Web API測試一下,用WireShark抓包看一下壓縮是否生效。

測試成功!

【參考資料】

How to compress http request on the fly and without loading compressed buffer in memory

How do enable a .Net web-API to accept g-ziped posts

HTTP Message Handlers in ASP.NET Web API

Compressed HTTP Requests


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM