WCF message compression

For WCF applications, compressing request and reply messages before transmission can not only reduce network traffic, but also improve the performance of network transmission.

I. Message Compression Scheme
II. Components for Data Compression and Decompression
Components for message compression and decompression
Components for Compression and Decompression of Request/Reply Messages
5. Compression Message Formatter for WCF Runtime Framework Operational Behavior
6. Viewing compressed messages
VII. Expansion

 

I. Message Compression Scheme
The implementation of message compression in WCF is very simple. We only need to compress the message (request message/reply message) before it is sent, and decompress it before it is deserialized after it is received. There are three typical solutions for the timing of compression/decompression. This is done by customizing Message Encoder and Message Encoding Binding Element.

1. Compression and transmission of encoded byte stream

2. Create channels for compression and decompression

3. Custom Message Formatter implements compression after serialization and decompression before normal serialization

Solution 3 to be introduced here.   

II. Components for Data Compression and Decompression
We support two ways of compression, Dflat and GZip. Two different compression algorithms are represented by the Compression Algorithm enumeration defined below.

1     public enum CompressionAlgorithm
2     {
3         GZip,
4         Deflate
5     }

The DataCompressor defined below is responsible for the actual compression and decompression based on the above two compression algorithms.

 1     internal class DataCompressor
 2     {
 3         public static byte[] Compress(byte[] decompressedData, CompressionAlgorithm algorithm)
 4         {
 5             using (MemoryStream stream = new MemoryStream())
 6             {
 7                 if (algorithm == CompressionAlgorithm.Deflate)
 8                 {
 9                     GZipStream stream2 = new GZipStream(stream, CompressionMode.Compress, true);
10                     stream2.Write(decompressedData, 0, decompressedData.Length);
11                     stream2.Close();
12                 }
13                 else
14                 {
15                     DeflateStream stream3 = new DeflateStream(stream, CompressionMode.Compress, true);
16                     stream3.Write(decompressedData, 0, decompressedData.Length);
17                     stream3.Close();
18                 }
19                 return stream.ToArray();
20             }
21         }
22 
23         public static byte[] Decompress(byte[] compressedData, CompressionAlgorithm algorithm)
24         {
25             using (MemoryStream stream = new MemoryStream(compressedData))
26             {
27                 if (algorithm == CompressionAlgorithm.Deflate)
28                 {
29                     using (GZipStream stream2 = new GZipStream(stream, CompressionMode.Decompress))
30                     {
31                         return LoadToBuffer(stream2);
32                     }
33                 }
34                 else
35                 {
36                     using (DeflateStream stream3 = new DeflateStream(stream, CompressionMode.Decompress))
37                     {
38                         return LoadToBuffer(stream3);
39                     }
40                 }
41             }
42         }
43 
44         private static byte[] LoadToBuffer(Stream stream)
45         {
46             using (MemoryStream stream2 = new MemoryStream())
47             {
48                 int num;
49                 byte[] buffer = new byte[0x400];
50                 while ((num = stream.Read(buffer, 0, buffer.Length)) > 0)
51                 {
52                     stream2.Write(buffer, 0, num);
53                 }
54                 return stream2.ToArray();
55             }
56         }
57     }

Components for message compression and decompression

Compression and decompression of messages are accomplished by the following Message Compressor. Specifically, we compress the main body of the message through the Data Compressor defined above, and store the compressed content in a predefined XML element (CompressedBody and http://www.yswenli.net/comporession/, respectively) with the corresponding Message Header to denote cancellation. The information is compressed and the compression algorithm is adopted. For decompression, the message is judged by whether it has a corresponding Message Header, and if so, decompressed according to the corresponding algorithm.

The concrete realization is as follows:

 1     public class MessageCompressor
 2     {
 3         public MessageCompressor(CompressionAlgorithm algorithm)
 4         {
 5             this.Algorithm = algorithm;
 6         }
 7         public Message CompressMessage(Message sourceMessage)
 8         {
 9             byte[] buffer;
10             using (XmlDictionaryReader reader1 = sourceMessage.GetReaderAtBodyContents())
11             {
12                 buffer = Encoding.UTF8.GetBytes(reader1.ReadOuterXml());
13             }
14             if (buffer.Length == 0)
15             {
16                 Message emptyMessage = Message.CreateMessage(sourceMessage.Version, (string)null);
17                 sourceMessage.Headers.CopyHeadersFrom(sourceMessage);
18                 sourceMessage.Properties.CopyProperties(sourceMessage.Properties);
19                 emptyMessage.Close();
20                 return emptyMessage;
21             }
22             byte[] compressedData = DataCompressor.Compress(buffer, this.Algorithm);
23             string copressedBody = CompressionUtil.CreateCompressedBody(compressedData);
24             XmlTextReader reader = new XmlTextReader(new StringReader(copressedBody), new NameTable());
25             Message message2 = Message.CreateMessage(sourceMessage.Version, null, (XmlReader)reader);
26             message2.Headers.CopyHeadersFrom(sourceMessage);
27             message2.Properties.CopyProperties(sourceMessage.Properties);
28             message2.AddCompressionHeader(this.Algorithm);
29             sourceMessage.Close();
30             return message2;
31         }
32 
33         public Message DecompressMessage(Message sourceMessage)
34         {
35             if (!sourceMessage.IsCompressed())
36             {
37                 return sourceMessage;
38             }
39             CompressionAlgorithm algorithm = sourceMessage.GetCompressionAlgorithm();
40             sourceMessage.RemoveCompressionHeader();
41             byte[] compressedBody = sourceMessage.GetCompressedBody();
42             byte[] decompressedBody = DataCompressor.Decompress(compressedBody, algorithm);
43             string newMessageXml = Encoding.UTF8.GetString(decompressedBody);
44             XmlTextReader reader2 = new XmlTextReader(new StringReader(newMessageXml));
45             Message newMessage = Message.CreateMessage(sourceMessage.Version, null, reader2);
46             newMessage.Headers.CopyHeadersFrom(sourceMessage);
47             newMessage.Properties.CopyProperties(sourceMessage.Properties);
48             return newMessage;
49         }
50         public CompressionAlgorithm Algorithm { get; private set; }
51     }

Below are some extensions and auxiliary methods defined for Message types.

 1     public static class CompressionUtil
 2     {
 3         public const string CompressionMessageHeader = "Compression";
 4         public const string CompressionMessageBody = "CompressedBody";
 5         public const string Namespace = "http://www.yswenli.net/compression";
 6 
 7         public static bool IsCompressed(this Message message)
 8         {
 9             return message.Headers.FindHeader(CompressionMessageHeader, Namespace) > -1;
10         }
11 
12         public static void AddCompressionHeader(this Message message, CompressionAlgorithm algorithm)
13         {
14             message.Headers.Add(MessageHeader.CreateHeader(CompressionMessageHeader, Namespace, string.Format("algorithm = \"{0}\"", algorithm)));
15         }
16 
17         public static void RemoveCompressionHeader(this Message message)
18         {
19             message.Headers.RemoveAll(CompressionMessageHeader, Namespace);
20         }
21 
22         public static CompressionAlgorithm GetCompressionAlgorithm(this Message message)
23         {
24             if (message.IsCompressed())
25             {
26                 var algorithm = message.Headers.GetHeader<string>(CompressionMessageHeader, Namespace);
27                 algorithm = algorithm.Replace("algorithm =", string.Empty).Replace("\"", string.Empty).Trim();
28                 if (algorithm == CompressionAlgorithm.Deflate.ToString())
29                 {
30                     return CompressionAlgorithm.Deflate;
31                 }
32 
33                 if (algorithm == CompressionAlgorithm.GZip.ToString())
34                 {
35                     return CompressionAlgorithm.GZip;
36                 }
37                 throw new InvalidOperationException("Invalid compression algrorithm!");
38             }
39             throw new InvalidOperationException("Message is not compressed!");
40         }
41 
42         public static byte[] GetCompressedBody(this Message message)
43         {
44             byte[] buffer;
45             using (XmlReader reader1 = message.GetReaderAtBodyContents())
46             {
47                 buffer = Convert.FromBase64String(reader1.ReadElementString(CompressionMessageBody, Namespace));
48             }
49             return buffer;
50         }
51 
52         public static string CreateCompressedBody(byte[] content)
53         {
54             StringWriter output = new StringWriter();
55             using (XmlWriter writer2 = XmlWriter.Create(output))
56             {
57                 writer2.WriteStartElement(CompressionMessageBody, Namespace);
58                 writer2.WriteBase64(content, 0, content.Length);
59                 writer2.WriteEndElement();
60             }
61             return output.ToString();
62         }
63     }

Components for compressing and decompressing request/reply messages

The serialization and deserialization of messages are ultimately accomplished through Message Formatter. Specifically, the client implements the serialization of the request message and the reply message through the Client Message Formatter, while the server implements the deserialization of the request message and the serialization of the reply message through the Dispatch Message Formatter.

By default, the Message Formatter selected by WCF is Data ContractSerializer OperationFormatter, which uses Data ContractSerializer for actual serialization and method serialization operations. Our custom Mesage Formatter is actually a package of Data Control Serializer Operational Formatter. We still use it to complete serialization and deserialization, with the addition of compression after serialization and decompression before serialization.

Because Data ContractSerializer OperationFormatter is an internal type, we can only create it by reflection. The following code snippet is the definition of a custom Message Formatter, Compression Message Formatter, for message compression and decompression.

 1     public class CompressionMessageFormatter : IDispatchMessageFormatter, IClientMessageFormatter
 2     {
 3         private const string DataContractSerializerOperationFormatterTypeName = "System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";
 4 
 5         public IDispatchMessageFormatter InnerDispatchMessageFormatter { get; private set; }
 6         public IClientMessageFormatter InnerClientMessageFormatter { get; private set; }
 7         public MessageCompressor MessageCompressor { get; private set; }
 8 
 9         public CompressionMessageFormatter(CompressionAlgorithm algorithm, OperationDescription description, DataContractFormatAttribute dataContractFormatAttribute, DataContractSerializerOperationBehavior serializerFactory)
10         {
11             this.MessageCompressor = new MessageCompressor(algorithm);
12             Type innerFormatterType = Type.GetType(DataContractSerializerOperationFormatterTypeName);
13             var innerFormatter = Activator.CreateInstance(innerFormatterType, description, dataContractFormatAttribute, serializerFactory);
14             this.InnerClientMessageFormatter = innerFormatter as IClientMessageFormatter;
15             this.InnerDispatchMessageFormatter = innerFormatter as IDispatchMessageFormatter;
16         }
17 
18         public void DeserializeRequest(Message message, object[] parameters)
19         {
20             message = this.MessageCompressor.DecompressMessage(message);
21             this.InnerDispatchMessageFormatter.DeserializeRequest(message, parameters);
22         }
23 
24         public Message SerializeReply(MessageVersion messageVersion, object[] parameters, object result)
25         {
26             var message = this.InnerDispatchMessageFormatter.SerializeReply(messageVersion, parameters, result);
27             return this.MessageCompressor.CompressMessage(message);
28         }
29 
30         public object DeserializeReply(Message message, object[] parameters)
31         {
32             message = this.MessageCompressor.DecompressMessage(message);
33             return this.InnerClientMessageFormatter.DeserializeReply(message, parameters);
34         }
35 
36         public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
37         {
38             var message = this.InnerClientMessageFormatter.SerializeRequest(messageVersion, parameters);
39             return this.MessageCompressor.CompressMessage(message);
40         }
41     }

 

5. Compression Message Formatter for WCF Runtime Framework Operational Behavior

Client Message Formatter and Dispatch Message Formatter are actually components of Client Operation and Dispatch Operation. We can apply it to the corresponding operations by following a custom operation behavior Compression OperationBehaviorAttribute.

 1     [AttributeUsage(AttributeTargets.Method)]
 2     public class CompressionOperationBehaviorAttribute : Attribute, IOperationBehavior
 3     {
 4         public CompressionAlgorithm Algorithm { get; set; }
 5 
 6         public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters) { }
 7 
 8         public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
 9         {
10             clientOperation.SerializeRequest = true;
11             clientOperation.DeserializeReply = true;
12             var dataContractFormatAttribute = operationDescription.SyncMethod.GetCustomAttributes(typeof(DataContractFormatAttribute), true).FirstOrDefault() as DataContractFormatAttribute;
13             if (null == dataContractFormatAttribute)
14             {
15                 dataContractFormatAttribute = new DataContractFormatAttribute();
16             }
17 
18             var dataContractSerializerOperationBehavior = operationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
19             clientOperation.Formatter = new CompressionMessageFormatter(this.Algorithm, operationDescription, dataContractFormatAttribute, dataContractSerializerOperationBehavior);
20         }
21 
22         public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
23         {
24             dispatchOperation.SerializeReply = true;
25             dispatchOperation.DeserializeRequest = true;
26             var dataContractFormatAttribute = operationDescription.SyncMethod.GetCustomAttributes(typeof(DataContractFormatAttribute), true).FirstOrDefault() as DataContractFormatAttribute;
27             if (null == dataContractFormatAttribute)
28             {
29                 dataContractFormatAttribute = new DataContractFormatAttribute();
30             }
31             var dataContractSerializerOperationBehavior = operationDescription.Behaviors.Find<DataContractSerializerOperationBehavior>();
32             dispatchOperation.Formatter = new CompressionMessageFormatter(this.Algorithm, operationDescription, dataContractFormatAttribute, dataContractSerializerOperationBehavior);
33         }
34 
35         public void Validate(OperationDescription operationDescription) { }
36     }

6. Viewing compressed messages
In order to verify whether the corresponding message has been compressed using the operation method of Compression OperationBehaviorAttribute, we can use a simple example to verify. We use common examples of computing services. Here is the definition of service contracts and service types. The Compression Operational Behavior Attribute we defined above applies to the Add operation of the service contract.

 1     [ServiceContract(Namespace = "http://www.yswenli.net/")]
 2     public interface ICalculator
 3     {
 4         [OperationContract]
 5         [CompressionOperationBehavior]
 6         double Add(double x, double y);
 7     }
 8     public class CalculatorService : ICalculator
 9     {
10         public double Add(double x, double y)
11         {
12             return x + y;
13         }
14     }

We use BasicHttpBinding as the binding type of the endpoint (see the source code for the specific configuration). The following is the content of the message obtained through Fiddler, whose main part has been encoding based on compression.

1     <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
2       <s:Header>
3         <Compression xmlns="http://www.yswenli.net/compression">algorithm = "GZip"</Compression>
4       </s:Header>
5       <s:Body>
6         <CompressedBody xmlns="http://www.yswenli.net/compression">7L0HYBx ... CQAA//8=</CompressedBody>
7       </s:Body>
8     </s:Envelope>

Reply to the message

1     <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
2       <s:Header>
3         <Compression xmlns="http://www.yswenli.net/compression">algorithm = "GZip"</Compression>
4       </s:Header>
5       <s:Body>
6         <CompressedBody xmlns="http://www.yswenli.net/compression">7L0H...PAAAA//8=</CompressedBody>
7       </s:Body>
8     </s:Envelope>

VII. Expansion

If you don't want to serialize Microsoft's own, or for some reason (emoji character exceptions, etc.), you can use custom IDispatchMessageInspector . Because Compression Message Formatter uses Data ContractSerializer Operational Formatter based on Data ContractSerializer serializer to serialize and send messages, Data ContractSerializer is only a default choice for serialization by WCF (WCF can also use traditional XmlSeriaizer); To enable Compression Message Formatter to use other serializers, corresponding corrections can be made.

 

 


For reprinting, please indicate the source of this article: http://www.cnblogs.com/yswenli/p/6670081.html
More welcome to my github: https://github.com/yswenli
If you find any questions or suggestions in this article, you are welcome to share them with us.~

Keywords: C# encoding network Attribute github

Added by blommer on Sat, 13 Jul 2019 01:26:26 +0300