首先说一下项目环境,服务采用webapi技术,客户端使用json与服务端通信,客户端目前有android、ios和pc,大部分接口通信量都不大,只有少数几个列表接口通信量较大,所以只针对少数几个接口使用数据压缩技术,上网查了一下,可以通过配置iis的方式来实现,但那是全局性的,对于我来说,会造成不必要的资源浪费。最后就有了以下方案。
该方案主要分两部分:
- 第一部分是服务器压缩数据后传送给客户端,客户端接收到数据后进行解压,这也是最常用的。
- webapi部分代码:主要是一个attribute,使用很简单,只要把attribute放到action或者controller上就行了。压缩使用了dotnetzip,当然你也可以使用.net自带的压缩方法。
/// <summary>
/// 数据压缩处理,使用请求中指定压缩方法进行压缩
/// </summary>
public class CompressAttribute : ActionFilterAttribute
{
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
string acceptEncoding = actionExecutedContext.Request.Headers.AcceptEncoding.ToString();
if (!string.IsNullOrWhiteSpace(acceptEncoding))
{
if (acceptEncoding.Contains("gzip"))
{
var content = actionExecutedContext.Response.Content;
var bytes = content == null ? null : content.ReadAsByteArrayAsync().Result;
var zlibbedContent = bytes == null ? new byte[0] : CompressionHelper.GZipByte(bytes);
actionExecutedContext.Response.Content = new ByteArrayContent(zlibbedContent);
actionExecutedContext.Response.Content.Headers.Remove("Content-Type");
actionExecutedContext.Response.Content.Headers.Add("Content-encoding", "gzip");
actionExecutedContext.Response.Content.Headers.Add("Content-Type", "application/json;charset=utf-8");
}
else if (acceptEncoding.Contains("deflate"))
{
var content = actionExecutedContext.Response.Content;
var bytes = content == null ? null : content.ReadAsByteArrayAsync().Result;
var zlibbedContent = bytes == null ? new byte[0] : CompressionHelper.DeflateByte(bytes);
actionExecutedContext.Response.Content = new ByteArrayContent(zlibbedContent);
actionExecutedContext.Response.Content.Headers.Remove("Content-Type");
actionExecutedContext.Response.Content.Headers.Add("Content-encoding", "deflate");
actionExecutedContext.Response.Content.Headers.Add("Content-Type", "application/json;charset=utf-8");
}
else
base.OnActionExecuted(actionExecutedContext);
}
else
base.OnActionExecuted(actionExecutedContext);
}
}
/// <summary>
/// 数据压缩与解压
/// </summary>
public class CompressionHelper
{
/// <summary>
/// deflate方式压缩
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static byte[] DeflateByte(byte[] str)
{
if (str == null)
{
return null;
}
using (var output = new System.IO.MemoryStream())
{
using (
var compressor = new Ionic.Zlib.DeflateStream(
output, Ionic.Zlib.CompressionMode.Compress,
Ionic.Zlib.CompressionLevel.BestSpeed))
{
compressor.Write(str, 0, str.Length);
}
return output.ToArray();
}
}
/// <summary>
/// GZip方式压缩
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
public static byte[] GZipByte(byte[] str)
{
if (str == null)
{
return null;
}
using (var output = new System.IO.MemoryStream())
{
using (
var compressor = new Ionic.Zlib.GZipStream(
output, Ionic.Zlib.CompressionMode.Compress, Ionic.Zlib.CompressionLevel.BestSpeed))
{
compressor.Write(str, 0, str.Length);
}
return output.ToArray();
}
}
- 客户端代码,说明一下,有两种方式,可以解压,一是自动解压,var client = new HttpClient(handler),二是手动解压,下面代码是手动。
public static void TestGZip()
{
string Content;
try
{
var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate };
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:40802/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
// New code:
string Url ="自定义代码" ;
var response = client.GetAsync(Url).Result;
if (response.IsSuccessStatusCode)
{
MemoryStream decompressedStream = new MemoryStream();
IEnumerable<string> c = new List<string>();
var a = response.Headers.TryGetValues("Content-Length", out c);
var b = response.Content.ReadAsStringAsync().Result;
using (var gzipStream = new GZipStream(response.Content.ReadAsStreamAsync().Result,
CompressionMode.Decompress))
{
gzipStream.CopyTo(decompressedStream);
}
var l = System.Text.Encoding.UTF8.GetString(decompressedStream.ToArray());
Console.WriteLine(l);
}
else
{
}
}
}
catch (Exception ex)
{
Content = ex.Message;
}
}
- 第二部分是客户端将数据压缩后发送给服务器,服务器收到消息后进行解压。
- webapi部分代码:在网上查了很多资料,大都是先写一个httphandler来处理解压,然后在webapiconfig中注册 config.MessageHandlers.Add(new DecompressionHandler()),但我不喜欢这种法,因为只有少数接口需要压缩、解压,将httphandler注册为全局后,必然会造成资源浪费。自己尝试过使用attribte来做,后来发现行不通,因为在actionfilterattribute中取到的数据是经过.net处理的。所以后来想了一个折衷的方案,就是使用路由,将需要压缩解压的action注册为单独路由,在这个路由中注册httphandler。代码如下:
public class DecompressionHandler : DelegatingHandler
{
public DecompressionHandler(HttpConfiguration config)
{//不加这句可能会报InnerHandler has not assigned
InnerHandler = new System.Web.Http.Dispatcher.HttpControllerDispatcher(config);
}
protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
bool isGzip = request.Content.Headers.ContentEncoding.Contains("gzip");
bool isDeflate = !isGzip && request.Content.Headers.ContentEncoding.Contains("deflate");
if (isGzip || isDeflate)
{
Stream stream = request.Content.ReadAsStreamAsync().Result;
stream.Position = 0;
if (isGzip)
{
request.Content = new StreamContent(new GZipStream(stream, CompressionMode.Decompress));
}
else if (isDeflate)
{
request.Content = new StreamContent(new DeflateStream(stream, CompressionMode.Decompress));
}
request.Content.Headers.Remove("Content-Type");
request.Content.Headers.Add("Content-encoding", "gzip");
request.Content.Headers.Add("Content-Type", "application/json;charset=UTF-8");
}
return base.SendAsync(request, cancellationToken);
}
}
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "{{controller}/{action}/{id}",
defaults: new { action="{action}", id = RouteParameter.Optional }
);
config.Routes.MapHttpRoute(
name: "ZipedApi",//上传数据经过gzip或者deflate压缩的接口
routeTemplate: "{controller}/{action}/ziped/{id}",
defaults: new { action = "{action}", id = RouteParameter.Optional },
constraints: null,
handler: new DecompressionHandler(config)//重点在这里
);
}}
- 客户端代码。
public static void TestPostGZip()
{
try
{
using (var client = new HttpClient())
{
client.BaseAddress = new Uri("http://localhost:40802/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip"));
// New code:
string Url = "自定义url";
string json = JsonConvert.SerializeObject(new { nickname = "k l 中国", headimgurl = "http://ewewew.efwefwwe", openid="333" });
var content = new CompressedContent(
new StringContent(json, Encoding.UTF8, "application/json"),
CompressionMethod.GZip);
var response = client.PostAsync(Url, content).Result;
if (response.IsSuccessStatusCode)
{
Console.WriteLine(response.Content.ReadAsStringAsync().Result);
}
else
{
}
}
}
catch (Exception ex)
{
}
}
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);
}
}
}
}
参考资料:
http://www.asp.net/web-api/overview/advanced/http-message-handlers