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();
}
}
}
}
{
// 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