HTTP协议中的压缩
Http协议中使用Accept-Encoding和Content-Encoding头来表示期望Response内容的编码和当前Request的内容编码。而Http内容的压缩其实是内容编码的子集。所以也通过这两个头来描述Http Request和Response内容的压缩方式。
常用的压缩算法有gzip(采用GNU zip压缩)和deflate(采用zlib的格式压缩),对应的Http头中的值也为:gzip或者deflate.
内容压缩与解压
Web服务器可以配置进行全局压缩和解压。当压缩或者解压需要基于某些逻辑进行判断,则要自行实现。
浏览器(或者部分移动端网络库,包括iOS系统网络库)会对Content-Encoding头的值设为gzip或deflate的Response自动解压。而压缩则需要自己实现。
实现
这里采用DotNetZip库进行压缩和解压。
首先创建两个压缩类来分别对gzip和deflate进行处理:
public abstract class Compressor { public abstract byte[] Compress(byte[] originalData); public abstract byte[] Decompress(byte[] compressedData); } public class DeflateCompressor : Compressor { public static string Name => "Deflate"; public override byte[] Compress(byte[] originalData) { using (var outputStream = new MemoryStream()) { using (var gzipStream = new DeflateStream(outputStream, CompressionMode.Compress, CompressionLevel.BestSpeed)) { gzipStream.Write(originalData, 0, originalData.Length); } return outputStream.ToArray(); } } public override byte[] Decompress(byte[] compressedData) { using (var inputStream = new MemoryStream(compressedData)) { var outputStream = new MemoryStream(); var gzipStream = new DeflateStream(inputStream, CompressionMode.Decompress, CompressionLevel.BestSpeed); int blockSize = 10 * 1024; int readSize = 1; while (readSize > 0) { byte[] buffer = new byte[blockSize]; readSize = gzipStream.Read(buffer, 0, blockSize); if (readSize > 0) { outputStream.Write(buffer, 0, readSize); } } outputStream.Flush(); return outputStream.ToArray(); } } }
public class GZipCompressor : Compressor { public static string Name => "GZip"; public override byte[] Compress(byte[] originalData) { using (var outputStream = new MemoryStream()) { using (var gzipStream = new GZipStream(outputStream, CompressionMode.Compress, CompressionLevel.BestSpeed)) { gzipStream.Write(originalData, 0, originalData.Length); } return outputStream.ToArray(); } } public override byte[] Decompress(byte[] compressedData) { using (var inputStream = new MemoryStream(compressedData)) { var outputStream = new MemoryStream(); var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress, CompressionLevel.BestSpeed); int blockSize = 10 * 1024; int readSize = 1; while (readSize > 0) { byte[] buffer = new byte[blockSize]; readSize = gzipStream.Read(buffer, 0, blockSize); if (readSize > 0) { outputStream.Write(buffer, 0, readSize); } } outputStream.Flush(); return outputStream.ToArray(); } } }
创建一个DelegatingHandler: CompressHandler
public class CompressHandler : DelegatingHandler { private Dictionary<string, Compressor> _supportCompressors = new Dictionary<string, Compressor>(); public void RegisterCompressor(string compressorName, Compressor compressor) { if (string.IsNullOrEmpty(compressorName) || compressor == null) { throw new InvalidOperationException("parameter is invalid."); } _supportCompressors[compressorName.ToLower()] = compressor; } public CompressHandler() { } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { //拦截请求,对请求进行解压 await DecompressRequestIfNeed(request); var response = await base.SendAsync(request, cancellationToken); //对响应进行压缩 await CompressResponseIfNeed(request, response); return response; } private async Task DecompressRequestIfNeed(HttpRequestMessage request) { if (request.Content != null) { var originalContentType = request.Content.Headers.ContentType; foreach (var encoding in request.Content.Headers.ContentEncoding) { if (_supportCompressors.ContainsKey(encoding)) { var compressor = _supportCompressors[encoding]; var compressedBytes = await request.Content.ReadAsByteArrayAsync(); if (compressedBytes == null || compressedBytes.Length == 0) { // don't need to decompress, since the content is empty. break; } //基于Content-Encoding进行解压 var decompressedBytes = compressor.Decompress(compressedBytes); var byteContent = new ByteArrayContent(decompressedBytes); request.Content = byteContent; //恢复原始的ContentTypeHeader到新的RequestContent中 request.Content.Headers.ContentType = originalContentType; break; } } } } private async Task CompressResponseIfNeed(HttpRequestMessage request, HttpResponseMessage response) { if (response.Content != null) { foreach (var acceptEncoding in request.Headers.AcceptEncoding) { if (_supportCompressors.ContainsKey(acceptEncoding.Value.ToLower())) { var originalBytes = await response.Content.ReadAsByteArrayAsync(); if (originalBytes == null || originalBytes.Length == 0) { // don't need to compress, since the content is empty. break; } //基于客户端能接受的压缩算法进行压缩 var compressor = _supportCompressors[acceptEncoding.Value]; var compresedBytes = compressor.Compress(originalBytes); //重新设置新的Response Content和Header response.Content = new ByteArrayContent(compresedBytes); response.Content.Headers.ContentEncoding.Add(acceptEncoding.Value); response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); break; } } } } }
将CompressHandler添加到WebApi的MessageHandler列表中来拦截Http请求和响应, 来执行压缩和解压。
public static void Register(HttpConfiguration config) { var compressHandler = new CompressHandler(); //将GZip和Deflate压缩器注册到CompressHandler compressHandler.RegisterCompressor(GZipCompressor.Name, new GZipCompressor()); compressHandler.RegisterCompressor(DeflateCompressor.Name, new DeflateCompressor()); config.MessageHandlers.Add(compressHandler); config.MapHttpAttributeRoutes(); }