123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334 |
- #if !BESTHTTP_DISABLE_WEBSOCKET && (!UNITY_WEBGL || UNITY_EDITOR)
- using System;
- using BestHTTP.Extensions;
- using BestHTTP.WebSocket.Frames;
- using BestHTTP.Decompression.Zlib;
- namespace BestHTTP.WebSocket.Extensions
- {
-
-
-
-
- public sealed class PerMessageCompression : IExtension
- {
- private static readonly byte[] Trailer = new byte[] { 0x00, 0x00, 0xFF, 0xFF };
- #region Public Properties
-
-
-
-
-
- public bool ClientNoContextTakeover { get; private set; }
-
-
-
- public bool ServerNoContextTakeover { get; private set; }
-
-
-
- public int ClientMaxWindowBits { get; private set; }
-
-
-
- public int ServerMaxWindowBits { get; private set; }
-
-
-
- public CompressionLevel Level { get; private set; }
-
-
-
- public int MinimumDataLegthToCompress { get; set; }
- #endregion
- #region Private fields
-
-
-
- private System.IO.MemoryStream compressorOutputStream;
- private DeflateStream compressorDeflateStream;
-
-
-
- private System.IO.MemoryStream decompressorInputStream;
- private System.IO.MemoryStream decompressorOutputStream;
- private DeflateStream decompressorDeflateStream;
- private byte[] copyBuffer = new byte[1024];
- #endregion
- public PerMessageCompression()
- :this(CompressionLevel.Default, false, false, ZlibConstants.WindowBitsMax, ZlibConstants.WindowBitsMax, 10)
- { }
- public PerMessageCompression(CompressionLevel level,
- bool clientNoContextTakeover,
- bool serverNoContextTakeover,
- int desiredClientMaxWindowBits,
- int desiredServerMaxWindowBits,
- int minDatalengthToCompress)
- {
- this.Level = level;
- this.ClientNoContextTakeover = clientNoContextTakeover;
- this.ServerNoContextTakeover = ServerNoContextTakeover;
- this.ClientMaxWindowBits = desiredClientMaxWindowBits;
- this.ServerMaxWindowBits = desiredServerMaxWindowBits;
- this.MinimumDataLegthToCompress = minDatalengthToCompress;
- }
- #region IExtension Implementation
-
-
-
-
- public void AddNegotiation(HTTPRequest request)
- {
-
- string headerValue = "permessage-deflate";
-
-
-
-
- if (this.ServerNoContextTakeover)
- headerValue += "; server_no_context_takeover";
-
-
-
-
-
- if (this.ClientNoContextTakeover)
- headerValue += "; client_no_context_takeover";
-
-
-
- if (this.ServerMaxWindowBits != ZlibConstants.WindowBitsMax)
- headerValue += "; server_max_window_bits=" + this.ServerMaxWindowBits.ToString();
- else
-
- this.ServerMaxWindowBits = ZlibConstants.WindowBitsMax;
-
-
-
- if (this.ClientMaxWindowBits != ZlibConstants.WindowBitsMax)
- headerValue += "; client_max_window_bits=" + this.ClientMaxWindowBits.ToString();
- else
- {
- headerValue += "; client_max_window_bits";
-
-
-
-
-
- this.ClientMaxWindowBits = ZlibConstants.WindowBitsMax;
- }
-
- request.AddHeader("Sec-WebSocket-Extensions", headerValue);
- }
- public bool ParseNegotiation(WebSocketResponse resp)
- {
-
- var headerValues = resp.GetHeaderValues("Sec-WebSocket-Extensions");
- if (headerValues == null)
- return false;
- for (int i = 0; i < headerValues.Count; ++i)
- {
-
- HeaderParser parser = new HeaderParser(headerValues[i]);
- for (int cv = 0; cv < parser.Values.Count; ++cv)
- {
- HeaderValue value = parser.Values[i];
- if (!string.IsNullOrEmpty(value.Key) && value.Key.StartsWith("permessage-deflate", StringComparison.OrdinalIgnoreCase))
- {
- HTTPManager.Logger.Information("PerMessageCompression", "Enabled with header: " + headerValues[i]);
- HeaderValue option;
- if (value.TryGetOption("client_no_context_takeover", out option))
- this.ClientNoContextTakeover = true;
- if (value.TryGetOption("server_no_context_takeover", out option))
- this.ServerNoContextTakeover = true;
- if (value.TryGetOption("client_max_window_bits", out option))
- if (option.HasValue)
- {
- int windowBits;
- if (int.TryParse(option.Value, out windowBits))
- this.ClientMaxWindowBits = windowBits;
- }
- if (value.TryGetOption("server_max_window_bits", out option))
- if (option.HasValue)
- {
- int windowBits;
- if (int.TryParse(option.Value, out windowBits))
- this.ServerMaxWindowBits = windowBits;
- }
- return true;
- }
- }
- }
- return false;
- }
-
-
-
-
- public byte GetFrameHeader(WebSocketFrame writer, byte inFlag)
- {
-
-
- if ((writer.Type == WebSocketFrameTypes.Binary || writer.Type == WebSocketFrameTypes.Text) &&
- writer.Data != null && writer.Data.Length >= this.MinimumDataLegthToCompress)
- return (byte)(inFlag | 0x40);
- else
- return inFlag;
- }
-
-
-
- public byte[] Encode(WebSocketFrame writer)
- {
- if (writer.Data == null)
- return WebSocketFrame.NoData;
-
- if ((writer.Header & 0x40) != 0)
- return Compress(writer.Data);
- else
- return writer.Data;
- }
-
-
-
- public byte[] Decode(byte header, byte[] data)
- {
-
- if ((header & 0x40) != 0)
- return Decompress(data);
- else
- return data;
- }
- #endregion
- #region Private Helper Functions
-
-
-
- private byte[] Compress(byte[] data)
- {
- if (compressorOutputStream == null)
- compressorOutputStream = new System.IO.MemoryStream();
- compressorOutputStream.SetLength(0);
- if (compressorDeflateStream == null)
- {
- compressorDeflateStream = new DeflateStream(compressorOutputStream, CompressionMode.Compress, this.Level, true, this.ClientMaxWindowBits);
- compressorDeflateStream.FlushMode = FlushType.Sync;
- }
- byte[] result = null;
- try
- {
- compressorDeflateStream.Write(data, 0, data.Length);
- compressorDeflateStream.Flush();
- compressorOutputStream.Position = 0;
-
-
- compressorOutputStream.SetLength(compressorOutputStream.Length - 4);
- result = compressorOutputStream.ToArray();
- }
- finally
- {
- if (this.ClientNoContextTakeover)
- {
- compressorDeflateStream.Dispose();
- compressorDeflateStream = null;
- }
- }
- return result;
- }
-
-
-
- private byte[] Decompress(byte[] data)
- {
- if (decompressorInputStream == null)
- decompressorInputStream = new System.IO.MemoryStream(data.Length + 4);
- decompressorInputStream.Write(data, 0, data.Length);
-
-
- decompressorInputStream.Write(PerMessageCompression.Trailer, 0, PerMessageCompression.Trailer.Length);
- decompressorInputStream.Position = 0;
- if (decompressorDeflateStream == null)
- {
- decompressorDeflateStream = new DeflateStream(decompressorInputStream, CompressionMode.Decompress, CompressionLevel.Default, true, this.ServerMaxWindowBits);
- decompressorDeflateStream.FlushMode = FlushType.Sync;
- }
- if (decompressorOutputStream == null)
- decompressorOutputStream = new System.IO.MemoryStream();
- decompressorOutputStream.SetLength(0);
- int readCount;
- while ((readCount = decompressorDeflateStream.Read(copyBuffer, 0, copyBuffer.Length)) != 0)
- decompressorOutputStream.Write(copyBuffer, 0, readCount);
- decompressorDeflateStream.SetLength(0);
- byte[] result = decompressorOutputStream.ToArray();
- if (this.ServerNoContextTakeover)
- {
- decompressorDeflateStream.Dispose();
- decompressorDeflateStream = null;
- }
- return result;
- }
- #endregion
- }
- }
- #endif
|