首先说明一下,这里的压缩与解压不是通常所说的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 = ,
Deflate =
} 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 = -;
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(, 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

最新文章

  1. 【干货】用大白话聊聊JavaSE — ArrayList 深入剖析和Java基础知识详解(二)
  2. 如何安装Genymotion虚拟机以及Genmotion的eclipse插件
  3. [史上最全]C#(VB.NET)中位运算符工作过程剖析(译)
  4. protobuf-net
  5. SDOI2016 round1滚粗记
  6. 【面试题】Google of Greater China Test for New Grads of 2014总结
  7. ActionBar官方教程(2)选主题让应用支或不支持ActionBar及支持ActionBar的应用如何隐藏和显示
  8. WCF X.b 操作引用了已经从 Y.b 操作导出的消息元素 [http://tempuri.org/:b]。可以通过更改方法名称或使用 OperationContractAttribute 的 Name 属性更改其中一个操作的名称...
  9. 45 Useful JavaScript Tips, Tricks and Best Practices(有用的JavaScript技巧,技巧和最佳实践)
  10. EditText无法失去焦点、失去焦点隐藏软键盘
  11. (转)MATLAB入门教程
  12. CentOS6.9下安装rabbitmq消息队列
  13. 听翁恺老师mooc笔记(3)--指针的定义
  14. 计蒜客NOIP2017提高组模拟赛(三)day1
  15. 使用DWR实现自动补全 类似百度搜索框的自动显示效果
  16. m3u8下载转码一次完成
  17. 如何将Tomcat添加到服务中【笔记】
  18. Notepadd ++ PluginManager安装
  19. ASP.NET MVC的ContentResult
  20. LOG4J 的配置

热门文章

  1. Jmeter外部函数引用
  2. 数据库数据怎样导出成Excle表格或Word文档?
  3. asp.net GridView控件中诗选全选和全不选功能
  4. 使用C# WinForm窗体制作经理评分项目 ——S2 2.2
  5. 奇怪的margin,padding,table
  6. 单片机TM4C123学习(九):PWM
  7. LeetCode.4 两个有序数组的中位数问题
  8. LeetCode(三)
  9. XE3随笔3:访问
  10. js控制控件不可编辑