zoukankan      html  css  js  c++  java
  • wcf rest服务启用gzip压缩

        在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 

  • 相关阅读:
    120.三角形最短路径(leetcode)
    Python Pycharm中灵活运用debugger
    POJ 1284
    POJ 2407
    POJ 1811
    HDU 1164
    HDU 4228
    HDU 2521
    HDU 4133
    ZOJ 2562 反素数
  • 原文地址:https://www.cnblogs.com/vwxyzh/p/2334526.html
Copyright © 2011-2022 走看看