Fix for the per message compression

This commit is contained in:
sta 2013-04-25 02:31:38 +09:00
parent b33b5e63ba
commit a1345386fd
63 changed files with 230 additions and 135 deletions

Binary file not shown.

View File

@ -116,6 +116,7 @@ namespace Example
//ws.SetCookie(new Cookie("nobita", "\"idiot, gunfighter\"")); //ws.SetCookie(new Cookie("nobita", "\"idiot, gunfighter\""));
//ws.SetCookie(new Cookie("dora", "tanuki")); //ws.SetCookie(new Cookie("dora", "tanuki"));
ws.Connect(); ws.Connect();
//Console.WriteLine("Compression: {0}", ws.Compression);
Thread.Sleep(500); Thread.Sleep(500);
Console.WriteLine("\nType \"exit\" to exit.\n"); Console.WriteLine("\nType \"exit\" to exit.\n");

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -42,6 +42,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text; using System.Text;
@ -63,6 +64,83 @@ namespace WebSocketSharp {
#region Private Methods #region Private Methods
private static byte[] compress(this byte[] value)
{
if (value.LongLength == 0)
//return new Byte[] { 0x00, 0x00, 0x00, 0xff, 0xff };
return value;
using (var input = new MemoryStream(value))
using (var output = input.compress())
{
output.Close();
return output.ToArray();
}
}
private static MemoryStream compress(this Stream stream)
{
var output = new MemoryStream();
if (stream.Length == 0)
return output;
using (var ds = new DeflateStream(output, CompressionMode.Compress, true))
{
int readLen = 0;
var buffer = new byte[256];
while (true)
{
readLen = stream.Read(buffer, 0, buffer.Length);
if (readLen == 0)
break;
ds.Write(buffer, 0, readLen);
}
ds.Close(); // "BFINAL" set to 1.
output.Position = 0;
return output;
}
}
private static byte[] decompress(this byte[] value)
{
if (value.LongLength == 0)
return value;
using (var input = new MemoryStream(value))
using (var output = input.decompress())
{
output.Close();
return output.ToArray();
}
}
private static MemoryStream decompress(this Stream stream)
{
var output = new MemoryStream();
if (stream.Length == 0)
return output;
using (var ds = new DeflateStream(stream, CompressionMode.Decompress, true))
{
int readLen = 0;
var buffer = new byte[256];
while (true)
{
readLen = ds.Read(buffer, 0, buffer.Length);
if (readLen == 0)
break;
output.Write(buffer, 0, readLen);
}
output.Position = 0;
return output;
}
}
private static void times(this ulong n, Action act) private static void times(this ulong n, Action act)
{ {
for (ulong i = 0; i < n; i++) for (ulong i = 0; i < n; i++)
@ -73,6 +151,34 @@ namespace WebSocketSharp {
#region Internal Methods #region Internal Methods
internal static byte[] Compress(this byte[] value, CompressionMethod method)
{
return method == CompressionMethod.DEFLATE
? value.compress()
: value;
}
internal static Stream Compress(this Stream stream, CompressionMethod method)
{
return method == CompressionMethod.DEFLATE
? stream.compress()
: stream;
}
internal static byte[] Decompress(this byte[] value, CompressionMethod method)
{
return method == CompressionMethod.DEFLATE
? value.decompress()
: value;
}
internal static Stream Decompress(this Stream stream, CompressionMethod method)
{
return method == CompressionMethod.DEFLATE
? stream.decompress()
: stream;
}
internal static string GetNameInternal(this string nameAndValue, string separator) internal static string GetNameInternal(this string nameAndValue, string separator)
{ {
int i = nameAndValue.IndexOf(separator); int i = nameAndValue.IndexOf(separator);

View File

@ -128,48 +128,6 @@ namespace WebSocketSharp {
#region Private Methods #region Private Methods
private static byte[] compress(byte[] value)
{
if (value.LongLength == 0)
//return new Byte[] { 0x00, 0x00, 0x00, 0xff, 0xff };
return value;
using (var comp = new MemoryStream())
using (var ds = new DeflateStream(comp, CompressionMode.Compress, true))
{
ds.Write(value, 0, value.Length);
ds.Close(); // "BFINAL" set to 1.
comp.Close();
return comp.ToArray();
}
}
private static byte[] decompress(byte[] value)
{
if (value.LongLength == 0)
return value;
using (var decomp = new MemoryStream())
using (var comp = new MemoryStream(value))
using (var ds = new DeflateStream(comp, CompressionMode.Decompress, true))
{
int readLen = 0;
var buffer = new byte[256];
while (true)
{
readLen = ds.Read(buffer, 0, buffer.Length);
if (readLen == 0)
break;
decomp.Write(buffer, 0, readLen);
}
decomp.Close();
return decomp.ToArray();
}
}
private static void mask(byte[] src, byte[] key) private static void mask(byte[] src, byte[] key)
{ {
for (long i = 0; i < src.LongLength; i++) for (long i = 0; i < src.LongLength; i++)
@ -184,14 +142,13 @@ namespace WebSocketSharp {
{ {
try try
{ {
if (method == CompressionMethod.NONE)
return false;
if (ExtensionData.LongLength > 0) if (ExtensionData.LongLength > 0)
return false; return false;
if (method == CompressionMethod.DEFLATE) ApplicationData = ApplicationData.Compress(method);
ApplicationData = compress(ApplicationData);
else
return false;
return true; return true;
} }
catch catch
@ -204,14 +161,12 @@ namespace WebSocketSharp {
{ {
try try
{ {
if (ApplicationData.LongLength == 0) if (method == CompressionMethod.NONE)
return true;
if (method == CompressionMethod.DEFLATE)
ApplicationData = decompress(ApplicationData);
else
return false; return false;
if (ApplicationData.LongLength > 0)
ApplicationData = ApplicationData.Decompress(method);
return true; return true;
} }
catch catch

View File

@ -36,6 +36,7 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography; using System.Security.Cryptography;
@ -63,12 +64,6 @@ namespace WebSocketSharp {
#endregion #endregion
#region Private Static Fields
private static NameValueCollection _extensionsReg;
#endregion
#region Private Fields #region Private Fields
private string _base64key; private string _base64key;
@ -82,6 +77,7 @@ namespace WebSocketSharp {
private Object _forClose; private Object _forClose;
private Object _forSend; private Object _forSend;
private string _origin; private string _origin;
private bool _perFrameCompress;
private string _protocol; private string _protocol;
private string _protocols; private string _protocols;
private volatile WsState _readyState; private volatile WsState _readyState;
@ -93,18 +89,6 @@ namespace WebSocketSharp {
#endregion #endregion
#region Static Constructor
static WebSocket()
{
_extensionsReg = new NameValueCollection();
_extensionsReg.Add("deflate", "deflate-frame");
_extensionsReg.Add("deflate", "perframe-compress; method=deflate");
_extensionsReg.Add("deflate", "permessage-compress; method=deflate");
}
#endregion
#region Private Constructors #region Private Constructors
private WebSocket() private WebSocket()
@ -115,6 +99,7 @@ namespace WebSocketSharp {
_forClose = new Object(); _forClose = new Object();
_forSend = new Object(); _forSend = new Object();
_origin = String.Empty; _origin = String.Empty;
_perFrameCompress = false;
_protocol = String.Empty; _protocol = String.Empty;
_readyState = WsState.CONNECTING; _readyState = WsState.CONNECTING;
} }
@ -512,7 +497,7 @@ namespace WebSocketSharp {
private void closeHandshake(PayloadData data) private void closeHandshake(PayloadData data)
{ {
var args = new CloseEventArgs(data); var args = new CloseEventArgs(data);
var frame = createControlFrame(Opcode.CLOSE, data); var frame = createControlFrame(Opcode.CLOSE, data, _client);
if (send(frame)) if (send(frame))
args.WasClean = true; args.WasClean = true;
@ -574,7 +559,7 @@ namespace WebSocketSharp {
} }
// As client // As client
private string createBase64Key() private static string createBase64Key()
{ {
var src = new byte[16]; var src = new byte[16];
var rand = new Random(); var rand = new Random();
@ -583,37 +568,38 @@ namespace WebSocketSharp {
return Convert.ToBase64String(src); return Convert.ToBase64String(src);
} }
// As client private static string createCompressExtension(CompressionMethod method)
private string createCompressionExtension()
{ {
return _compression != CompressionMethod.NONE return method != CompressionMethod.NONE
//? "deflate-frame" ? String.Format("permessage-compress; method={0}", method.ToString().ToLower())
//? String.Format("perframe-compress; method={0}", _compression.ToString().ToLower())
? String.Format("permessage-compress; method={0}", _compression.ToString().ToLower())
: String.Empty; : String.Empty;
} }
private WsFrame createControlFrame(Opcode opcode, PayloadData payloadData) private static WsFrame createControlFrame(Opcode opcode, PayloadData payloadData, bool client)
{ {
return _client var mask = client ? Mask.MASK : Mask.UNMASK;
? new WsFrame(opcode, Mask.MASK, payloadData) return new WsFrame(opcode, mask, payloadData);
: new WsFrame(opcode, Mask.UNMASK, payloadData);
} }
private WsFrame createDataFrame(Fin fin, Opcode opcode, PayloadData payloadData) private static WsFrame createDataFrame(
Fin fin, Opcode opcode, PayloadData payloadData, CompressionMethod method, bool compressed, bool client)
{ {
return _client var mask = client ? Mask.MASK : Mask.UNMASK;
? new WsFrame(fin, opcode, Mask.MASK, payloadData, _compression) var compress = compressed ? CompressionMethod.NONE : method;
: new WsFrame(fin, opcode, Mask.UNMASK, payloadData, _compression); var frame = new WsFrame(fin, opcode, mask, payloadData, compress);
if (compressed)
frame.PerMessageCompressed = true;
return frame;
} }
// As client // As client
private string createRequestExtensions() private string createRequestExtensions()
{ {
var extensions = new StringBuilder(64); var extensions = new StringBuilder(64);
var compression = createCompressionExtension(); var compress = createCompressExtension(_compression);
if (!compression.IsEmpty()) if (!compress.IsEmpty())
extensions.Append(compression); extensions.Append(compress);
return extensions.Length > 0 return extensions.Length > 0
? extensions.ToString() ? extensions.ToString()
@ -712,28 +698,14 @@ namespace WebSocketSharp {
_client = false; _client = false;
} }
// As client private static bool isCompressExtension(string value, CompressionMethod method)
private bool isCompressionExtension(string value)
{ {
var expected = createCompressionExtension(); var expected = createCompressExtension(method);
return !expected.IsEmpty() return !expected.IsEmpty()
? value.Equals(expected) ? value.Equals(expected)
: false; : false;
} }
// As server
private bool isDeflateExtension(string value)
{
if (value.StartsWith("x-webkit-"))
value = value.Substring(9);
foreach (var deflate in _extensionsReg.GetValues("deflate"))
if (value.Equals(deflate))
return true;
return false;
}
private bool isOpened(bool errorIfOpened) private bool isOpened(bool errorIfOpened)
{ {
if (_readyState != WsState.OPEN && _readyState != WsState.CLOSING) if (_readyState != WsState.OPEN && _readyState != WsState.CLOSING)
@ -745,6 +717,18 @@ namespace WebSocketSharp {
return true; return true;
} }
// As server
private static bool isPerFrameCompressExtension(string value)
{
if (value.Equals("deflate-frame"))
return true;
if (value.Equals("perframe-compress; method=deflate"))
return true;
return false;
}
// As server // As server
private bool isValidHostHeader() private bool isValidHostHeader()
{ {
@ -831,7 +815,7 @@ namespace WebSocketSharp {
return false; return false;
} }
var frame = createControlFrame(Opcode.PING, new PayloadData(buffer)); var frame = createControlFrame(Opcode.PING, new PayloadData(buffer), _client);
if (!send(frame)) if (!send(frame))
return false; return false;
@ -840,7 +824,7 @@ namespace WebSocketSharp {
private void pong(PayloadData data) private void pong(PayloadData data)
{ {
var frame = createControlFrame(Opcode.PONG, data); var frame = createControlFrame(Opcode.PONG, data, _client);
send(frame); send(frame);
} }
@ -914,7 +898,7 @@ namespace WebSocketSharp {
private void processFragments(WsFrame first) private void processFragments(WsFrame first)
{ {
bool compressed = first.IsCompressed; bool compressed = first.IsCompressed;
if (compressed) if (compressed && _perFrameCompress)
first.Decompress(_compression); first.Decompress(_compression);
var buffer = new List<byte>(first.PayloadData.ToByteArray()); var buffer = new List<byte>(first.PayloadData.ToByteArray());
@ -923,7 +907,7 @@ namespace WebSocketSharp {
if (!contFrame.IsContinuation) if (!contFrame.IsContinuation)
return false; return false;
if (compressed) if (contFrame.IsCompressed)
contFrame.Decompress(_compression); contFrame.Decompress(_compression);
buffer.AddRange(contFrame.PayloadData.ToByteArray()); buffer.AddRange(contFrame.PayloadData.ToByteArray());
@ -966,7 +950,11 @@ namespace WebSocketSharp {
return; return;
} }
onMessage(new MessageEventArgs(first.Opcode, new PayloadData(buffer.ToArray()))); var data = compressed && !_perFrameCompress
? buffer.ToArray().Decompress(_compression)
: buffer.ToArray();
onMessage(new MessageEventArgs(first.Opcode, new PayloadData(data)));
} }
private void processFrame(WsFrame frame) private void processFrame(WsFrame frame)
@ -1022,23 +1010,40 @@ namespace WebSocketSharp {
if (extensions.IsNullOrEmpty()) if (extensions.IsNullOrEmpty())
return; return;
bool deflate = false; var compress = false;
var buffer = new StringBuilder(64); var buffer = new List<string>();
foreach (var extension in extensions.SplitHeaderValue(',')) foreach (var extension in extensions.SplitHeaderValue(','))
{ {
var tmp = extension.Trim(); var e = extension.Trim();
if (!deflate && isDeflateExtension(tmp)) var tmp = e.StartsWith("x-webkit-")
? e.Substring(9)
: e;
if (!compress)
{ {
buffer.Append(tmp); if (isPerFrameCompressExtension(tmp))
deflate = true; {
_perFrameCompress = true;
_compression = CompressionMethod.DEFLATE;
compress = true;
}
if (!compress && isCompressExtension(tmp, CompressionMethod.DEFLATE))
{
_compression = CompressionMethod.DEFLATE;
compress = true;
}
if (compress)
{
buffer.Add(e);
continue;
}
} }
} }
if (deflate) if (buffer.Count > 0)
_compression = CompressionMethod.DEFLATE; _extensions = buffer.ToArray().ToString(",");
if (buffer.Length > 0)
_extensions = buffer.ToString();
} }
// As server // As server
@ -1080,13 +1085,13 @@ namespace WebSocketSharp {
// As client // As client
private void processResponseExtensions(string extensions) private void processResponseExtensions(string extensions)
{ {
bool compress = false; var compress = false;
if (!extensions.IsNullOrEmpty()) if (!extensions.IsNullOrEmpty())
{ {
foreach (var extension in extensions.SplitHeaderValue(',')) foreach (var extension in extensions.SplitHeaderValue(','))
{ {
var tmp = extension.Trim(); var e = extension.Trim();
if (!compress && isCompressionExtension(tmp)) if (!compress && isCompressExtension(e, _compression))
compress = true; compress = true;
} }
@ -1197,6 +1202,20 @@ namespace WebSocketSharp {
} }
private void send(Opcode opcode, Stream stream) private void send(Opcode opcode, Stream stream)
{
if (_compression == CompressionMethod.NONE || _perFrameCompress)
{
send(opcode, stream, false);
return;
}
using (var compressed = stream.Compress(_compression))
{
send(opcode, compressed, true);
}
}
private void send(Opcode opcode, Stream stream, bool compressed)
{ {
lock (_forSend) lock (_forSend)
{ {
@ -1210,9 +1229,9 @@ namespace WebSocketSharp {
var length = stream.Length; var length = stream.Length;
if (length <= _fragmentLen) if (length <= _fragmentLen)
send(Fin.FINAL, opcode, stream.ReadBytes((int)length)); send(Fin.FINAL, opcode, stream.ReadBytes((int)length), compressed);
else else
sendFragmented(opcode, stream); sendFragmented(opcode, stream, compressed);
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -1221,9 +1240,10 @@ namespace WebSocketSharp {
} }
} }
private bool send(Fin fin, Opcode opcode, byte[] data) private bool send(Fin fin, Opcode opcode, byte[] data, bool compressed)
{ {
var frame = createDataFrame(fin, opcode, new PayloadData(data)); var frame = createDataFrame(
fin, opcode, new PayloadData(data), _compression, compressed, _client);
return send(frame); return send(frame);
} }
@ -1257,7 +1277,7 @@ namespace WebSocketSharp {
action.BeginInvoke(opcode, stream, callback, null); action.BeginInvoke(opcode, stream, callback, null);
} }
private long sendFragmented(Opcode opcode, Stream stream) private long sendFragmented(Opcode opcode, Stream stream, bool compressed)
{ {
var length = stream.Length; var length = stream.Length;
var quo = length / _fragmentLen; var quo = length / _fragmentLen;
@ -1270,7 +1290,7 @@ namespace WebSocketSharp {
// First // First
tmpLen = stream.Read(buffer, 0, _fragmentLen); tmpLen = stream.Read(buffer, 0, _fragmentLen);
if (send(Fin.MORE, opcode, buffer)) if (send(Fin.MORE, opcode, buffer, compressed))
readLen += tmpLen; readLen += tmpLen;
else else
return 0; return 0;
@ -1279,7 +1299,7 @@ namespace WebSocketSharp {
for (long i = 0; i < count; i++) for (long i = 0; i < count; i++)
{ {
tmpLen = stream.Read(buffer, 0, _fragmentLen); tmpLen = stream.Read(buffer, 0, _fragmentLen);
if (send(Fin.MORE, Opcode.CONT, buffer)) if (send(Fin.MORE, Opcode.CONT, buffer, compressed))
readLen += tmpLen; readLen += tmpLen;
else else
return readLen; return readLen;
@ -1289,7 +1309,7 @@ namespace WebSocketSharp {
if (rem != 0) if (rem != 0)
buffer = new byte[rem]; buffer = new byte[rem];
tmpLen = stream.Read(buffer, 0, buffer.Length); tmpLen = stream.Read(buffer, 0, buffer.Length);
if (send(Fin.FINAL, Opcode.CONT, buffer)) if (send(Fin.FINAL, Opcode.CONT, buffer, compressed))
readLen += tmpLen; readLen += tmpLen;
return readLen; return readLen;

View File

@ -178,6 +178,19 @@ namespace WebSocketSharp {
} }
} }
internal bool PerMessageCompressed {
get {
return IsData && IsCompressed;
}
set {
if (value && !IsData)
return;
Rsv1 = value ? Rsv.ON : Rsv.OFF;
}
}
#endregion #endregion
#region Public Properties #region Public Properties

View File

@ -1,6 +1,6 @@
<Type Name="WebSocket" FullName="WebSocketSharp.WebSocket"> <Type Name="WebSocket" FullName="WebSocketSharp.WebSocket">
<TypeSignature Language="C#" Value="public class WebSocket : IDisposable" /> <TypeSignature Language="C#" Value="public class WebSocket : IDisposable" />
<TypeSignature Language="ILAsm" Value=".class public auto ansi WebSocket extends System.Object implements class System.IDisposable" /> <TypeSignature Language="ILAsm" Value=".class public auto ansi beforefieldinit WebSocket extends System.Object implements class System.IDisposable" />
<AssemblyInfo> <AssemblyInfo>
<AssemblyName>websocket-sharp</AssemblyName> <AssemblyName>websocket-sharp</AssemblyName>
</AssemblyInfo> </AssemblyInfo>

View File

@ -1,6 +1,6 @@
<Overview> <Overview>
<Assemblies> <Assemblies>
<Assembly Name="websocket-sharp" Version="1.0.2.32570"> <Assembly Name="websocket-sharp" Version="1.0.2.4444">
<AssemblyPublicKey>[00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 00 24 00 00 52 53 41 31 00 04 00 00 11 00 00 00 29 17 fb 89 fe c3 91 f7 2b cb 8b e2 61 d2 3f 05 93 6d 65 a8 9e 63 72 a6 f5 d5 2c f2 9d 20 fa 0b c0 70 6a f6 88 7e 8b 90 3f 39 f5 76 c8 48 e0 bb 7b b2 7b ed d3 10 a7 1a 0f 70 98 0f 7f f4 4b 53 09 d2 a5 ef 36 c3 56 b4 aa f0 91 72 63 25 07 89 e0 93 3e 3f 2e f2 b9 73 0e 12 15 5d 43 56 c3 f4 70 a5 89 fe f7 f6 ac 3e 77 c2 d8 d0 84 91 f4 0c d1 f3 8e dc c3 c3 b8 38 3d 0c bf 17 de 20 78 c1 ]</AssemblyPublicKey> <AssemblyPublicKey>[00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 00 24 00 00 52 53 41 31 00 04 00 00 11 00 00 00 29 17 fb 89 fe c3 91 f7 2b cb 8b e2 61 d2 3f 05 93 6d 65 a8 9e 63 72 a6 f5 d5 2c f2 9d 20 fa 0b c0 70 6a f6 88 7e 8b 90 3f 39 f5 76 c8 48 e0 bb 7b b2 7b ed d3 10 a7 1a 0f 70 98 0f 7f f4 4b 53 09 d2 a5 ef 36 c3 56 b4 aa f0 91 72 63 25 07 89 e0 93 3e 3f 2e f2 b9 73 0e 12 15 5d 43 56 c3 f4 70 a5 89 fe f7 f6 ac 3e 77 c2 d8 d0 84 91 f4 0c d1 f3 8e dc c3 c3 b8 38 3d 0c bf 17 de 20 78 c1 ]</AssemblyPublicKey>
<Attributes> <Attributes>
<Attribute> <Attribute>

Binary file not shown.