Reputation: 5833
I'm trying to utilize GZip compression for the self hosted WCF service.
The SOAP 1.1 is required.
I implemented custom MessageEncoder from the next sample http://msdn.microsoft.com/en-us/library/cc138373.aspx.
The message encoder was incorporated to the new custom binding:
readonly HttpTransportBindingElement httpTransport;
readonly TextMessageEncodingBindingElement textMessageEncoding;
readonly CompressionMessageEncodingBindingElement compressionMessageEncoding;
public InsecureGZipBasicHttpBinding()
{
httpTransport = new AutoSecuredHttpTransportElement();
textMessageEncoding = new TextMessageEncodingBindingElement { MessageVersion = **MessageVersion.Soap11** };
compressionMessageEncoding = new CompressionMessageEncodingBindingElement(textMessageEncoding);
}
public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingParameterCollection parameters)
{
return base.BuildChannelFactory<TChannel>(parameters);
}
public override BindingElementCollection CreateBindingElements()
{
var elements = new BindingElementCollection {
compressionMessageEncoding,
httpTransport
};
return elements;
}
The issue in the next code:
public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
{
var buffer = innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, messageOffset);
var compressedBuffer = CompressBuffer(buffer, bufferManager, messageOffset);
return compressedBuffer;
}
Each time when it reached, the message has SOAP 1.2 version and can't be processed by the innerEncoder.
How to set the default message version to the SOAP 1.1?
Upvotes: 0
Views: 4252
Reputation: 87218
The message version of the binding is defined basically by the message encoder, so if the encoder implementation itself is returning it correctly, you shouldn't see any messages with an incorrect version arriving at the encoder. I got the code for the GZip encoder verbatim from the samples in the code shown below, and it works out just fine. Take a look at the code below, compare with yours and it should give you some hint on where it can be fixed.
public class StackOverflow_6986346
{
#region Code from the sample, verbatim
// This is constants for GZip message encoding policy.
static class GZipMessageEncodingPolicyConstants
{
public const string GZipEncodingName = "GZipEncoding";
public const string GZipEncodingNamespace = "http://schemas.microsoft.com/ws/06/2004/mspolicy/netgzip1";
public const string GZipEncodingPrefix = "gzip";
}
//This is the binding element that, when plugged into a custom binding, will enable the GZip encoder
public sealed class GZipMessageEncodingBindingElement
: MessageEncodingBindingElement //BindingElement
, IPolicyExportExtension
{
//We will use an inner binding element to store information required for the inner encoder
MessageEncodingBindingElement innerBindingElement;
//By default, use the default text encoder as the inner encoder
public GZipMessageEncodingBindingElement()
: this(new TextMessageEncodingBindingElement()) { }
public GZipMessageEncodingBindingElement(MessageEncodingBindingElement messageEncoderBindingElement)
{
this.innerBindingElement = messageEncoderBindingElement;
}
public MessageEncodingBindingElement InnerMessageEncodingBindingElement
{
get { return 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
public override MessageEncoderFactory CreateMessageEncoderFactory()
{
return new GZipMessageEncoderFactory(innerBindingElement.CreateMessageEncoderFactory());
}
public override MessageVersion MessageVersion
{
get { return innerBindingElement.MessageVersion; }
set { innerBindingElement.MessageVersion = value; }
}
public override BindingElement Clone()
{
return new GZipMessageEncodingBindingElement(this.innerBindingElement);
}
public override T GetProperty<T>(BindingContext context)
{
if (typeof(T) == typeof(XmlDictionaryReaderQuotas))
{
return innerBindingElement.GetProperty<T>(context);
}
else
{
return base.GetProperty<T>(context);
}
}
public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
{
if (context == null)
throw new ArgumentNullException("context");
context.BindingParameters.Add(this);
return context.BuildInnerChannelFactory<TChannel>();
}
public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
{
if (context == null)
throw new ArgumentNullException("context");
context.BindingParameters.Add(this);
return context.BuildInnerChannelListener<TChannel>();
}
public override bool CanBuildChannelListener<TChannel>(BindingContext context)
{
if (context == null)
throw new ArgumentNullException("context");
context.BindingParameters.Add(this);
return context.CanBuildInnerChannelListener<TChannel>();
}
void IPolicyExportExtension.ExportPolicy(MetadataExporter exporter, PolicyConversionContext policyContext)
{
if (policyContext == null)
{
throw new ArgumentNullException("policyContext");
}
XmlDocument document = new XmlDocument();
policyContext.GetBindingAssertions().Add(document.CreateElement(
GZipMessageEncodingPolicyConstants.GZipEncodingPrefix,
GZipMessageEncodingPolicyConstants.GZipEncodingName,
GZipMessageEncodingPolicyConstants.GZipEncodingNamespace));
}
}
//This class is used to create the custom encoder (GZipMessageEncoder)
internal class GZipMessageEncoderFactory : MessageEncoderFactory
{
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 = new GZipMessageEncoder(messageEncoderFactory.Encoder);
}
//The service framework uses this property to obtain an encoder from this encoder factory
public override MessageEncoder Encoder
{
get { return encoder; }
}
public override MessageVersion MessageVersion
{
get { return encoder.MessageVersion; }
}
//This is the actual GZip encoder
class GZipMessageEncoder : MessageEncoder
{
static string GZipContentType = "application/x-gzip";
//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)
internal GZipMessageEncoder(MessageEncoder messageEncoder)
: base()
{
if (messageEncoder == null)
throw new ArgumentNullException("messageEncoder", "A valid message encoder must be passed to the GZipEncoder");
innerEncoder = messageEncoder;
}
public override string ContentType
{
get { return GZipContentType; }
}
public override string MediaType
{
get { return GZipContentType; }
}
//SOAP version to use - we delegate to the inner encoder for this
public override MessageVersion MessageVersion
{
get { return innerEncoder.MessageVersion; }
}
//Helper method to compress an array of bytes
static ArraySegment<byte> CompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager, int messageOffset)
{
MemoryStream memoryStream = new MemoryStream();
using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Compress, true))
{
gzStream.Write(buffer.Array, buffer.Offset, buffer.Count);
}
byte[] compressedBytes = memoryStream.ToArray();
int totalLength = messageOffset + compressedBytes.Length;
byte[] bufferedBytes = bufferManager.TakeBuffer(totalLength);
Array.Copy(compressedBytes, 0, bufferedBytes, messageOffset, compressedBytes.Length);
bufferManager.ReturnBuffer(buffer.Array);
ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferedBytes, messageOffset, bufferedBytes.Length - messageOffset);
return byteArray;
}
//Helper method to decompress an array of bytes
static ArraySegment<byte> DecompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager)
{
MemoryStream memoryStream = new MemoryStream(buffer.Array, buffer.Offset, buffer.Count);
MemoryStream decompressedStream = new MemoryStream();
int totalRead = 0;
int blockSize = 1024;
byte[] tempBuffer = bufferManager.TakeBuffer(blockSize);
using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Decompress))
{
while (true)
{
int bytesRead = gzStream.Read(tempBuffer, 0, blockSize);
if (bytesRead == 0)
break;
decompressedStream.Write(tempBuffer, 0, bytesRead);
totalRead += bytesRead;
}
}
bufferManager.ReturnBuffer(tempBuffer);
byte[] decompressedBytes = decompressedStream.ToArray();
byte[] bufferManagerBuffer = bufferManager.TakeBuffer(decompressedBytes.Length + buffer.Offset);
Array.Copy(buffer.Array, 0, bufferManagerBuffer, 0, buffer.Offset);
Array.Copy(decompressedBytes, 0, bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);
ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferManagerBuffer, buffer.Offset, decompressedBytes.Length);
bufferManager.ReturnBuffer(buffer.Array);
return byteArray;
}
//One of the two main entry points into the encoder. Called by WCF to decode a buffered byte array into a Message.
public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType)
{
//Decompress the buffer
ArraySegment<byte> decompressedBuffer = DecompressBuffer(buffer, bufferManager);
//Use the inner encoder to decode the decompressed buffer
Message returnMessage = innerEncoder.ReadMessage(decompressedBuffer, bufferManager);
returnMessage.Properties.Encoder = this;
return returnMessage;
}
//One of the two main entry points into the encoder. Called by WCF to encode a Message into a buffered byte array.
public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset)
{
//Use the inner encoder to encode a Message into a buffered byte array
ArraySegment<byte> buffer = innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, 0);
//Compress the resulting byte array
return CompressBuffer(buffer, bufferManager, messageOffset);
}
public override Message ReadMessage(System.IO.Stream stream, int maxSizeOfHeaders, string contentType)
{
//Pass false for the "leaveOpen" parameter to the GZipStream constructor.
//This will ensure that the inner stream gets closed when the message gets closed, which
//will ensure that resources are available for reuse/release.
GZipStream gzStream = new GZipStream(stream, CompressionMode.Decompress, false);
return innerEncoder.ReadMessage(gzStream, maxSizeOfHeaders);
}
public override void WriteMessage(Message message, System.IO.Stream stream)
{
using (GZipStream gzStream = new GZipStream(stream, CompressionMode.Compress, true))
{
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();
}
}
}
#endregion
public class InsecureGZipBasicHttpBinding : Binding
{
readonly HttpTransportBindingElement httpTransport;
readonly TextMessageEncodingBindingElement textMessageEncoding;
readonly GZipMessageEncodingBindingElement compressionMessageEncoding;
public InsecureGZipBasicHttpBinding()
{
httpTransport = new HttpTransportBindingElement();
textMessageEncoding = new TextMessageEncodingBindingElement { MessageVersion = MessageVersion.Soap11 };
compressionMessageEncoding = new GZipMessageEncodingBindingElement(textMessageEncoding);
}
public override BindingElementCollection CreateBindingElements()
{
return new BindingElementCollection
{
this.compressionMessageEncoding,
this.httpTransport
};
}
public override string Scheme
{
get { return this.httpTransport.Scheme; }
}
}
[ServiceContract]
public interface ITest
{
[OperationContract]
string Echo(string text);
}
public class Service : ITest
{
public string Echo(string text)
{
return text;
}
}
public static void Test()
{
string baseAddress = "http://" + Environment.MachineName + ":8000/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddress));
host.AddServiceEndpoint(typeof(ITest), new InsecureGZipBasicHttpBinding(), "");
host.Open();
Console.WriteLine("Host opened");
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(new InsecureGZipBasicHttpBinding(), new EndpointAddress(baseAddress));
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.Echo("Hello"));
((IClientChannel)proxy).Close();
factory.Close();
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
Upvotes: 2