POST GZIP/Deflate Data to ASP.NET Web API
前一篇使用GZIP或DEFLATE壓縮,提升資料傳遞效能40倍,我們處理了查詢資料量大時,Web API 在傳遞未經壓縮的資料產生許多不必要的網路流量的問題。現在我們轉換方向,如果是用戶端要傳遞大量資料至 Web API 時,那麼我們要怎麼處理?這比較麻煩,有二個方向要討論,一是 Client 端進行發送請求時,必須先做 GZIP/Deflate 的壓縮,而 Web API 接收到 GZIP/Deflate 壓縮資料後需要解壓縮還原資料內容。
從這個方向來思考,你應該能發現另一件事,就是前一篇我們只專注在 GZIP/Deflate 的壓縮上,並無做任何解壓縮的工作,那是因為瀏覽器本身在協商過程與接收到的 Header 可以判定接收到的是 GZIP/Deflate 的內容,瀏覽器會直接幫我們進行 GZIP/Deflate 的解壓縮工作。
DecompressionHandler
一樣先思考資訊流,與前一篇方向完全相反,這裡是用戶端傳遞資料,是進來的方向,有二種擴充點可以選擇:MessageHandler、Action Filter。這二種擴充點都能做出符合需求解法,但因為進來的是資料,希望越早處理越好,以下選擇使用 MessageHandler 來處理。
/// <summary>
/// Decompression GZIP, Deflate content
/// </summary>
/// <seealso cref="System.Net.Http.DelegatingHandler" />
public class DecompressionHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.Content.Headers.ContentEncoding.Any())
{
var encodes = request.Content.Headers.ContentEncoding.ToList();
foreach (var encode in encodes)
{
if (encode.Equals("deflate", StringComparison.InvariantCultureIgnoreCase))
{
request.Content = DecompressDeflateContent(request);
}
request.Content = DecompressGzipContent(request);
}
}
return base.SendAsync(request, cancellationToken);
}
/// <summary>
/// Decompresses the content of the gzip.
/// </summary>
/// <param name="request">The request.</param>
/// <returns></returns>
private HttpContent DecompressGzipContent(HttpRequestMessage request)
{
MemoryStream outputStream = new MemoryStream();
var inputStream = request.Content.ReadAsStreamAsync().Result;
using (var decompressor = new GZipStream(inputStream, CompressionMode.Decompress))
{
decompressor.CopyToAsync(outputStream);
}
outputStream.Seek(0, SeekOrigin.Begin);
HttpContent newContent = AddHeadersToNewContent(request, outputStream);
return newContent;
}
/// <summary>
/// Decompresses the content of the deflate.
/// </summary>
/// <param name="request">The request.</param>
/// <returns></returns>
private HttpContent DecompressDeflateContent(HttpRequestMessage request)
{
MemoryStream outputStream = new MemoryStream();
var inputStream = request.Content.ReadAsStreamAsync().Result;
using (var decompressor = new DeflateStream(inputStream, CompressionMode.Decompress))
{
decompressor.CopyToAsync(outputStream);
}
outputStream.Seek(0, SeekOrigin.Begin);
HttpContent newContent = AddHeadersToNewContent(request, outputStream);
return newContent;
}
private HttpContent AddHeadersToNewContent(HttpRequestMessage request, MemoryStream outputStream)
{
HttpContent requestContent = request.Content;
HttpContent newContent = new StreamContent(outputStream);
// 複製原內容所有 Header 到 newContent
foreach (var header in requestContent.Headers)
{
// 更改 content-encoding header 到預設值
if (header.Key.ToLowerInvariant() == "content-encoding")
{
newContent.Headers.Add(header.Key, "identity");
continue;
}
// 更改 content-length header 值為解壓縮後的長度
if (header.Key.ToLowerInvariant() == "content-length")
{
newContent.Headers.Add(header.Key, outputStream.Length.ToString());
continue;
}
newContent.Headers.Add(header.Key, header.Value);
}
return newContent;
}
}
其中的 content-encoding
操作可能是大家可比較看不懂的,可以參考 MDN 文件,以下節錄 identity
說明。
identity:Indicates the identity function (i.e. no compression, nor modification). This token, except if explicitly specified, is always deemed acceptable.
HTTP Client with GZIP/Deflate
這等於前一篇在做的事,現在換用戶端來做,發送前設定應有 Header 與壓縮,然後進行傳送。以下整理我常用的 HttpClient 與 RestSharp 兩個套件的 GZIP/Deflate 設置方式。
HttpClient
HttpClient 類別 是 .NET Framework 內建類別,它簡單好用且全部都是非同步方法,非常合適 RESTful API 的用戶端呼叫使用。
void Main(string[] args)
{
Task t = MainAsync(args);
t.Wait();
}
// Define other methods and classes here
static async Task MainAsync(string[] args)
{
var jsonData = new
{
Name = "ASP.NET Web API",
Site = "skilltree.my",
Lecturer = "Bruce Chen"
};
var jsonParam = JsonConvert.SerializeObject(jsonData);
var content = JsonToGZipStream(jsonParam);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
content.Headers.ContentEncoding.Add("gzip");
HttpClientHandler handler = new HttpClientHandler()
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
using (var client = new HttpClient(handler))
{
var response = await client.PostAsync("https://requestb.in/18i73061", content).ConfigureAwait(false);
var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
Console.WriteLine(result);
}
}
static StreamContent JsonToGZipStream(string jsonParam)
{
byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonParam);
MemoryStream ms = new MemoryStream();
using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress, true))
{
gzip.Write(jsonBytes, 0, jsonBytes.Length);
}
ms.Position = 0;
StreamContent content = new StreamContent(ms);
return content;
}
與平常的寫法差異只有加入 HttpClientHandler
的組態,指定 AutomaticDecompression
,而這一行組態的重點在於要能處理前篇 ASP.NET Web API 已經把回應都使用 GZip 壓縮,當 HttpClient
取得使用 GZip 壓縮資料才能還原資料,這個還原資料的動作不需要在撰寫任何程式碼。然後 JSON 資料在傳遞前需要先使用 GZipStream
處理並指定好 Header 即可進行 POST 操作。
測試結果可以利用 https://requestb.in/ 查看:
RestSharp.org
RestSharp 也是一套強調簡單易用開源 HTTP Client 套件,我們也大量用於專案內。
void Main()
{
var jsonData = new
{
Name = "ASP.NET Web API",
Site = "skilltree.my",
Lecturer = "Bruce Chen"
};
var jsonParam = JsonConvert.SerializeObject(jsonData);
var content = JsonToGZipStream(jsonParam);
var client = new RestClient("https://requestb.in/18i73061");
var request = new RestRequest(Method.POST);
request.AddHeader("cache-control", "no-cache");
request.AddHeader("content-type", "application/json");
request.AddHeader("content-encoding", "gzip");
request.AddParameter("application/json", content.ToArray(), ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
}
static MemoryStream JsonToGZipStream(string jsonParam)
{
byte[] jsonBytes = Encoding.UTF8.GetBytes(jsonParam);
MemoryStream ms = new MemoryStream();
using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress, true))
{
gzip.Write(jsonBytes, 0, jsonBytes.Length);
}
ms.Position = 0;
return ms;
}
RestSharp 不用特別設置本身即支援 GZip 的解壓縮,唯一要處理的是先將 JSON 做 GZipStream 處理,這部分前面已經看很多了,就不多解釋了。一樣來看一下 requestb.in 的結果:
從 Header 來看,,RestSharp 幫我們做的事情比 HttpClient 多一些。
JavaScript
目前在 JavaScript 環境,看到許多人介紹 JSONC 這一套來做 GZIP 的壓縮與解壓縮。如果你是 SPA 環境,又有大量資料要傳遞,可以試試看。
小結
這樣處理之後,整個 ASP.NET Web API 的資訊流,從進入到輸出都有完整的 GZip 配套措施。
沒有留言:
張貼留言
感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。