Refactored WebSocket.cs

This commit is contained in:
sta 2014-01-13 14:32:39 +09:00
parent 82015cdb1a
commit 7c610d06ff
3 changed files with 209 additions and 225 deletions

View File

@ -193,13 +193,6 @@ namespace WebSocketSharp
} }
} }
internal static string CheckIfCanClose (this WebSocketState state)
{
return state == WebSocketState.CLOSING || state == WebSocketState.CLOSED
? "While closing the WebSocket connection, or already closed."
: null;
}
internal static string CheckIfCanRead (this Stream stream) internal static string CheckIfCanRead (this Stream stream)
{ {
return stream == null return stream == null
@ -209,11 +202,24 @@ namespace WebSocketSharp
: null; : null;
} }
internal static string CheckIfClosable (this WebSocketState state)
{
return state == WebSocketState.CLOSING
? "While closing the WebSocket connection."
: state == WebSocketState.CLOSED
? "The WebSocket connection has already been closed."
: null;
}
internal static string CheckIfOpen (this WebSocketState state) internal static string CheckIfOpen (this WebSocketState state)
{ {
return state != WebSocketState.OPEN return state == WebSocketState.CONNECTING
? "A WebSocket connection isn't established or has been closed." ? "A WebSocket connection isn't established."
: null; : state == WebSocketState.CLOSING
? "While closing the WebSocket connection."
: state == WebSocketState.CLOSED
? "The WebSocket connection has already been closed."
: null;
} }
internal static string CheckIfStarted (this ServerState state) internal static string CheckIfStarted (this ServerState state)

View File

@ -484,6 +484,74 @@ namespace WebSocketSharp
#region Private Methods #region Private Methods
private bool acceptCloseFrame (WsFrame frame)
{
var payload = frame.PayloadData;
close (payload, !payload.ContainsReservedCloseStatusCode, false);
return false;
}
private bool acceptDataFrame (WsFrame frame)
{
var args = frame.IsCompressed
? new MessageEventArgs (
frame.Opcode,
frame.PayloadData.ApplicationData.Decompress (_compression))
: new MessageEventArgs (frame.Opcode, frame.PayloadData);
OnMessage.Emit (this, args);
return true;
}
private bool acceptFragmentedFrame (WsFrame frame)
{
return frame.IsContinuation // Not first fragment
? true
: acceptFragments (frame);
}
private bool acceptFragments (WsFrame first)
{
using (var concatenated = new MemoryStream ()) {
concatenated.WriteBytes (first.PayloadData.ApplicationData);
if (!concatenateFragmentsInto (concatenated))
return false;
byte [] data;
if (_compression != CompressionMethod.NONE) {
data = concatenated.DecompressToArray (_compression);
}
else {
concatenated.Close ();
data = concatenated.ToArray ();
}
OnMessage.Emit (this, new MessageEventArgs (first.Opcode, data));
return true;
}
}
private bool acceptFrame (WsFrame frame)
{
return frame.IsCompressed && _compression == CompressionMethod.NONE
? acceptUnsupportedFrame (
frame,
CloseStatusCode.INCORRECT_DATA,
"A compressed data has been received without available decompression method.")
: frame.IsFragmented
? acceptFragmentedFrame (frame)
: frame.IsData
? acceptDataFrame (frame)
: frame.IsPing
? acceptPingFrame (frame)
: frame.IsPong
? acceptPongFrame (frame)
: frame.IsClose
? acceptCloseFrame (frame)
: acceptUnsupportedFrame (frame, CloseStatusCode.POLICY_VIOLATION, null);
}
// As server // As server
private bool acceptHandshake () private bool acceptHandshake ()
{ {
@ -516,6 +584,32 @@ namespace WebSocketSharp
return send (createHandshakeResponse ()); return send (createHandshakeResponse ());
} }
private bool acceptPingFrame (WsFrame frame)
{
var mask = _client ? Mask.MASK : Mask.UNMASK;
if (send (WsFrame.CreatePongFrame (mask, frame.PayloadData)))
_logger.Trace ("Returned a Pong.");
return true;
}
private bool acceptPongFrame (WsFrame frame)
{
_receivePong.Set ();
_logger.Trace ("Received a Pong.");
return true;
}
private bool acceptUnsupportedFrame (
WsFrame frame, CloseStatusCode code, string reason)
{
_logger.Debug ("Unsupported frame:\n" + frame.PrintToString (false));
processException (new WebSocketException (code, reason), null);
return false;
}
// As server // As server
private string checkIfValidHandshakeRequest (WebSocketContext context) private string checkIfValidHandshakeRequest (WebSocketContext context)
{ {
@ -566,10 +660,10 @@ namespace WebSocketSharp
private void close (PayloadData payload, bool send, bool wait) private void close (PayloadData payload, bool send, bool wait)
{ {
lock (_forConn) { lock (_forConn) {
var msg = _readyState.CheckIfCanClose (); if (_readyState == WebSocketState.CLOSING ||
if (msg != null) { _readyState == WebSocketState.CLOSED) {
_logger.Info (String.Format ("{0}\nstate: {1}", msg, _readyState)); _logger.Info (
"Closing the WebSocket connection has already been done.");
return; return;
} }
@ -664,47 +758,39 @@ namespace WebSocketSharp
private bool concatenateFragmentsInto (Stream dest) private bool concatenateFragmentsInto (Stream dest)
{ {
while (true) var frame = _stream.ReadFrame ();
{
var frame = _stream.ReadFrame ();
// MORE & CONT // MORE & CONT
if (!frame.IsFinal && frame.IsContinuation) if (!frame.IsFinal && frame.IsContinuation) {
{ dest.WriteBytes (frame.PayloadData.ApplicationData);
dest.WriteBytes (frame.PayloadData.ApplicationData); return concatenateFragmentsInto (dest);
continue;
}
// FINAL & CONT
if (frame.IsFinal && frame.IsContinuation)
{
dest.WriteBytes (frame.PayloadData.ApplicationData);
break;
}
// FINAL & PING
if (frame.IsFinal && frame.IsPing)
{
processPingFrame (frame);
continue;
}
// FINAL & PONG
if (frame.IsFinal && frame.IsPong)
{
processPongFrame ();
continue;
}
// FINAL & CLOSE
if (frame.IsFinal && frame.IsClose)
return processCloseFrame (frame);
// ?
return processUnsupportedFrame (frame, CloseStatusCode.INCORRECT_DATA, null);
} }
return true; // FINAL & CONT
if (frame.IsFinal && frame.IsContinuation) {
dest.WriteBytes (frame.PayloadData.ApplicationData);
return true;
}
// FINAL & PING
if (frame.IsFinal && frame.IsPing) {
acceptPingFrame (frame);
return concatenateFragmentsInto (dest);
}
// FINAL & PONG
if (frame.IsFinal && frame.IsPong) {
acceptPongFrame (frame);
return concatenateFragmentsInto (dest);
}
// FINAL & CLOSE
if (frame.IsFinal && frame.IsClose)
return acceptCloseFrame (frame);
// ?
return acceptUnsupportedFrame (
frame, CloseStatusCode.INCORRECT_DATA, null);
} }
// As client // As client
@ -869,7 +955,8 @@ namespace WebSocketSharp
{ {
try { try {
OnOpen.Emit (this, EventArgs.Empty); OnOpen.Emit (this, EventArgs.Empty);
startReceiving (); if (_readyState == WebSocketState.OPEN)
startReceiving ();
} }
catch (Exception ex) { catch (Exception ex) {
processException ( processException (
@ -877,25 +964,6 @@ namespace WebSocketSharp
} }
} }
private bool processCloseFrame (WsFrame frame)
{
var payload = frame.PayloadData;
close (payload, !payload.ContainsReservedCloseStatusCode, false);
return false;
}
private bool processDataFrame (WsFrame frame)
{
var args = frame.IsCompressed
? new MessageEventArgs (
frame.Opcode, frame.PayloadData.ApplicationData.Decompress (_compression))
: new MessageEventArgs (frame.Opcode, frame.PayloadData);
OnMessage.Emit (this, args);
return true;
}
private void processException (Exception exception, string reason) private void processException (Exception exception, string reason)
{ {
var code = CloseStatusCode.ABNORMAL; var code = CloseStatusCode.ABNORMAL;
@ -923,87 +991,17 @@ namespace WebSocketSharp
close (code, reason ?? code.GetMessage (), false); close (code, reason ?? code.GetMessage (), false);
} }
private bool processFragmentedFrame (WsFrame frame)
{
return frame.IsContinuation // Not first fragment
? true
: processFragments (frame);
}
private bool processFragments (WsFrame first)
{
using (var concatenated = new MemoryStream ())
{
concatenated.WriteBytes (first.PayloadData.ApplicationData);
if (!concatenateFragmentsInto (concatenated))
return false;
byte [] data;
if (_compression != CompressionMethod.NONE)
{
data = concatenated.DecompressToArray (_compression);
}
else
{
concatenated.Close ();
data = concatenated.ToArray ();
}
OnMessage.Emit (this, new MessageEventArgs (first.Opcode, data));
return true;
}
}
private bool processFrame (WsFrame frame)
{
return frame.IsCompressed && _compression == CompressionMethod.NONE
? processUnsupportedFrame (
frame,
CloseStatusCode.INCORRECT_DATA,
"A compressed data has been received without available decompression method.")
: frame.IsFragmented
? processFragmentedFrame (frame)
: frame.IsData
? processDataFrame (frame)
: frame.IsPing
? processPingFrame (frame)
: frame.IsPong
? processPongFrame ()
: frame.IsClose
? processCloseFrame (frame)
: processUnsupportedFrame (frame, CloseStatusCode.POLICY_VIOLATION, null);
}
private bool processPingFrame (WsFrame frame)
{
if (send (WsFrame.CreatePongFrame (_client ? Mask.MASK : Mask.UNMASK, frame.PayloadData)))
_logger.Trace ("Returned Pong.");
return true;
}
private bool processPongFrame ()
{
_receivePong.Set ();
_logger.Trace ("Received Pong.");
return true;
}
// As server // As server
private void processRequestedExtensions (string extensions) private void processRequestedExtensions (string extensions)
{ {
var comp = false; var comp = false;
var buffer = new List<string> (); var buffer = new List<string> ();
foreach (var e in extensions.SplitHeaderValue (',')) foreach (var e in extensions.SplitHeaderValue (',')) {
{
var extension = e.Trim (); var extension = e.Trim ();
var tmp = extension.RemovePrefix ("x-webkit-"); var tmp = extension.RemovePrefix ("x-webkit-");
if (!comp && tmp.IsCompressionExtension ()) if (!comp && tmp.IsCompressionExtension ()) {
{
var method = tmp.ToCompressionMethod (); var method = tmp.ToCompressionMethod ();
if (method != CompressionMethod.NONE) if (method != CompressionMethod.NONE) {
{
_compression = method; _compression = method;
comp = true; comp = true;
buffer.Add (extension); buffer.Add (extension);
@ -1020,10 +1018,8 @@ namespace WebSocketSharp
{ {
var comp = _compression != CompressionMethod.NONE ? true : false; var comp = _compression != CompressionMethod.NONE ? true : false;
var hasComp = false; var hasComp = false;
if (extensions != null && extensions.Length > 0) if (extensions != null && extensions.Length > 0) {
{ foreach (var e in extensions.SplitHeaderValue (',')) {
foreach (var e in extensions.SplitHeaderValue (','))
{
var extension = e.Trim (); var extension = e.Trim ();
if (comp && !hasComp && extension.Equals (_compression)) if (comp && !hasComp && extension.Equals (_compression))
hasComp = true; hasComp = true;
@ -1036,14 +1032,6 @@ namespace WebSocketSharp
_compression = CompressionMethod.NONE; _compression = CompressionMethod.NONE;
} }
private bool processUnsupportedFrame (WsFrame frame, CloseStatusCode code, string reason)
{
_logger.Debug ("Unsupported frame:\n" + frame.PrintToString (false));
processException (new WebSocketException (code, reason), null);
return false;
}
// As client // As client
private HandshakeResponse receiveHandshakeResponse () private HandshakeResponse receiveHandshakeResponse ()
{ {
@ -1055,15 +1043,17 @@ namespace WebSocketSharp
private bool send (byte [] frameAsBytes) private bool send (byte [] frameAsBytes)
{ {
if (_readyState != WebSocketState.OPEN) { lock (_forConn) {
var msg = "A WebSocket connection isn't established or has been closed."; if (_readyState != WebSocketState.OPEN) {
_logger.Error (msg); var msg = "The WebSocket connection isn't available.";
error (msg); _logger.Error (msg);
error (msg);
return false; return false;
}
return _stream.Write (frameAsBytes);
} }
return _stream.Write (frameAsBytes);
} }
// As client // As client
@ -1087,15 +1077,17 @@ namespace WebSocketSharp
private bool send (WsFrame frame) private bool send (WsFrame frame)
{ {
if (_readyState != WebSocketState.OPEN) { lock (_forConn) {
var msg = "A WebSocket connection isn't established or has been closed."; if (_readyState != WebSocketState.OPEN) {
_logger.Error (msg); var msg = "The WebSocket connection isn't available.";
error (msg); _logger.Error (msg);
error (msg);
return false; return false;
}
return _stream.Write (frame.ToByteArray ());
} }
return _stream.Write (frame.ToByteArray ());
} }
private bool send (Opcode opcode, byte [] data) private bool send (Opcode opcode, byte [] data)
@ -1304,21 +1296,19 @@ namespace WebSocketSharp
var host = _uri.DnsSafeHost; var host = _uri.DnsSafeHost;
var port = _uri.Port; var port = _uri.Port;
_tcpClient = new TcpClient (host, port); _tcpClient = new TcpClient (host, port);
_stream = WsStream.CreateClientStream (_tcpClient, _secure, host, _certValidationCallback); _stream = WsStream.CreateClientStream (
_tcpClient, _secure, host, _certValidationCallback);
} }
private void startReceiving () private void startReceiving ()
{ {
if (_readyState != WebSocketState.OPEN)
return;
_exitReceiving = new AutoResetEvent (false); _exitReceiving = new AutoResetEvent (false);
_receivePong = new AutoResetEvent (false); _receivePong = new AutoResetEvent (false);
Action receive = null; Action receive = null;
receive = () => _stream.ReadFrameAsync ( receive = () => _stream.ReadFrameAsync (
frame => { frame => {
if (processFrame (frame)) if (acceptFrame (frame))
receive (); receive ();
else else
_exitReceiving.Set (); _exitReceiving.Set ();
@ -1382,10 +1372,10 @@ namespace WebSocketSharp
CloseEventArgs args, byte [] frameAsBytes, int waitTimeOut) CloseEventArgs args, byte [] frameAsBytes, int waitTimeOut)
{ {
lock (_forConn) { lock (_forConn) {
var msg = _readyState.CheckIfCanClose (); if (_readyState == WebSocketState.CLOSING ||
if (msg != null) { _readyState == WebSocketState.CLOSED) {
_logger.Info (String.Format ("{0}\nstate: {1}", msg, _readyState)); _logger.Info (
"Closing the WebSocket connection has already been done.");
return; return;
} }
@ -1446,14 +1436,13 @@ namespace WebSocketSharp
} }
// As server, used to broadcast // As server, used to broadcast
internal void Send (Opcode opcode, byte [] data, Dictionary<CompressionMethod, byte []> cache) internal void Send (
Opcode opcode, byte [] data, Dictionary<CompressionMethod, byte []> cache)
{ {
lock (_forSend) lock (_forSend) {
{
try { try {
byte [] cached; byte [] cached;
if (!cache.TryGetValue (_compression, out cached)) if (!cache.TryGetValue (_compression, out cached)) {
{
cached = WsFrame.CreateFrame ( cached = WsFrame.CreateFrame (
Fin.FINAL, Fin.FINAL,
opcode, opcode,
@ -1468,31 +1457,31 @@ namespace WebSocketSharp
} }
catch (Exception ex) { catch (Exception ex) {
_logger.Fatal (ex.ToString ()); _logger.Fatal (ex.ToString ());
error ("An exception has occurred."); error ("An exception has occurred while sending a data.");
} }
} }
} }
// As server, used to broadcast // As server, used to broadcast
internal void Send (Opcode opcode, Stream stream, Dictionary <CompressionMethod, Stream> cache) internal void Send (
Opcode opcode, Stream stream, Dictionary <CompressionMethod, Stream> cache)
{ {
lock (_forSend) lock (_forSend) {
{
try { try {
Stream cached; Stream cached;
if (!cache.TryGetValue (_compression, out cached)) if (!cache.TryGetValue (_compression, out cached)) {
{
cached = stream.Compress (_compression); cached = stream.Compress (_compression);
cache.Add (_compression, cached); cache.Add (_compression, cached);
} }
else else
cached.Position = 0; cached.Position = 0;
sendFragmented (opcode, cached, Mask.UNMASK, _compression != CompressionMethod.NONE); sendFragmented (
opcode, cached, Mask.UNMASK, _compression != CompressionMethod.NONE);
} }
catch (Exception ex) { catch (Exception ex) {
_logger.Fatal (ex.ToString ()); _logger.Fatal (ex.ToString ());
error ("An exception has occurred."); error ("An exception has occurred while sending a data.");
} }
} }
} }
@ -1506,9 +1495,9 @@ namespace WebSocketSharp
/// </summary> /// </summary>
public void Close () public void Close ()
{ {
var msg = _readyState.CheckIfCanClose (); var msg = _readyState.CheckIfClosable ();
if (msg != null) { if (msg != null) {
_logger.Error (String.Format ("{0}\nstate: {1}", msg, _readyState)); _logger.Error (msg);
error (msg); error (msg);
return; return;
@ -1565,14 +1554,13 @@ namespace WebSocketSharp
public void Close (ushort code, string reason) public void Close (ushort code, string reason)
{ {
byte [] data = null; byte [] data = null;
var msg = _readyState.CheckIfCanClose () ?? var msg = _readyState.CheckIfClosable () ??
code.CheckIfValidCloseStatusCode () ?? code.CheckIfValidCloseStatusCode () ??
(data = code.Append (reason)).CheckIfValidCloseData (); (data = code.Append (reason)).CheckIfValidCloseData ();
if (msg != null) { if (msg != null) {
_logger.Error ( _logger.Error (
String.Format ( String.Format ("{0}\ncode: {1} reason: {2}", msg, code, reason));
"{0}\nstate: {1} code: {2} reason: {3}", msg, _readyState, code, reason));
error (msg); error (msg);
return; return;
@ -1600,13 +1588,12 @@ namespace WebSocketSharp
public void Close (CloseStatusCode code, string reason) public void Close (CloseStatusCode code, string reason)
{ {
byte [] data = null; byte [] data = null;
var msg = _readyState.CheckIfCanClose () ?? var msg = _readyState.CheckIfClosable () ??
(data = ((ushort) code).Append (reason)).CheckIfValidCloseData (); (data = ((ushort) code).Append (reason)).CheckIfValidCloseData ();
if (msg != null) { if (msg != null) {
_logger.Error ( _logger.Error (
String.Format ( String.Format ("{0}\ncode: {1} reason: {2}", msg, code, reason));
"{0}\nstate: {1} reason: {2}", msg, _readyState, reason));
error (msg); error (msg);
return; return;
@ -1625,9 +1612,9 @@ namespace WebSocketSharp
/// </remarks> /// </remarks>
public void CloseAsync () public void CloseAsync ()
{ {
var msg = _readyState.CheckIfCanClose (); var msg = _readyState.CheckIfClosable ();
if (msg != null) { if (msg != null) {
_logger.Error (String.Format ("{0}\nstate: {1}", msg, _readyState)); _logger.Error (msg);
error (msg); error (msg);
return; return;
@ -1698,14 +1685,13 @@ namespace WebSocketSharp
public void CloseAsync (ushort code, string reason) public void CloseAsync (ushort code, string reason)
{ {
byte [] data = null; byte [] data = null;
var msg = _readyState.CheckIfCanClose () ?? var msg = _readyState.CheckIfClosable () ??
code.CheckIfValidCloseStatusCode () ?? code.CheckIfValidCloseStatusCode () ??
(data = code.Append (reason)).CheckIfValidCloseData (); (data = code.Append (reason)).CheckIfValidCloseData ();
if (msg != null) { if (msg != null) {
_logger.Error ( _logger.Error (
String.Format ( String.Format ("{0}\ncode: {1} reason: {2}", msg, code, reason));
"{0}\nstate: {1} code: {2} reason: {3}", msg, _readyState, code, reason));
error (msg); error (msg);
return; return;
@ -1739,13 +1725,12 @@ namespace WebSocketSharp
public void CloseAsync (CloseStatusCode code, string reason) public void CloseAsync (CloseStatusCode code, string reason)
{ {
byte [] data = null; byte [] data = null;
var msg = _readyState.CheckIfCanClose () ?? var msg = _readyState.CheckIfClosable () ??
(data = ((ushort) code).Append (reason)).CheckIfValidCloseData (); (data = ((ushort) code).Append (reason)).CheckIfValidCloseData ();
if (msg != null) { if (msg != null) {
_logger.Error ( _logger.Error (
String.Format ( String.Format ("{0}\ncode: {1} reason: {2}", msg, code, reason));
"{0}\nstate: {1} reason: {2}", msg, _readyState, reason));
error (msg); error (msg);
return; return;
@ -1850,8 +1835,7 @@ namespace WebSocketSharp
var data = Encoding.UTF8.GetBytes (message); var data = Encoding.UTF8.GetBytes (message);
var msg = data.CheckIfValidPingData (); var msg = data.CheckIfValidPingData ();
if (msg != null) if (msg != null) {
{
_logger.Error (msg); _logger.Error (msg);
error (msg); error (msg);

View File

@ -104,12 +104,10 @@ namespace WebSocketSharp
TcpClient client, TcpClient client,
bool secure, bool secure,
string host, string host,
System.Net.Security.RemoteCertificateValidationCallback validationCallback System.Net.Security.RemoteCertificateValidationCallback validationCallback)
)
{ {
var netStream = client.GetStream (); var netStream = client.GetStream ();
if (secure) if (secure) {
{
if (validationCallback == null) if (validationCallback == null)
validationCallback = (sender, certificate, chain, sslPolicyErrors) => true; validationCallback = (sender, certificate, chain, sslPolicyErrors) => true;
@ -144,8 +142,7 @@ namespace WebSocketSharp
internal bool Write (byte [] data) internal bool Write (byte [] data)
{ {
lock (_forWrite) lock (_forWrite) {
{
try { try {
_innerStream.Write (data, 0, data.Length); _innerStream.Write (data, 0, data.Length);
return true; return true;
@ -182,16 +179,15 @@ namespace WebSocketSharp
public string [] ReadHandshake () public string [] ReadHandshake ()
{ {
var read = false;
var exception = false; var exception = false;
var read = false;
var timeout = false;
var buffer = new List<byte> (); var buffer = new List<byte> ();
Action<int> add = i => buffer.Add ((byte) i); Action<int> add = i => buffer.Add ((byte) i);
var timeout = false;
var timer = new Timer ( var timer = new Timer (
state => state => {
{
timeout = true; timeout = true;
_innerStream.Close (); _innerStream.Close ();
}, },
@ -200,13 +196,11 @@ namespace WebSocketSharp
-1); -1);
try { try {
while (buffer.Count < _handshakeLimitLen) while (buffer.Count < _handshakeLimitLen) {
{
if (_innerStream.ReadByte ().EqualsWith ('\r', add) && if (_innerStream.ReadByte ().EqualsWith ('\r', add) &&
_innerStream.ReadByte ().EqualsWith ('\n', add) && _innerStream.ReadByte ().EqualsWith ('\n', add) &&
_innerStream.ReadByte ().EqualsWith ('\r', add) && _innerStream.ReadByte ().EqualsWith ('\r', add) &&
_innerStream.ReadByte ().EqualsWith ('\n', add)) _innerStream.ReadByte ().EqualsWith ('\n', add)) {
{
read = true; read = true;
break; break;
} }