ASP.NET Web API使用GZIP或Deflate壓縮,提升資料傳遞效能40倍
專案有個查詢資料量不小,基本測試資料每次約1.92 MB,透過 ASP.NET Web API 傳遞至 Client 端每次就是 1.92 MB 的網路傳遞量,而且未來上線後,資料量只會越來越大,直覺感到不對勁(壞味道)。
IIS的動態壓縮與靜態壓縮對 ASP.NET Web API 是無效的。而且,平常 ASP.NET Web API 的請求,資料負載量也不是很大,那種 KB 級的資料壓縮率不高,平常也就沒特別注意。不過在 MB 級的資料,有無啟用 GZIP 或 Deflate 壓縮的資料量差異,直接說結果,以我實測得到的數據差了40倍。
啟用IIS的GZIP或Deflate壓縮 for JSON
這前提是你碰得到 IIS 主機能修改 applicationHost.config
,把 mimeType
加入至 <dynamicTypes>
,這樣 application/json
回應就會被 IIS 接手並使用 GZIP 壓縮處理。
<httpCompression directory="%TEMP%\iisexpress\IIS Temporary Compressed Files">
<scheme name="gzip" dll="%IIS_BIN%\gzip.dll" />
<dynamicTypes>
<!-- 壓縮 JSON 回應 -->
<add mimeType="application/json" enabled="true" />
</dynamicTypes>
</httpCompression>
這是最直接簡易的處理方式。如果碰不到也沒關係,我們有土炮精神。
Compression ActionFilter
ASP.NET Web API 提供完整的擴充點,沒有的東西,那麼就拉起袖子來自己來做一個。
首先思考的問題點是,資訊流進出問題,目前問題在查詢後資料量非常大,也就是查詢取得結果後我們想加工處理,很明顯,我們應該使用 ActionFilter 來處理。
/// <summary>
/// Response content compression
/// </summary>
/// <seealso cref="System.Web.Http.Filters.ActionFilterAttribute" />
public class CompressionAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
base.OnActionExecuted(actionExecutedContext);
}
}
以生命流程來看,取得資料後的加工,應該是在 OnActionExecuted
來處理。
這裡要先回到 HTTP 協定上,在 Web 的世界,如果 Client 與 Server 雙方要進行資料壓縮傳遞,雙方的內容協商中必須包含特定的 Header,Client 端指定
Accept-Encoding
,Server 端指定Content-Encoding
。這必須有 HTTP 協定基礎瞭解才能明白後面的實作。
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var content = actionExecutedContext.Response.Content;
var bytes = content?.ReadAsByteArrayAsync().Result;
if (bytes != null && bytes.Length > 0)
{
var acceptEncoding = actionExecutedContext.Request.Headers.AcceptEncoding.Where(x => x.Value == "gzip" || x.Value == "deflate").ToList();
byte[] zlibbedContent;
if (acceptEncoding.FirstOrDefault()?.Value == "deflate")
{
zlibbedContent = DeflateByte(bytes);
actionExecutedContext.Response.Content = new ByteArrayContent(zlibbedContent);
actionExecutedContext.Response.Content.Headers.Add("Content-Encoding", "deflate");
}
else
{
zlibbedContent = GZipByte(bytes);
actionExecutedContext.Response.Content = new ByteArrayContent(zlibbedContent);
actionExecutedContext.Response.Content.Headers.Add("Content-Encoding", "gzip");
}
}
actionExecutedContext.Response.Content.Headers.Add("Content-Type", "application/json");
base.OnActionExecuted(actionExecutedContext);
}
這裡的實作是不論 Client 是否指定 Accept-Encoding
值都會進行壓縮,預設使用 GZIP 壓縮。如果希望 Client 有指定 Accept-Encoding
才進行壓縮,請自行調整程式碼。
HTTP壓縮常見有 GZIP 與 Deflate 兩種,針對兩種方式提供不同實作方法。
以下使用常見的 DotNetZip 套件來進行實作:
#region DotNetZip
private static byte[] DeflateByte(byte[] data)
{
using (var output = new MemoryStream())
{
using (var compressor = new DeflateStream(
output, CompressionMode.Compress,
CompressionLevel.BestSpeed))
{
compressor.Write(data, 0, data.Length);
}
return output.ToArray();
}
}
private byte[] GZipByte(byte[] data)
{
using (var output = new MemoryStream())
{
using (var compressor = new GZipStream(
output, CompressionMode.Compress,
CompressionLevel.BestSpeed))
{
compressor.Write(data, 0, data.Length);
}
return output.ToArray();
}
}
#endregion
這樣就完成我們第一版的 Compression
Action Filter。
測試取得的數據:
- 原始:1.92 MB
- GZIP:89.7 KB
- Deflate:89.68 KB
透過第一版 Compression
Action Filter 的幫忙,我們整整提升21倍的資料傳遞效率。
- 讀者:雖然內容不錯,但作者你也喜歡用標頭殺人法來吸流量?
- 筆者:非也非也。我是個有誠信的人。讓我們往下看。
第一版 Compression
Action Filter 採用網路上大家常用的 DotNetZip 套件來實作。不過,我愛 .NET Framework,那麼剛好就記得 .NET Framework 也有支援GZipStream 類別與DeflateStream 類別,就順手實作並測試。
/// <summary>
/// GZip the byte.
/// </summary>
/// <param name="data">The data.</param>
/// <returns></returns>
private byte[] GZipByte(byte[] data)
{
using (var output = new MemoryStream())
{
using (var compressor = new GZipStream(output, CompressionMode.Compress))
{
compressor.Write(data, 0, data.Length);
compressor.Close();
}
return output.ToArray();
}
}
/// <summary>
/// Deflate the byte.
/// </summary>
/// <param name="data">The data.</param>
/// <returns></returns>
private byte[] DeflateByte(byte[] data)
{
using (var output = new MemoryStream())
{
using (var compressor = new DeflateStream(output, CompressionMode.Compress))
{
compressor.Write(data, 0, data.Length);
compressor.Close();
}
return output.ToArray();
}
}
就程式碼而言,.NET Framework提供 GZipStream
與 DeflateStream
程式碼更短也更好寫。
有沒有嚇到。
- 原始:1.92 MB
- GZIP:50.15 KB
- Deflate:50.14 KB
第二版 Compression
Action Filter 改採用 .NET Framework 的 GZipStream
類別與 DeflateStream
類別來實作,硬是把 DotNetZip 的 89 KB 比下去。整體的資料傳遞效能整整從21倍提升至38.4倍。
原始:1.92 MB → DotNetZip:89 KB → .NET Framework:50 KB。
不是外來的和尚比較會唸經,讓我們幫 .NET Framework 按個讚,好棒!
- 筆者:這樣的數據,讓我取個整數號稱40倍,不過份吧。
- 讀者:;-)
沒有留言:
張貼留言
感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。