基于WCF的RESTFul WebAPI如何对传输内容实现压缩

前言

WCF作为通迅框架可以很容易地实现对消息的压缩,且方法不止一种,主要解决方法主要有以下四种:

1、通过自定义MessageEncoder和MessageEncodingBindingElement 来完成。具体的实现,可以参阅张玉彬的文章《WCF进阶:将编码后的字节流压缩传输》;
2、直接创建用于压缩和解压缩的信道,在CodePlex中具有这么一个WCF Extensions
3、自定义MessageFormatter实现序列化后的压缩和反序列化前的解压,详见WCF大师Artech中的博客有《通过WCF扩展实现消息压缩》;
4、自定义MessageInspector实现,详见博客园似若流云的文章《WCF 消息压缩性能问题及解决方法》。

这几种方法实现、配置都很简单。后两种方法的内部实现方法很类似,区别在于第三种方法通过自定义MessageFormatter中对消息进行压缩和解压缩,而第四种方法是在自定义MessageInspector中对消息进行压缩和解压缩。比较而言最后一种是最简单粗暴的。

几种方案的适用场景

那么,这几种方法都适用于什么场景呢?从技术上看,这4种方案基本可以分为两类。一种是在消息编码器上动手脚,另一种是在消息上做文章。

第一种是属于在消息编码器上动手脚的,实现稍复杂。应用场景比较广泛,基本上所有的场景都是适用的。

后面三种都是在消息上做文章的,只适合有WCF客户端的情况,因为如果没有客户端压缩时在消息中加入的压缩标志,服务端就没法正确解压,反之亦然。虽然原理相同,但三种方法的切入点各不相同。同时,第二种方法是可以改成在消息编码器上进行压缩/解压的。

因为RESTFul的WCF的客户端不仅仅是WCF,所以暂时只能选第一种方案了。

一个问题

虽然有现成的方案可用,但是,如果要完美支持多种客户端的话,这里面还有几个问题需要解决。

按照Http协议的规范,客户端发送/服务端返回一个压缩的数据,需要在协议头部加上Content-Encoding,并设置其值为gzip或者deflate。告诉对方数据的压缩方法,好让对方能够正确解压。

如果客户端希望返回的数据是压缩的,那么就在头部加上Accept-Encoding,并设置其值为gzip或者deflate。服务器收到这个信息后,就知道客户端选择的压缩方法,就可以按照客户端指定的方法去压缩数据。

然而,无论哪种方案,都是需要事先配置压缩方式的!这不优雅!!这是个大问题!!!

解决的办法

我们知道,第四种方案的切入点在消息检查器上,在这个点上,通常会实现一些自定义的拦截功能。一个自定义的消息检查器需要继承IDispatchMessageInspector,这个接口类定义了两个接口:

object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
void BeforeSendReply(ref Message reply, object correlationState)

AfterReceiveRequest作用在收到消息后,BeforeSendReply作用在发送响应消息前。
我们可以通过下面的代码,来根据请求头的Accept-Encoding的值,给消息加上对应的压缩标示,以便消息编码器选择正确的压缩方式;并在返回响应前在响应头部加上Content-Encoding并设置相应的值,以便客户端正确解压。

using System.Linq;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Dispatcher;
namespace Insight.WCF.CustomEncoder
{
    public class CompressInspector : IDispatchMessageInspector   
    {
        public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
        {
            var property = request.Properties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty;
            var accept = property?.Headers[HttpRequestHeader.AcceptEncoding];
            switch (accept)
            {
                case "gzip": 
                   OperationContext.Current.Extensions.Add(new GzipExtension());
                    break;
                case "deflate":
                    OperationContext.Current.Extensions.Add(new DeflateExtension());
                    break;
            }
            return null;
        }

        public void BeforeSendReply(ref Message reply, object correlationState)
        {
            var property = reply.Properties[HttpResponseMessageProperty.Name] as HttpResponseMessageProperty;
            var exts = OperationContext.Current.Extensions;
            if (exts.OfType(GzipExtension).Any())
            {
                property?.Headers.Add(HttpResponseHeader.ContentEncoding, "gzip");
            }
            else if (exts.OfType(DeflateExtension).Any())
            {
                property?.Headers.Add(HttpResponseHeader.ContentEncoding, "deflate");
            }
        }
    }
    public class GzipExtension : IExtension
    {
        public void Attach(OperationContext owner)
        {
        }
        public void Detach(OperationContext owner)
        {
        }
    }
    public class DeflateExtension : IExtension
    {
        public void Attach(OperationContext owner)
        {
        }
        public void Detach(OperationContext owner)
        {
        }
    }
}

未彻底解决的问题

因为消息检查器的AfterReceiveRequest作用在收到消息后,也就是说,对于POST/PUT/DELETE这三类请求,它们如果对提交的数据进行了压缩的话,我们无法在消息编码器中根据Content-Encoding的值进行解压。消息编码器中的ReadMessage方法是这样的:

public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)

所以,如果要实现由客户端来决定POST/PUT/DELETE数据的压缩方式的话,只有在Content-Type上面搞点小动作。

这。。。。。。

不够优雅!

看来得研究下改造第二种解决方案了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容