微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在传输层上压缩WebService的请求和响应

http://www.blogjava.net/mstar/archive/2013/06/23/400884.html

场景 


场景是这样的:客户端.NET 3.5应用程序,WCF实现WebService调用, 服务端Java,通过CXF提供WebService。 有一个方法提供了有一个字符串类型的参数,实际生产环境里会传100k以上的字符串。在并发量比较大的情况下,带宽占用很严重。所以寻找一种可以把传输的 SOAP消息在客户端压缩,服务端解压缩的方法。 

这里提供的方式在是客户端通过WCF的MessageEncoder机制对所有的SOAP请求消息压缩,SOAP响应消息解压缩,反过来在服务端通过一个Filter对所有的SOAP请求消息,对SOAP响应消息压缩。 
请求的流程如下:
Client -> SOAP Request -> GzipMessageEncoder -> gzip binary -> GzipwebSericeFilter -> SOAP Request -> CXF 
响应的流程如下:
CXF -> SOAP Response -> GzipwebServiceFilter -> gzip binary -> GzipMessageEncoder -> SOAP Response -> Client 
其中.NET的WCF的GzipMessageEncoder是参照 WCF的Samples , 下载解压后路径WF_WCF_Samples\WCF\Extensibility\MessageEncoder\Compression
客户端
下面先来看一下客户端部分的代码: 
GZipMessageEncoderFactory.cs 这文件主要是提供GZipMessageEncoder,在里面通过重写ReadMessage和WriteMessage方法来实现压缩和解压缩。 实际压缩和解压处理是使用GZipStream实现的。 
namespace  ConsoleApplication2
{
    
// This class is used to create the custom encoder (GZipMessageEncoder)
     internal   class  GZipMessageEncoderFactory : MessageEncoderFactory
    {
        
readonly  MessageEncoder _encoder;

        
The GZip encoder wraps an inner encoder
        
We require a factory to be passed in that will create this inner encoder          public  GZipMessageEncoderFactory(MessageEncoderFactory messageEncoderFactory)
        {
            
if  (messageEncoderFactory  == null )
                
throw new  ArgumentNullException( " messageEncoderFactory A valid message encoder factory must be passed to the GZipEncoder );
            _encoder 
=  GZipMessageEncoder(messageEncoderFactory.Encoder);

        }
        
        
The service framework uses this property to obtain an encoder from this encoder factory override  MessageEncoder Encoder
        {
            
get  {  return  _encoder; }
        }

        
 MessageVersion MessageVersion
        {
            
 _encoder.MessageVersion; }
        }

        
This is the actual GZip encoder  GZipMessageEncoder : MessageEncoder
        {
            
private const string  GZipMediaType  application/x-gzip ;
            
 GZipContentType  + ; charset=utf-8 ;

            
This implementation wraps an inner encoder that actually converts a WCF Message
            
into textual XML, binary XML or some other format. This implementation then compresses the results.
            
The opposite happens when reading messages.
            
This member stores this inner encoder.               MessageEncoder _innerEncoder;

            
We require an inner encoder to be supplied (see comment above)  GZipMessageEncoder(MessageEncoder messageEncoder)
            {
                
 (messageEncoder  )
                    
messageEncoder A valid message encoder must be passed to the GZipEncoder );
                _innerEncoder 
 messageEncoder;
            }

            
 ContentType
            {
                
 GZipContentType; }
            }

            
 MediaType
            {
                
 GZipMediaType; }
            }

            
SOAP version to use - we delegate to the inner encoder for this  MessageVersion MessageVersion
            {
                
 _innerEncoder.MessageVersion; }
            }

            
bool  IsContentTypeSupported(  contentType)
            {
                
 contentType.StartsWith(GZipMediaType, StringComparison.OrdinalIgnoreCase)  ||  contentType.StartsWith( text/xml noreCase);
            }

            
Helper method to compress an array of bytes static  ArraySegment < byte >  CompressBuffer(ArraySegment  buffer, BufferManager bufferManager,255)">int  messageOffset)
            {
                var memoryStream 
 MemoryStream();
                memoryStream.Write(buffer.Array,0)">0
using  (var gzStream   GZipStream(memoryStream, CompressionMode.Compress,255)">true ))
                {
                    gzStream.Write(buffer.Array, messageOffset, buffer.Count);
                }


                var compressedBytes 
 memoryStream.ToArray();
                var bufferedBytes 
 bufferManager.TakeBuffer(compressedBytes.Length);

                Array.copy(compressedBytes, bufferedBytes, compressedBytes.Length);

                bufferManager.ReturnBuffer(buffer.Array);
                var byteArray 
(bufferedBytes, bufferedBytes.Length  -  messageOffset);

                
 byteArray;
            }

            
Helper method to decompress an array of bytes  DecompressBuffer(ArraySegment  MemoryStream(buffer.Array, buffer.Offset, buffer.Count   buffer.Offset);
                var decompressedStream 
 MemoryStream();
                
 blockSize  1024 ;
                
[] tempBuffer   bufferManager.TakeBuffer(blockSize);
                
while  ( )
                    {
                        var bytesRead 
 gzStream.Read(tempBuffer, blockSize);
                        
 (bytesRead  )
                            
break ;
                        decompressedStream.Write(tempBuffer, bytesRead);
                    }
                }
                bufferManager.ReturnBuffer(tempBuffer);

                var decompressedBytes 
 decompressedStream.ToArray();
                var bufferManagerBuffer 
 bufferManager.TakeBuffer(decompressedBytes.Length   buffer.Offset);
                Array.copy(buffer.Array, bufferManagerBuffer, buffer.Offset);
                Array.copy(decompressedBytes, decompressedBytes.Length);

                var byteArray 
(bufferManagerBuffer, decompressedBytes.Length);
                bufferManager.ReturnBuffer(buffer.Array);

                
One of the two main entry points into the encoder. Called by WCF to encode a Message into a buffered byte array.  WriteMessage(Message message,0)"> maxMessageSize,0)"> messageOffset)
            {
                
Use the inner encoder to encode a Message into a buffered byte array                 ArraySegment  buffer   _innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
                
Compress the resulting byte array                   CompressBuffer(buffer, messageOffset);
            }

            
 Message ReadMessage(Stream stream,0)"> maxSizeOfheaders,0)"> contentType)
            {
                var gzStream 
 GZipStream(stream, CompressionMode.Decompress,0)">);
                
 _innerEncoder.ReadMessage(gzStream, maxSizeOfheaders);
            }

            
 Message ReadMessage(ArraySegment Decompress the buffer  decompressedBuffer   DecompressBuffer(buffer, bufferManager);
                
Use the inner encoder to decode the decompressed buffer                 Message returnMessage   _innerEncoder.ReadMessage(decompressedBuffer, bufferManager);
                returnMessage.Properties.Encoder 
this  returnMessage;
            }

            
void ))
                {
                    _innerEncoder.WriteMessage(message, gzStream);
                }

                
 innerEncoder.WriteMessage(message, gzStream) depends on that it can flush data by flushing 
                
 the stream passed in, but the implementation of GZipStream.Flush will not flush underlying
                
 stream, so we need to flush here.                 stream.Flush();
            }
        }
    }
}

下面是GZipMessageEncodingBindingElement.cs 这里的GZipMessageEncodingBindingElement类是为了在app.config添加配置项。 
This is the binding element that, when plugged into a custom binding, will enable the GZip encoder sealed  GZipMessageEncodingBindingElement 
                        : MessageEncodingBindingElement 
BindingElement     {

        
We will use an inner binding element to store @R_669_4045@ion required for the inner encoder         MessageEncodingBindingElement _innerBindingElement;

        
By default, use the default text encoder as the inner encoder  GZipMessageEncodingBindingElement()
            : 
(  TextMessageEncodingBindingElement()) { }

        
 GZipMessageEncodingBindingElement(MessageEncodingBindingElement messageEncoderBindingElement)
        {
            _innerBindingElement 
 messageEncoderBindingElement;
        }

        
 MessageEncodingBindingElement InnerMessageEncodingBindingElement
        {
            
 _innerBindingElement; }
            
set  { _innerBindingElement   value; }
        }

        
Main entry point into the encoder binding element. Called by WCF to get the factory that will create the
        
message encoder  MessageEncoderFactory CreateMessageEncoderFactory()
        {
            
 GZipMessageEncoderFactory(_innerBindingElement.CreateMessageEncoderFactory());
        }
       
        
 _innerBindingElement.MessageVersion; }
            
 { _innerBindingElement.MessageVersion   BindingElement Clone()
        {
            
 GZipMessageEncodingBindingElement(_innerBindingElement);
        }

        
 T GetProperty T (BindingContext context)
        {
            
typeof (T)  (XmlDictionaryReaderQuotas))
            {
                
 _innerBindingElement.GetProperty (context);
            }
            
base .GetProperty (context);
        }

        
 IChannelFactory TChannel  BuildChannelFactory  (context  context );

            context.BindingParameters.Add(
);
            
 context.BuildInnerChannelFactory ();
        }

        
 IChannelListener  BuildChannelListener  context.BuildInnerChannelListener  CanBuildChannelListener  context.CanBuildInnerChannelListener ();
        }
    }

    
This class is necessary to be able to plug in the GZip encoder binding element through
    
a configuration file  GZipMessageEncodingElement : BindingElementExtensionElement
    {
        
Called by the WCF to discover the type of binding element this config section enables  Type BindingElementType
        {
            
(GZipMessageEncodingBindingElement); }
        }

        
The only property we need to configure for our binding element is the type of
        
inner encoder to use. Here, we support text and binary.         [ConfigurationProperty( innerMessageEncoding textMessageEncoding )]
        
 InnerMessageEncoding
        {
            
) [ ]; }
            
messageVersion Soap12  MessageVersion
        {
            
Called by the WCF to apply the configuration settings (the property above) to the binding element  ApplyConfiguration(BindingElement bindingElement)
        {
            var binding 
 (GZipMessageEncodingBindingElement)bindingElement;
            Property@R_669_4045@ionCollection propertyInfo 
 Element@R_669_404[email protected];
            var property@R_669_4045@ion 
 propertyInfo[ ];
            
 (property@R_669_4045@ion   property@R_669_404[email protected]   PropertyValueOrigin.Default)  ;

            var version 
 System.ServiceModel.Channels.MessageVersion.soap12;
            
Soap11  MessageVersion)
            {
                version 
 System.ServiceModel.Channels.MessageVersion.soap11;
            }

            
switch  (InnerMessageEncoding)
            {
                
case :
                    binding.InnerMessageEncodingBindingElement 
 TextMessageEncodingBindingElement() { MessageVersion   version };
                    
binaryMessageEncoding  BinaryMessageEncodingBindingElement();
                    
;
            }
        }

        
Called by the WCF to create the binding element protected  BindingElement CreateBindingElement()
        {
            var bindingElement 
 GZipMessageEncodingBindingElement();
            ApplyConfiguration(bindingElement);
            
 bindingElement;
        }
    }
}


然后我们就可以把这个GZipMessageEncodingElement配置到app.config里了 
<? xml version="1.0" encoding="utf-8"  ?> < configuration >
  
system .serviceModel
    
extensions
      
bindingElementExtensions
        
add  name ="gzipMessageEncoding"  type ="ConsoleApplication2.GZipMessageEncodingElement,ConsoleApplication2" /> </ bindings customBinding binding  ="countServiceSoapBinding"
          
gzipMessageEncoding  ="textMessageEncoding"  messageVersion ="Soap11" httpTransport  manualAddressing ="false"
                         authenticationScheme
="Anonymous"
                         bypassproxyOnLocal

                         hostNameComparisonMode
="StrongWildcard"
                         proxyAuthenticationScheme

                         realm
=""
                         useDefaultWebProxy
="true" binding client endpoint  address ="http://192.168.2.3:8080/binder/services/countService"
          binding
="customBinding"  bindingConfiguration ="countServiceSoapBinding"
          contract
="ServiceReference1.HolidayService"  name ="HolidayServiceImplPort" system.serviceModel


客户端最后的部分就是调用webservice, 这里的压缩和解压对于调用者和陪调用者是透明的。也就是同没有压缩和解压之前的使用方法一样。 
 Program
    {
          Main( [] args)
        {
            
try
            {
                var service 
 ServiceReference1.HolidayServiceClient();
                var text 
File.ReadAllText( c:\\words );

                var len 
 service.countText(text);
                Console.WriteLine(
lenght = {0} catch  (Exception e)
            {
                Console.WriteLine(e.Message);
                Console.WriteLine(e.StackTrace);
            }
            Console.Read();
        }
    }
}

服务端
服务端是一个Filter,和HttpServletRequest和HttpServletResponse的包装类。 
入口:GzipwebServiceFilter.java 
/**
 * 把使用Gzip压缩的SOAP消息解压缩。
 * 
@author  matianyi
 *
 
*/  GzipwebServiceFilter  implements  Filter {

    
final  String CONTENT_TYPE  ;
    
 String CONTENT_ENCODING  utf-8 ;

    @Override
    
 init(FilterConfig filterConfig throws  servletexception {
        
 Todo Auto-generated method stub
    }

    @SuppressWarnings(
unchecked )
    @Override
    
 doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) 
 IOException, servletexception {
        
        HttpServletRequest req 
 (HttpServletRequest) request;
        HttpServletResponse resp 
 (HttpServletResponse) response;
        
        
(req.getContentType()  ! req.getContentType().startsWith(CONTENT_TYPE)){
            chain.doFilter(request, response);
        } 
else  {
            chain.doFilter(
 GzipHttpServletRequestWrapper(req),0)"> GzipHttpServletResponseWrapper(resp));
        }
    }

    @Override
    
 destroy() {
        

    }

}

这里就是判断contentType,如果是gzip的就用GzipHttpServletRequestWrapper和GzipHttpServletResponseWrapper包装原始的Request和Response以实现压缩和解压缩。 
GzipHttpServletRequestWrapper 
 GzipHttpServletRequestWrapper  extends  HttpServletRequestWrapper {

    
 String CONTNET_TYPE_SOAP_1_2  application/soap+xml  String CONTNET_TYPE_SOAP_1_1  ;

    
 GzipHttpServletRequestWrapper(HttpServletRequest request) {
        
super (request);
    }

    @Override
    
 ServletInputStream getInputStream()   IOException {
        
 GzipServletInputStream( .getInputStream());
    }

    @Override
    
 String getContentType() {
        
 CONTNET_TYPE_SOAP_1_2;
    }

    @Override
    
 String getHeader(String name) {
        
content-type .equalsIgnoreCase(name)) {
            
 getContentType();
        } 
 {
            
.getHeader(name);
        }
    }

}

 GzipServletInputStream   ServletInputStream {

    
 GZIPInputStream delegate;

    
 GzipServletInputStream(ServletInputStream servletInputStream)
            
();
        
.delegate   GZIPInputStream(servletInputStream);
    }

    @Override
    
 read()   delegate.read();
    }

}

GzipHttpServletResponseWrapper 
 GzipHttpServletResponseWrapper   HttpServletResponseWrapper {

    
 GzipHttpServletResponseWrapper(HttpServletResponse response) {
        
(response);
    }

    @Override
    
 ServletoutputStream getoutputStream()   GzipServletoutputStream( .getoutputStream());
    }

    @Override
    
 setCharacterEncoding(String charset) {
        
.setCharacterEncoding(GzipwebServiceFilter.CONTENT_ENCODING);
    }

    @Override
    
 setContentType(String type) {
        
.setContentType(GzipwebServiceFilter.CONTENT_TYPE  ; charset=  GzipwebServiceFilter.CONTENT_ENCODING);
    }
    
}

 GzipServletoutputStream   ServletoutputStream{
    
 GZIPOutputStream delegate;

    
 GzipServletoutputStream(ServletoutputStream servletoutputStream)
            
 GZIPOutputStream(servletoutputStream);
    }
    
    
    @Override
    
 write(  b)   IOException {
        System.out.print((
char )b);
        delegate.write(b);
    }


    
 close()   IOException {
        delegate.close();
    }


    
 flush()   IOException {
        delegate.flush();
    }


    
[] buf,0)"> off,0)"> len)   IOException {
        delegate.write(buf, off, len);
    }


    
[] b)   IOException {
        delegate.write(b);
    }
    
    
}
这里做的主要事情就是在Resquest的getInputStream和Response的getoutputStream是返回一个拥有GZip功能的Stream,来代替原始的Stream。通过原始的Stream仍然是最终的输入和输出源。 
然后在web.xml中把这个Filter作用于原来的WebService的Servlet 
web.xml 
xml version="1.0" encoding="UTF-8" web-app  version ="2.5"  xmlns ="http://java.sun.com/xml/ns/javaee"
    xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation
="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

    
<!--  The deFinition of the Root Spring Container shared by all Servlets and Filters  --> context-param param-name contextConfigLocation param-value /WEB-INF/spring/root-context.xml
    
    
 Creates the Spring Container shared by all Servlets and Filters  listener listener-class org.springframework.web.context.ContextLoaderListener filter filter-name GzipwebServiceFilter filter-class com.cccis.ws.GzipwebServiceFilter filter-mapping url-pattern /services/* servlet description Apache CXF Endpoint servlet-name cxf servlet-class org.apache.cxf.transport.servlet.CXFServlet load-on-startup 1 servlet-mapping

web-app >

webservice的配置和cxf原来的一样 
beans  xmlns ="http://www.springframework.org/schema/beans"
    xmlns:context
="http://www.springframework.org/schema/context"
    xmlns:jaxws
="http://cxf.apache.org/jaxws" ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
       http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd"
import  resource ="classpath:meta-inf/cxf/cxf.xml" ="classpath:meta-inf/cxf/cxf-servlet.xml" bean  id ="countServiceImpl"  class ="com.cccis.ws.HolidayServiceImpl" jaxws:endpoint 
      
="countService"  
      implementor
="#countServiceImpl"  
      serviceName

      address
="/countService"
      
beans >
如果你想看一下实际的HTTP请求和响应是什么样子的可以用fiddler Web Debugger来查看 


本文的源代码在附件中。 
本文的方案没有在最终的被用于生产环境,一个原因是比较复杂,另外一个是服务器在对大XML进行unmarshal的效率并不高。单本文的方案的好处就是不用对原有的webservice接口和实现进行修改。 最后在实际场景用我们使用 MTOM 解决问题的, 后面我还会写一篇文章来介绍这个方法。 
@H_94_1502@

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐