在IIS上添加gzip压缩已经不是什么新鲜事情了,但是如何在自host的wcf上对rest响应支持gzip压缩哪?

    乍一看这个命题还真的有点难,但是wcf框架本身相当强大,拥有众多的介入点,只要正确的介入binding和behavior就可以很简单的达到目的

准备Binding

     首先,因为需要修改输出结果的编码,那么不可避免的需要修改Binding,如果熟悉WCF的Binding模型的话,可以很容易的将传统的wsHttpBinding,webHttpBinding,netTcpBinding等分解,由于目标是rest服务,因此传输层使用http方式,即:HttpTransportBindingElement,而编码层则需要在原来的编码层基础上添加gzip压缩,因此需要嵌套原来的WebMessageEncodingBindingElement,并在原来的基础上添加gzip效果。

     然后就是Binding的拼装了,好吧,我不想把事件搞得太负责,直接用CustomBinding来组装:

ZhenwayWebHttpBinding
using System;
using System.IO;
using System.IO.Compression;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
using System.Xml;

namespace Zhenway.RestWithGZip
{
    public class ZhenwayWebHttpBinding
        : CustomBinding, IBindingRuntimePreferences
    {

        #region Ctors

        public ZhenwayWebHttpBinding()
            : base(GetBindingElements()) { }

        public ZhenwayWebHttpBinding(string configurationName)
            : base(GetBindingElements())
        {
            ApplyConfiguration(configurationName);
        }

        #endregion

        #region Methods

        private static BindingElementCollection GetBindingElements()
        {
            WebHttpBinding webHttpBinding;
            webHttpBinding = new WebHttpBinding();
            var collection = webHttpBinding.CreateBindingElements();
            var encodingBindingElement = collection.Find<MessageEncodingBindingElement>();
            var index = collection.IndexOf(encodingBindingElement);
            collection.RemoveAt(index);
            var wrapperBindingElement = new WrapperEncodingBindingElement(encodingBindingElement);
            collection.Insert(index, wrapperBindingElement);
            return collection;
        }

        private void ApplyConfiguration(string configurationName)
        {
            ZhenwayWebHttpBindingCollectionElement c = ZhenwayWebHttpBindingCollectionElement.GetBindingCollectionElement();
            ZhenwayWebHttpBindingElement element = c.Bindings[configurationName];
            if (element == null)
                throw new InvalidOperationException("Configuration[" + configurationName + "] not found.");
            element.ApplyConfiguration(this);
        }

        #endregion

        #region Properties

        private HttpTransportBindingElement httpTransportBindingElement { get { return Elements.Find<HttpTransportBindingElement>(); } }

        public bool AllowCookies
        {
            get { return httpTransportBindingElement.AllowCookies; }
            set { httpTransportBindingElement.AllowCookies = value; }
        }

        public long MaxBufferPoolSize
        {
            get { return httpTransportBindingElement.MaxBufferPoolSize; }
            set { httpTransportBindingElement.MaxBufferPoolSize = value; }
        }

        public int MaxBufferSize
        {
            get { return httpTransportBindingElement.MaxBufferSize; }
            set { httpTransportBindingElement.MaxBufferSize = value; }
        }

        public long MaxReceivedMessageSize
        {
            get { return httpTransportBindingElement.MaxReceivedMessageSize; }
            set { httpTransportBindingElement.MaxReceivedMessageSize = value; }
        }

        public XmlDictionaryReaderQuotas ReaderQuotas
        {
            get { return ((WrapperEncodingBindingElement)Elements.Find<MessageEncodingBindingElement>()).ReaderQuotas; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("value");
                value.CopyTo(((WrapperEncodingBindingElement)Elements.Find<MessageEncodingBindingElement>()).ReaderQuotas);
            }
        }

        public override string Scheme { get { return "http"; } }

        bool IBindingRuntimePreferences.ReceiveSynchronously
        {
            get { return false; }
        }

        public TransferMode TransferMode
        {
            get { return httpTransportBindingElement.TransferMode; }
            set { httpTransportBindingElement.TransferMode = value; }
        }

        #endregion

        #region Wrapper Classes

        private sealed class WrapperEncodingBindingElement
            : MessageEncodingBindingElement
        {
            private readonly WebMessageEncodingBindingElement m_bindingElement;
            public WrapperEncodingBindingElement(MessageEncodingBindingElement bindingElement)
            {
                this.m_bindingElement = (WebMessageEncodingBindingElement)bindingElement;
            }
            public override MessageEncoderFactory CreateMessageEncoderFactory()
            {
                return new WrapperMessageEncoderFactory(m_bindingElement.CreateMessageEncoderFactory());
            }
            public override MessageVersion MessageVersion
            {
                get { return this.m_bindingElement.MessageVersion; }
                set { this.m_bindingElement.MessageVersion = value; }
            }
            public override BindingElement Clone()
            {
                MessageEncodingBindingElement bindingElement = (MessageEncodingBindingElement)this.m_bindingElement.Clone();
                return new WrapperEncodingBindingElement(bindingElement);
            }
            public XmlDictionaryReaderQuotas ReaderQuotas
            {
                get { return m_bindingElement.ReaderQuotas; }
            }
            public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
            {
                context.BindingParameters.Add(this);
                return context.BuildInnerChannelFactory<TChannel>();
            }
            public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
            {
                context.BindingParameters.Add(this);
                return context.BuildInnerChannelListener<TChannel>();
            }
            public override bool CanBuildChannelFactory<TChannel>(BindingContext context)
            {
                context.BindingParameters.Add(this);
                return context.CanBuildInnerChannelFactory<TChannel>();
            }
            public override bool CanBuildChannelListener<TChannel>(BindingContext context)
            {
                context.BindingParameters.Add(this);
                return context.CanBuildInnerChannelListener<TChannel>();
            }
            public override T GetProperty<T>(BindingContext context)
            {
                if (context == null)
                {
                    throw new ArgumentNullException("context");
                }
                if (typeof(T) == typeof(XmlDictionaryReaderQuotas))
                {
                    return (T)(object)this.ReaderQuotas;
                }
                return base.GetProperty<T>(context);
            }
        }

        private sealed class WrapperMessageEncoderFactory
            : MessageEncoderFactory
        {
            private readonly MessageEncoderFactory m_inner;
            private readonly MessageEncoder m_encoder;
            public WrapperMessageEncoderFactory(MessageEncoderFactory factory)
            {
                this.m_inner = factory;
                this.m_encoder = new WrapperMessageEncoder(factory.Encoder);
            }
            public override MessageEncoder Encoder
            {
                get { return this.m_encoder; }
            }
            public override MessageVersion MessageVersion
            {
                get { return this.m_encoder.MessageVersion; }
            }
        }

        private sealed class WrapperMessageEncoder
            : MessageEncoder
        {
            private readonly MessageEncoder m_inner;

            public WrapperMessageEncoder(MessageEncoder encoder)
            {
                this.m_inner = encoder;
            }

            #region Overrides
            public override string ContentType
            {
                get { return this.m_inner.ContentType; }
            }
            public override string MediaType
            {
                get { return this.m_inner.MediaType; }
            }
            public override MessageVersion MessageVersion
            {
                get { return this.m_inner.MessageVersion; }
            }
            public override bool IsContentTypeSupported(string contentType)
            {
                return this.m_inner.IsContentTypeSupported(contentType);
            }
            public override T GetProperty<T>()
            {
                return this.m_inner.GetProperty<T>();
            }
            public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
            {
                return this.m_inner.ReadMessage(buffer, bufferManager, contentType);
            }
            public override Message ReadMessage(Stream stream, int maxSizeOfHeaders, string contentType)
            {
                return this.m_inner.ReadMessage(stream, maxSizeOfHeaders, contentType);
            }
            public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
            {
                var c = WebOperationContext.Current;
                // 写Buffered消息
                ArraySegment<byte> buffer = this.m_inner.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
                if (c != null && message.Properties.ContainsKey("gzip"))
                    return CompressBuffer(buffer, bufferManager, messageOffset);
                return buffer;
            }
            public override void WriteMessage(Message message, Stream stream)
            {
                // 写Streaming消息
                var c = WebOperationContext.Current;
                if (c != null && message.Properties.ContainsKey("gzip"))
                {
                    using (var gz = new GZipStream(stream, CompressionMode.Compress, true))
                        this.m_inner.WriteMessage(message, gz);
                    stream.Flush();
                    return;
                }
                this.m_inner.WriteMessage(message, stream);
            }
            #endregion

            private static ArraySegment<byte> CompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager, int messageOffset)
            {
                // 压缩
                var ms = new MemoryStream(Math.Max(buffer.Count >> 1512));
                using (var gz = new GZipStream(ms, CompressionMode.Compress, true))
                    gz.Write(buffer.Array, messageOffset, buffer.Count);
                // 重新组织消息体
                byte[] bufferedBytes = bufferManager.TakeBuffer(messageOffset + (int)ms.Length);
                byte[] compressedBytes = ms.GetBuffer();
                Array.Copy(buffer.Array, 0, bufferedBytes, 0, messageOffset);
                Array.Copy(compressedBytes, 0, bufferedBytes, messageOffset, (int)ms.Length);
                bufferManager.ReturnBuffer(buffer.Array);
                return new ArraySegment<byte>(bufferedBytes, messageOffset, (int)ms.Length);
            }

        }

        #endregion

    }
}

 

    binding就准备好啦,不过这个binding只能用代码形式创建,而不能用配置形式创建,显然与wcf强大的配置功能有点不合拍,再加下binding的配置节支持:

ZhenwayWebHttpBindingElement
using System;
using System.Configuration;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.Xml;

namespace Zhenway.RestWithGZip
{
    public class ZhenwayWebHttpBindingElement
        : StandardBindingElement
    {

        #region Fields
        private ConfigurationPropertyCollection m_properties;
        #endregion

        #region Ctors

        public ZhenwayWebHttpBindingElement(string name)
            : base(name) { }

        public ZhenwayWebHttpBindingElement()
            : this(null) { }

        #endregion

        #region ConfigurationProperties

        [ConfigurationProperty("allowCookies", DefaultValue = false)]
        public bool AllowCookies
        {
            get { return (bool)base["allowCookies"]; }
            set { base["allowCookies"] = value; }
        }

        [LongValidator(MinValue = 0L), ConfigurationProperty("maxBufferPoolSize", DefaultValue = 524288L)]
        public long MaxBufferPoolSize
        {
            get { return (long)base["maxBufferPoolSize"]; }
            set { base["maxBufferPoolSize"] = value; }
        }

        [ConfigurationProperty("maxBufferSize", DefaultValue = 65536), IntegerValidator(MinValue = 1)]
        public int MaxBufferSize
        {
            get { return (int)base["maxBufferSize"]; }
            set { base["maxBufferSize"] = value; }
        }

        [ConfigurationProperty("maxReceivedMessageSize", DefaultValue = 65536L), LongValidator(MinValue = 1L)]
        public long MaxReceivedMessageSize
        {
            get { return (long)base["maxReceivedMessageSize"]; }
            set { base["maxReceivedMessageSize"] = value; }
        }

        [ConfigurationProperty("readerQuotas")]
        public XmlDictionaryReaderQuotasElement ReaderQuotas
        {
            get { return (XmlDictionaryReaderQuotasElement)base["readerQuotas"]; }
        }

        [ConfigurationProperty("transferMode", DefaultValue = TransferMode.Buffered)]
        public TransferMode TransferMode
        {
            get { return (TransferMode)base["transferMode"]; }
            set { base["transferMode"] = value; }
        }

        #endregion

        #region Methods

        protected override Type BindingElementType
        {
            get { return typeof(ZhenwayWebHttpBinding); }
        }

        protected override ConfigurationPropertyCollection Properties
        {
            get
            {
                if (this.m_properties == null)
                {
                    ConfigurationPropertyCollection c = base.Properties;
                    c.Add(new ConfigurationProperty("allowCookies"typeof(bool), falsenullnull, ConfigurationPropertyOptions.None));
                    c.Add(new ConfigurationProperty("maxBufferSize"typeof(int), 65536nullnew IntegerValidator(12147483647false), ConfigurationPropertyOptions.None));
                    c.Add(new ConfigurationProperty("maxBufferPoolSize"typeof(long), 524288Lnullnew LongValidator(0L9223372036854775807Lfalse), ConfigurationPropertyOptions.None));
                    c.Add(new ConfigurationProperty("maxReceivedMessageSize"typeof(long), 65536Lnullnew LongValidator(1L9223372036854775807Lfalse), ConfigurationPropertyOptions.None));
                    c.Add(new ConfigurationProperty("readerQuotas"typeof(XmlDictionaryReaderQuotasElement), nullnullnull, ConfigurationPropertyOptions.None));
                    c.Add(new ConfigurationProperty("transferMode"typeof(TransferMode), TransferMode.Buffered, nullnull, ConfigurationPropertyOptions.None));
                    this.m_properties = c;
                }
                return this.m_properties;
            }
        }

        protected override void InitializeFrom(Binding binding)
        {
            base.InitializeFrom(binding);
            ZhenwayWebHttpBinding zBinding = (ZhenwayWebHttpBinding)binding;
            this.MaxBufferSize = zBinding.MaxBufferSize;
            this.MaxBufferPoolSize = zBinding.MaxBufferPoolSize;
            this.MaxReceivedMessageSize = zBinding.MaxReceivedMessageSize;
            this.TransferMode = zBinding.TransferMode;
            this.AllowCookies = zBinding.AllowCookies;
            this.InitializeReaderQuotas(zBinding.ReaderQuotas);
        }

        internal void InitializeReaderQuotas(XmlDictionaryReaderQuotas readerQuotas)
        {
            if (readerQuotas == null)
            {
                throw new ArgumentNullException("readerQuotas");
            }
            this.ReaderQuotas.MaxDepth = readerQuotas.MaxDepth;
            this.ReaderQuotas.MaxStringContentLength = readerQuotas.MaxStringContentLength;
            this.ReaderQuotas.MaxArrayLength = readerQuotas.MaxArrayLength;
            this.ReaderQuotas.MaxBytesPerRead = readerQuotas.MaxBytesPerRead;
            this.ReaderQuotas.MaxNameTableCharCount = readerQuotas.MaxNameTableCharCount;
        }

        protected override void OnApplyConfiguration(Binding binding)
        {
            var zBinding = (ZhenwayWebHttpBinding)binding;
            zBinding.MaxBufferPoolSize = this.MaxBufferPoolSize;
            zBinding.MaxReceivedMessageSize = this.MaxReceivedMessageSize;
            zBinding.TransferMode = this.TransferMode;
            zBinding.AllowCookies = this.AllowCookies;
            PropertyInformationCollection propertyInformationCollection = base.ElementInformation.Properties;
            if (propertyInformationCollection["maxBufferSize"].ValueOrigin != PropertyValueOrigin.Default)
            {
                zBinding.MaxBufferSize = this.MaxBufferSize;
            }
            this.ApplyReaderQuotasConfiguration(zBinding.ReaderQuotas);
        }

        private void ApplyReaderQuotasConfiguration(XmlDictionaryReaderQuotas readerQuotas)
        {
            if (readerQuotas == null)
                throw new ArgumentNullException("readerQuotas");
            if (this.ReaderQuotas.MaxDepth != 0)
            {
                readerQuotas.MaxDepth = this.ReaderQuotas.MaxDepth;
            }
            if (this.ReaderQuotas.MaxStringContentLength != 0)
            {
                readerQuotas.MaxStringContentLength = this.ReaderQuotas.MaxStringContentLength;
            }
            if (this.ReaderQuotas.MaxArrayLength != 0)
            {
                readerQuotas.MaxArrayLength = this.ReaderQuotas.MaxArrayLength;
            }
            if (this.ReaderQuotas.MaxBytesPerRead != 0)
            {
                readerQuotas.MaxBytesPerRead = this.ReaderQuotas.MaxBytesPerRead;
            }
            if (this.ReaderQuotas.MaxNameTableCharCount != 0)
            {
                readerQuotas.MaxNameTableCharCount = this.ReaderQuotas.MaxNameTableCharCount;
            }
        }

        #endregion

    }
}

 

    还有配置节的入口:

ZhenwayWebHttpBindingCollectionElement
using System;
using System.Configuration;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;

namespace Zhenway.RestWithGZip
{
    public class ZhenwayWebHttpBindingCollectionElement
        : StandardBindingCollectionElement<ZhenwayWebHttpBinding, ZhenwayWebHttpBindingElement>
    {

        protected override Binding GetDefault()
        {
            return new ZhenwayWebHttpBinding();
        }

        internal static ZhenwayWebHttpBindingCollectionElement GetBindingCollectionElement()
        {
            BindingsSection bindingsSection = null;
            string text = "system.serviceModel/bindings";
                bindingsSection = (BindingsSection)ConfigurationManager.GetSection(text);
            BindingCollectionElement bindingCollectionElement = bindingsSection["zhenwayWebHttpBinding"];
            return (ZhenwayWebHttpBindingCollectionElement)bindingCollectionElement;
        }

    }
}


    这样binding相关的内容就全部准备好了。

准备Behavior

    binding虽然准备好了,不过,哪些接口的结果需要进行gzip压缩哪?毕竟gzip对于部分内容的压缩效果好,对于某些内容着完全没有效果,甚至还会变大。

    最好能有个标记,标了就用gzip,没标记就不压,这样就能基本顾及常规的使用


GZipAttribute
using System;
using System.IO;
using System.Net;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Web;

namespace Zhenway.RestWithGZip
{
    [AttributeUsage(AttributeTargets.Method)]
    public class GZipAttribute
        : Attribute, IOperationBehavior
    {

        #region IOperationBehavior 成员

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
            dispatchOperation.Formatter = new GZipFormatter(dispatchOperation.Formatter);
        }

        void IOperationBehavior.AddBindingParameters(OperationDescription operationDescription, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { }

        void IOperationBehavior.ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation) { }

        void IOperationBehavior.Validate(OperationDescription operationDescription) { }

        #endregion

        #region Subclass: OperationInvoker

        private sealed class GZipFormatter
            : IDispatchMessageFormatter
        {

            private readonly IDispatchMessageFormatter m_inner;

            public GZipFormatter(IDispatchMessageFormatter inner)
            {
                m_inner = inner;
            }

            #region IDispatchMessageFormatter 成员

            public void DeserializeRequest(Message message, object[] parameters)
            {
                m_inner.DeserializeRequest(message, parameters);
            }

            public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
            {
                var msg = m_inner.SerializeReply(messageVersion, parameters, result);
                var c = WebOperationContext.Current;
                if (c != null)
                {
                    var ae = c.IncomingRequest.Headers[HttpRequestHeader.AcceptEncoding];
                    if (ae != null || ae.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) >= 0 &&
                        "gzip".Equals(c.OutgoingResponse.Headers[HttpResponseHeader.ContentEncoding], StringComparison.OrdinalIgnoreCase) &&
                        result != null)
                    {
                        msg.Properties["gzip"] = "1";
                        c.OutgoingResponse.Headers[HttpResponseHeader.ContentEncoding] = "gzip";
                    }
                }
                return msg;
            }

            #endregion
        }

        #endregion

    }
}

 

    这样就能很方便的使用啦。

来个示例

    契约:

契约
using System;
using System.IO;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace Zhenway.RestWithGZip
{
    [ServiceContract]
    public interface IService1
    {
        [GZip]
        [ContentType("text/plain")]
        [CacheControl(CacheControl.NoCache)]
        [WebGet(UriTemplate = "buffered/{value}/")]
        [OperationContract]
        string TestBufferedMessage(string value);
        [GZip]
        [ContentType("text/html")]
        [CacheControl(10)]
        [WebGet(UriTemplate = "streaming/{value}/")]
        [OperationContract]
        Stream TestStreamingMessage(string value);
    }
}

 

    实现:

实现
using System;
using System.IO;

namespace Zhenway.RestWithGZip
{
    public class Service1 : IService1
    {
        public string TestBufferedMessage(string value)
        {
            return string.Format("You entered: {0}", value);
        }

        public Stream TestStreamingMessage(string value)
        {
            var s = string.Format("<p>You entered: {0}<p>", value);
            var ms = new MemoryStream();
            var sw = new StreamWriter(ms);
            sw.Write("<html><body>");
            for (int i = 0; i < 1000; i++)
            {
                sw.WriteLine(s);
            }
            sw.Write("</body></html>");
            sw.Flush();
            ms.Seek(0, SeekOrigin.Begin);
            return ms;
        }
    }
}

 

    配置:

配置
<?xml version="1.0"?>
<configuration>
  <system.serviceModel>
    <extensions>
      <bindingExtensions>
        <add name="zhenwayWebHttpBinding" type="Zhenway.RestWithGZip.ZhenwayWebHttpBindingCollectionElement, Zhenway.RestWithGZip"/>
      </bindingExtensions>
    </extensions>
    <bindings>
      <zhenwayWebHttpBinding>
        <binding name="AllowCookies" allowCookies="true"/>
      </zhenwayWebHttpBinding>
    </bindings>
    <services>
      <service name="Zhenway.RestWithGZip.Service1">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8888/"/>
          </baseAddresses>
        </host>
        <endpoint address="" binding="zhenwayWebHttpBinding" bindingConfiguration="AllowCookies" contract="Zhenway.RestWithGZip.IService1" behaviorConfiguration="RestBehavior">
          <identity>
            <dns value="localhost"/>
          </identity>
        </endpoint>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior>
          <serviceMetadata httpGetEnabled="True"/>
          <serviceDebug includeExceptionDetailInFaults="False"/>
        </behavior>
      </serviceBehaviors>
      <endpointBehaviors>
        <behavior name="RestBehavior">
          <webHttp/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

 

    实际效果(Buffered方式):

    实际效果(Streaming方式): 


 

源代码下载:

/Files/vwxyzh/201202/RestWithGZip.zip 

posted on 2012-02-01 14:36  Zhenway  阅读(3398)  评论(7编辑  收藏  举报