|
| 1 | + |
| 2 | +using System.IO.Compression; |
| 3 | + |
| 4 | +namespace CoreWcf.Samples.GZipEncoder |
| 5 | +{ |
| 6 | + //This class is used to create the custom encoder (GZipMessageEncoder) |
| 7 | + internal class GZipMessageEncoderFactory : MessageEncoderFactory |
| 8 | + { |
| 9 | + private readonly MessageEncoder _encoder; |
| 10 | + |
| 11 | + //The GZip encoder wraps an inner encoder |
| 12 | + //We require a factory to be passed in that will create this inner encoder |
| 13 | + public GZipMessageEncoderFactory(MessageEncoderFactory messageEncoderFactory) |
| 14 | + { |
| 15 | + if (messageEncoderFactory == null) |
| 16 | + throw new ArgumentNullException(nameof(messageEncoderFactory), "A valid message encoder factory must be passed to the GZipEncoder"); |
| 17 | + _encoder = new GZipMessageEncoder(messageEncoderFactory.Encoder); |
| 18 | + } |
| 19 | + |
| 20 | + //The service framework uses this property to obtain an encoder from this encoder factory |
| 21 | + public override MessageEncoder Encoder => _encoder; |
| 22 | + |
| 23 | + public override MessageVersion MessageVersion => _encoder.MessageVersion; |
| 24 | + |
| 25 | + //This is the actual GZip encoder |
| 26 | + public class GZipMessageEncoder : MessageEncoder |
| 27 | + { |
| 28 | + private static readonly string s_gZipContentType = "application/x-gzip"; |
| 29 | + |
| 30 | + //This implementation wraps an inner encoder that actually converts a WCF Message |
| 31 | + //into textual XML, binary XML or some other format. This implementation then compresses the results. |
| 32 | + //The opposite happens when reading messages. |
| 33 | + //This member stores this inner encoder. |
| 34 | + private readonly MessageEncoder _innerEncoder; |
| 35 | + |
| 36 | + //We require an inner encoder to be supplied (see comment above) |
| 37 | + internal GZipMessageEncoder(MessageEncoder messageEncoder) |
| 38 | + : base() |
| 39 | + { |
| 40 | + _innerEncoder = messageEncoder ?? throw new ArgumentNullException(nameof(messageEncoder), "A valid message encoder must be passed to the GZipEncoder"); |
| 41 | + } |
| 42 | + |
| 43 | + public override string ContentType => s_gZipContentType; |
| 44 | + |
| 45 | + public override string MediaType => s_gZipContentType; |
| 46 | + |
| 47 | + //SOAP version to use - we delegate to the inner encoder for this |
| 48 | + public override MessageVersion MessageVersion => _innerEncoder.MessageVersion; |
| 49 | + |
| 50 | + //Helper method to compress an array of bytes |
| 51 | + static ArraySegment<byte> CompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager, int messageOffset) |
| 52 | + { |
| 53 | + MemoryStream memoryStream = new MemoryStream(); |
| 54 | + |
| 55 | + using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Compress, true)) |
| 56 | + { |
| 57 | + gzStream.Write(buffer.Array, buffer.Offset, buffer.Count); |
| 58 | + } |
| 59 | + |
| 60 | + byte[] compressedBytes = memoryStream.ToArray(); |
| 61 | + int totalLength = messageOffset + compressedBytes.Length; |
| 62 | + byte[] bufferedBytes = bufferManager.TakeBuffer(totalLength); |
| 63 | + |
| 64 | + Array.Copy(compressedBytes, 0, bufferedBytes, messageOffset, compressedBytes.Length); |
| 65 | + |
| 66 | + bufferManager.ReturnBuffer(buffer.Array); |
| 67 | + ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferedBytes, messageOffset, bufferedBytes.Length - messageOffset); |
| 68 | + |
| 69 | + return byteArray; |
| 70 | + } |
| 71 | + |
| 72 | + //Helper method to decompress an array of bytes |
| 73 | + static ArraySegment<byte> DecompressBuffer(ArraySegment<byte> buffer, BufferManager bufferManager) |
| 74 | + { |
| 75 | + MemoryStream memoryStream = new MemoryStream(buffer.Array, buffer.Offset, buffer.Count); |
| 76 | + MemoryStream decompressedStream = new MemoryStream(); |
| 77 | + int totalRead = 0; |
| 78 | + int blockSize = 1024; |
| 79 | + byte[] tempBuffer = bufferManager.TakeBuffer(blockSize); |
| 80 | + using (GZipStream gzStream = new GZipStream(memoryStream, CompressionMode.Decompress)) |
| 81 | + { |
| 82 | + while (true) |
| 83 | + { |
| 84 | + int bytesRead = gzStream.Read(tempBuffer, 0, blockSize); |
| 85 | + if (bytesRead == 0) |
| 86 | + break; |
| 87 | + decompressedStream.Write(tempBuffer, 0, bytesRead); |
| 88 | + totalRead += bytesRead; |
| 89 | + } |
| 90 | + } |
| 91 | + bufferManager.ReturnBuffer(tempBuffer); |
| 92 | + |
| 93 | + byte[] decompressedBytes = decompressedStream.ToArray(); |
| 94 | + byte[] bufferManagerBuffer = bufferManager.TakeBuffer(decompressedBytes.Length + buffer.Offset); |
| 95 | + Array.Copy(buffer.Array, 0, bufferManagerBuffer, 0, buffer.Offset); |
| 96 | + Array.Copy(decompressedBytes, 0, bufferManagerBuffer, buffer.Offset, decompressedBytes.Length); |
| 97 | + |
| 98 | + ArraySegment<byte> byteArray = new ArraySegment<byte>(bufferManagerBuffer, buffer.Offset, decompressedBytes.Length); |
| 99 | + bufferManager.ReturnBuffer(buffer.Array); |
| 100 | + |
| 101 | + return byteArray; |
| 102 | + } |
| 103 | + |
| 104 | + //One of the two main entry points into the encoder. Called by WCF to decode a buffered byte array into a Message. |
| 105 | + public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType) |
| 106 | + { |
| 107 | + //Decompress the buffer |
| 108 | + ArraySegment<byte> decompressedBuffer = DecompressBuffer(buffer, bufferManager); |
| 109 | + //Use the inner encoder to decode the decompressed buffer |
| 110 | + Message returnMessage = _innerEncoder.ReadMessage(decompressedBuffer, bufferManager); |
| 111 | + returnMessage.Properties.Encoder = this; |
| 112 | + return returnMessage; |
| 113 | + } |
| 114 | + |
| 115 | + //One of the two main entry points into the encoder. Called by WCF to encode a Message into a buffered byte array. |
| 116 | + public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset) |
| 117 | + { |
| 118 | + //Use the inner encoder to encode a Message into a buffered byte array |
| 119 | + ArraySegment<byte> buffer = _innerEncoder.WriteMessage(message, maxMessageSize, bufferManager, 0); |
| 120 | + //Compress the resulting byte array |
| 121 | + return CompressBuffer(buffer, bufferManager, messageOffset); |
| 122 | + } |
| 123 | + |
| 124 | + public override Message ReadMessage(System.IO.Stream stream, int maxSizeOfHeaders, string contentType) |
| 125 | + { |
| 126 | + //Pass false for the "leaveOpen" parameter to the GZipStream constructor. |
| 127 | + //This will ensure that the inner stream gets closed when the message gets closed, which |
| 128 | + //will ensure that resources are available for reuse/release. |
| 129 | + GZipStream gzStream = new GZipStream(stream, CompressionMode.Decompress, false); |
| 130 | + return _innerEncoder.ReadMessage(gzStream, maxSizeOfHeaders); |
| 131 | + } |
| 132 | + |
| 133 | + public override void WriteMessage(Message message, System.IO.Stream stream) |
| 134 | + { |
| 135 | + using (GZipStream gzStream = new GZipStream(stream, CompressionMode.Compress, true)) |
| 136 | + { |
| 137 | + _innerEncoder.WriteMessage(message, gzStream); |
| 138 | + } |
| 139 | + |
| 140 | + // innerEncoder.WriteMessage(message, gzStream) depends on that it can flush data by flushing |
| 141 | + // the stream passed in, but the implementation of GZipStream.Flush will not flush underlying |
| 142 | + // stream, so we need to flush here. |
| 143 | + stream.Flush(); |
| 144 | + } |
| 145 | + } |
| 146 | + } |
| 147 | +} |
0 commit comments