首先说一下项目环境,服务采用webapi技术,客户端使用json与服务端通信,客户端目前有android、ios和pc,大部分接口通信量都不大,只有少数几个列表接口通信量较大,所以只针对少数几个接口使用数据压缩技术,上网查了一下,可以通过配置iis的方式来实现,但那是全局性的,对于我来说,会造成不必要的资源浪费。最后就有了以下方案。

该方案主要分两部分:

  • 第一部分是服务器压缩数据后传送给客户端,客户端接收到数据后进行解压,这也是最常用的。
  1. 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();
        }
    }
  1. 客户端代码,说明一下,有两种方式,可以解压,一是自动解压,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;
            }
        }
  • 第二部分是客户端将数据压缩后发送给服务器,服务器收到消息后进行解压。
  1. 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)//重点在这里
            ); 
}}
  1. 客户端代码。
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