diff --git a/websocket-sharp/Server/WebSocketSessionManager.cs b/websocket-sharp/Server/WebSocketSessionManager.cs
index f7144b0ce..9c23f3955 100644
--- a/websocket-sharp/Server/WebSocketSessionManager.cs
+++ b/websocket-sharp/Server/WebSocketSessionManager.cs
@@ -33,1663 +33,1807 @@
using System.Linq;
using System.Text;
using System.Threading;
+using System.Threading.Tasks;
using System.Timers;
namespace WebSocketSharp.Server
{
- ///
- /// Provides the management function for the sessions in a WebSocket service.
- ///
- ///
- /// This class manages the sessions in a WebSocket service provided by
- /// the or .
- ///
- public class WebSocketSessionManager
- {
- #region Private Fields
-
- private volatile bool _clean;
- private object _forSweep;
- private Logger _log;
- private Dictionary _sessions;
- private volatile ServerState _state;
- private volatile bool _sweeping;
- private System.Timers.Timer _sweepTimer;
- private object _sync;
- private TimeSpan _waitTime;
-
- #endregion
-
- #region Internal Constructors
-
- internal WebSocketSessionManager (Logger log)
- {
- _log = log;
-
- _clean = true;
- _forSweep = new object ();
- _sessions = new Dictionary ();
- _state = ServerState.Ready;
- _sync = ((ICollection) _sessions).SyncRoot;
- _waitTime = TimeSpan.FromSeconds (1);
-
- setSweepTimer (60000);
- }
-
- #endregion
-
- #region Internal Properties
-
- internal ServerState State {
- get {
- return _state;
- }
- }
-
- #endregion
-
- #region Public Properties
-
///
- /// Gets the IDs for the active sessions in the WebSocket service.
+ /// Provides the management function for the sessions in a WebSocket service.
///
- ///
- ///
- /// An IEnumerable<string> instance.
- ///
- ///
- /// It provides an enumerator which supports the iteration over
- /// the collection of the IDs for the active sessions.
- ///
- ///
- public IEnumerable ActiveIDs {
- get {
- foreach (var res in broadping (WebSocketFrame.EmptyPingBytes)) {
- if (res.Value)
- yield return res.Key;
+ ///
+ /// This class manages the sessions in a WebSocket service provided by
+ /// the or .
+ ///
+ public class WebSocketSessionManager
+ {
+ #region Private Fields
+
+ private volatile bool _clean;
+ private object _forSweep;
+ private Logger _log;
+ private Dictionary _sessions;
+ private volatile ServerState _state;
+ private volatile bool _sweeping;
+ private System.Timers.Timer _sweepTimer;
+ private object _sync;
+ private TimeSpan _waitTime;
+#if __PING_ASYNC
+ private System.Timers.Timer _broadTimer;
+#endif
+ #endregion
+
+ #region Internal Constructors
+
+ internal WebSocketSessionManager(Logger log)
+ {
+ _log = log;
+
+ _clean = true;
+ _forSweep = new object();
+ _sessions = new Dictionary();
+ _state = ServerState.Ready;
+ _sync = ((ICollection)_sessions).SyncRoot;
+ _waitTime = TimeSpan.FromSeconds(1);
+
+ setSweepTimer(60000);
+#if __PING_ASYNC
+ _broadTimer = new System.Timers.Timer(10000);
+ _broadTimer.Elapsed += (sender, e) => broadpingAsync(WebSocketFrame.EmptyPingBytes);
+ _broadTimer.Enabled = true;
+ _broadTimer.Start();
+#endif
}
- }
- }
- ///
- /// Gets the number of the sessions in the WebSocket service.
- ///
- ///
- /// An that represents the number of the sessions.
- ///
- public int Count {
- get {
- lock (_sync)
- return _sessions.Count;
- }
- }
+ #endregion
- ///
- /// Gets the IDs for the sessions in the WebSocket service.
- ///
- ///
- ///
- /// An IEnumerable<string> instance.
- ///
- ///
- /// It provides an enumerator which supports the iteration over
- /// the collection of the IDs for the sessions.
- ///
- ///
- public IEnumerable IDs {
- get {
- if (_state != ServerState.Start)
- return Enumerable.Empty ();
-
- lock (_sync) {
- if (_state != ServerState.Start)
- return Enumerable.Empty ();
-
- return _sessions.Keys.ToList ();
- }
- }
- }
+ #region Internal Properties
- ///
- /// Gets the IDs for the inactive sessions in the WebSocket service.
- ///
- ///
- ///
- /// An IEnumerable<string> instance.
- ///
- ///
- /// It provides an enumerator which supports the iteration over
- /// the collection of the IDs for the inactive sessions.
- ///
- ///
- public IEnumerable InactiveIDs {
- get {
- foreach (var res in broadping (WebSocketFrame.EmptyPingBytes)) {
- if (!res.Value)
- yield return res.Key;
+ internal ServerState State
+ {
+ get
+ {
+ return _state;
+ }
}
- }
- }
- ///
- /// Gets the session instance with .
- ///
- ///
- ///
- /// A instance or
- /// if not found.
- ///
- ///
- /// The session instance provides the function to access the information
- /// in the session.
- ///
- ///
- ///
- /// A that represents the ID of the session to find.
- ///
- ///
- /// is .
- ///
- ///
- /// is an empty string.
- ///
- public IWebSocketSession this[string id] {
- get {
- if (id == null)
- throw new ArgumentNullException ("id");
-
- if (id.Length == 0)
- throw new ArgumentException ("An empty string.", "id");
-
- IWebSocketSession session;
- tryGetSession (id, out session);
-
- return session;
- }
- }
-
- ///
- /// Gets or sets a value indicating whether the inactive sessions in
- /// the WebSocket service are cleaned up periodically.
- ///
- ///
- /// The set operation does nothing if the service has already started or
- /// it is shutting down.
- ///
- ///
- /// true if the inactive sessions are cleaned up every 60 seconds;
- /// otherwise, false.
- ///
- public bool KeepClean {
- get {
- return _clean;
- }
-
- set {
- string msg;
- if (!canSet (out msg)) {
- _log.Warn (msg);
- return;
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets the IDs for the active sessions in the WebSocket service.
+ ///
+ ///
+ ///
+ /// An IEnumerable<string> instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the IDs for the active sessions.
+ ///
+ ///
+#if __PING_ASYNC
+ public IEnumerable ActiveIDs
+ {
+ get
+ {
+ foreach (var res in Sessions)
+ {
+ if (res.Context.WebSocket.IsAlive)
+ yield return res.ID;
+ }
+ }
}
-
- lock (_sync) {
- if (!canSet (out msg)) {
- _log.Warn (msg);
- return;
- }
-
- _clean = value;
+#else
+ public IEnumerable ActiveIDs
+ {
+ get
+ {
+ foreach (var res in broadping(WebSocketFrame.EmptyPingBytes))
+ {
+ if (res.Value)
+ yield return res.Key;
+ }
+ }
}
- }
- }
-
- ///
- /// Gets the session instances in the WebSocket service.
- ///
- ///
- ///
- /// An IEnumerable<IWebSocketSession> instance.
- ///
- ///
- /// It provides an enumerator which supports the iteration over
- /// the collection of the session instances.
- ///
- ///
- public IEnumerable Sessions {
- get {
- if (_state != ServerState.Start)
- return Enumerable.Empty ();
-
- lock (_sync) {
- if (_state != ServerState.Start)
- return Enumerable.Empty ();
-
- return _sessions.Values.ToList ();
+#endif
+ ///
+ /// Gets the number of the sessions in the WebSocket service.
+ ///
+ ///
+ /// An that represents the number of the sessions.
+ ///
+ public int Count
+ {
+ get
+ {
+ lock (_sync)
+ return _sessions.Count;
+ }
}
- }
- }
- ///
- /// Gets or sets the time to wait for the response to the WebSocket Ping or
- /// Close.
- ///
- ///
- /// The set operation does nothing if the service has already started or
- /// it is shutting down.
- ///
- ///
- /// A to wait for the response.
- ///
- ///
- /// The value specified for a set operation is zero or less.
- ///
- public TimeSpan WaitTime {
- get {
- return _waitTime;
- }
-
- set {
- if (value <= TimeSpan.Zero)
- throw new ArgumentOutOfRangeException ("value", "Zero or less.");
-
- string msg;
- if (!canSet (out msg)) {
- _log.Warn (msg);
- return;
+ ///
+ /// Gets the IDs for the sessions in the WebSocket service.
+ ///
+ ///
+ ///
+ /// An IEnumerable<string> instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the IDs for the sessions.
+ ///
+ ///
+ public IEnumerable IDs
+ {
+ get
+ {
+ if (_state != ServerState.Start)
+ return Enumerable.Empty();
+
+ lock (_sync)
+ {
+ if (_state != ServerState.Start)
+ return Enumerable.Empty();
+
+ return _sessions.Keys.ToList();
+ }
+ }
}
- lock (_sync) {
- if (!canSet (out msg)) {
- _log.Warn (msg);
- return;
- }
-
- _waitTime = value;
+ ///
+ /// Gets the IDs for the inactive sessions in the WebSocket service.
+ ///
+ ///
+ ///
+ /// An IEnumerable<string> instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the IDs for the inactive sessions.
+ ///
+ ///
+#if __PING_ASYNC
+ public IEnumerable InactiveIDs
+ {
+ get
+ {
+ foreach (var res in Sessions)
+ {
+ if (!res.Context.WebSocket.IsAlive)
+ yield return res.ID;
+ }
+ }
}
- }
- }
-
- #endregion
-
- #region Private Methods
-
- private void broadcast (Opcode opcode, byte[] data, Action completed)
- {
- var cache = new Dictionary ();
-
- try {
- foreach (var session in Sessions) {
- if (_state != ServerState.Start) {
- _log.Error ("The service is shutting down.");
- break;
- }
-
- session.Context.WebSocket.Send (opcode, data, cache);
+#else
+ public IEnumerable InactiveIDs
+ {
+ get
+ {
+ foreach (var res in broadping(WebSocketFrame.EmptyPingBytes))
+ {
+ if (!res.Value)
+ yield return res.Key;
+ }
+ }
}
-
- if (completed != null)
- completed ();
- }
- catch (Exception ex) {
- _log.Error (ex.Message);
- _log.Debug (ex.ToString ());
- }
- finally {
- cache.Clear ();
- }
- }
-
- private void broadcast (Opcode opcode, Stream stream, Action completed)
- {
- var cache = new Dictionary ();
-
- try {
- foreach (var session in Sessions) {
- if (_state != ServerState.Start) {
- _log.Error ("The service is shutting down.");
- break;
- }
-
- session.Context.WebSocket.Send (opcode, stream, cache);
+#endif
+ ///
+ /// Gets the session instance with .
+ ///
+ ///
+ ///
+ /// A instance or
+ /// if not found.
+ ///
+ ///
+ /// The session instance provides the function to access the information
+ /// in the session.
+ ///
+ ///
+ ///
+ /// A that represents the ID of the session to find.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is an empty string.
+ ///
+ public IWebSocketSession this[string id]
+ {
+ get
+ {
+ if (id == null)
+ // throw new ArgumentNullException("id");
+ return null;
+
+ if (id.Length == 0)
+ throw new ArgumentException("An empty string.", "id");
+
+ IWebSocketSession session;
+ tryGetSession(id, out session);
+
+ return session;
+ }
}
- if (completed != null)
- completed ();
- }
- catch (Exception ex) {
- _log.Error (ex.Message);
- _log.Debug (ex.ToString ());
- }
- finally {
- foreach (var cached in cache.Values)
- cached.Dispose ();
-
- cache.Clear ();
- }
- }
-
- private void broadcastAsync (Opcode opcode, byte[] data, Action completed)
- {
- ThreadPool.QueueUserWorkItem (
- state => broadcast (opcode, data, completed)
- );
- }
-
- private void broadcastAsync (Opcode opcode, Stream stream, Action completed)
- {
- ThreadPool.QueueUserWorkItem (
- state => broadcast (opcode, stream, completed)
- );
- }
-
- private Dictionary broadping (byte[] frameAsBytes)
- {
- var ret = new Dictionary ();
-
- foreach (var session in Sessions) {
- if (_state != ServerState.Start) {
- _log.Error ("The service is shutting down.");
- break;
+ ///
+ /// Gets or sets a value indicating whether the inactive sessions in
+ /// the WebSocket service are cleaned up periodically.
+ ///
+ ///
+ /// The set operation does nothing if the service has already started or
+ /// it is shutting down.
+ ///
+ ///
+ /// true if the inactive sessions are cleaned up every 60 seconds;
+ /// otherwise, false.
+ ///
+ public bool KeepClean
+ {
+ get
+ {
+ return _clean;
+ }
+
+ set
+ {
+ string msg;
+ if (!canSet(out msg))
+ {
+ _log.Warn(msg);
+ return;
+ }
+
+ lock (_sync)
+ {
+ if (!canSet(out msg))
+ {
+ _log.Warn(msg);
+ return;
+ }
+
+ _clean = value;
+ }
+ }
}
- var res = session.Context.WebSocket.Ping (frameAsBytes, _waitTime);
- ret.Add (session.ID, res);
- }
-
- return ret;
- }
-
- private bool canSet (out string message)
- {
- message = null;
-
- if (_state == ServerState.Start) {
- message = "The service has already started.";
- return false;
- }
-
- if (_state == ServerState.ShuttingDown) {
- message = "The service is shutting down.";
- return false;
- }
-
- return true;
- }
-
- private static string createID ()
- {
- return Guid.NewGuid ().ToString ("N");
- }
-
- private void setSweepTimer (double interval)
- {
- _sweepTimer = new System.Timers.Timer (interval);
- _sweepTimer.Elapsed += (sender, e) => Sweep ();
- }
-
- private void stop (PayloadData payloadData, bool send)
- {
- var bytes = send
- ? WebSocketFrame.CreateCloseFrame (payloadData, false).ToArray ()
- : null;
-
- lock (_sync) {
- _state = ServerState.ShuttingDown;
-
- _sweepTimer.Enabled = false;
- foreach (var session in _sessions.Values.ToList ())
- session.Context.WebSocket.Close (payloadData, bytes);
-
- _state = ServerState.Stop;
- }
- }
-
- private bool tryGetSession (string id, out IWebSocketSession session)
- {
- session = null;
-
- if (_state != ServerState.Start)
- return false;
-
- lock (_sync) {
- if (_state != ServerState.Start)
- return false;
-
- return _sessions.TryGetValue (id, out session);
- }
- }
-
- #endregion
-
- #region Internal Methods
-
- internal string Add (IWebSocketSession session)
- {
- lock (_sync) {
- if (_state != ServerState.Start)
- return null;
-
- var id = createID ();
- _sessions.Add (id, session);
-
- return id;
- }
- }
-
- internal void Broadcast (
- Opcode opcode, byte[] data, Dictionary cache
- )
- {
- foreach (var session in Sessions) {
- if (_state != ServerState.Start) {
- _log.Error ("The service is shutting down.");
- break;
+ ///
+ /// Gets the session instances in the WebSocket service.
+ ///
+ ///
+ ///
+ /// An IEnumerable<IWebSocketSession> instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the session instances.
+ ///
+ ///
+ public IEnumerable Sessions
+ {
+ get
+ {
+ if (_state != ServerState.Start)
+ return Enumerable.Empty();
+
+ lock (_sync)
+ {
+ if (_state != ServerState.Start)
+ return Enumerable.Empty();
+
+ return _sessions.Values.ToList();
+ }
+ }
}
- session.Context.WebSocket.Send (opcode, data, cache);
- }
- }
-
- internal void Broadcast (
- Opcode opcode, Stream stream, Dictionary cache
- )
- {
- foreach (var session in Sessions) {
- if (_state != ServerState.Start) {
- _log.Error ("The service is shutting down.");
- break;
+ ///
+ /// Gets or sets the time to wait for the response to the WebSocket Ping or
+ /// Close.
+ ///
+ ///
+ /// The set operation does nothing if the service has already started or
+ /// it is shutting down.
+ ///
+ ///
+ /// A to wait for the response.
+ ///
+ ///
+ /// The value specified for a set operation is zero or less.
+ ///
+ public TimeSpan WaitTime
+ {
+ get
+ {
+ return _waitTime;
+ }
+
+ set
+ {
+ if (value <= TimeSpan.Zero)
+ throw new ArgumentOutOfRangeException("value", "Zero or less.");
+
+ string msg;
+ if (!canSet(out msg))
+ {
+ _log.Warn(msg);
+ return;
+ }
+
+ lock (_sync)
+ {
+ if (!canSet(out msg))
+ {
+ _log.Warn(msg);
+ return;
+ }
+
+ _waitTime = value;
+ }
+ }
}
- session.Context.WebSocket.Send (opcode, stream, cache);
- }
- }
-
- internal Dictionary Broadping (
- byte[] frameAsBytes, TimeSpan timeout
- )
- {
- var ret = new Dictionary ();
-
- foreach (var session in Sessions) {
- if (_state != ServerState.Start) {
- _log.Error ("The service is shutting down.");
- break;
+ #endregion
+
+ #region Private Methods
+
+ private void broadcast(Opcode opcode, byte[] data, Action completed)
+ {
+ var cache = new Dictionary();
+
+ try
+ {
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ _log.Error("The service is shutting down.");
+ break;
+ }
+
+ session.Context.WebSocket.Send(opcode, data, cache);
+ }
+
+ if (completed != null)
+ completed();
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex.Message);
+ _log.Debug(ex.ToString());
+ }
+ finally
+ {
+ cache.Clear();
+ }
}
- var res = session.Context.WebSocket.Ping (frameAsBytes, timeout);
- ret.Add (session.ID, res);
- }
+ private void broadcast(Opcode opcode, Stream stream, Action completed)
+ {
+ var cache = new Dictionary();
+
+ try
+ {
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ _log.Error("The service is shutting down.");
+ break;
+ }
+
+ session.Context.WebSocket.Send(opcode, stream, cache);
+ }
+
+ if (completed != null)
+ completed();
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex.Message);
+ _log.Debug(ex.ToString());
+ }
+ finally
+ {
+ foreach (var cached in cache.Values)
+ cached.Dispose();
+
+ cache.Clear();
+ }
+ }
- return ret;
- }
+ private void broadcastAsync(Opcode opcode, byte[] data, Action completed)
+ {
+ ThreadPool.QueueUserWorkItem(
+ state => broadcast(opcode, data, completed)
+ );
+ }
- internal bool Remove (string id)
- {
- lock (_sync)
- return _sessions.Remove (id);
- }
+ private void broadcastAsync(Opcode opcode, Stream stream, Action completed)
+ {
+ ThreadPool.QueueUserWorkItem(
+ state => broadcast(opcode, stream, completed)
+ );
+ }
+#if __PING_ASYNC
+ private void broadpingAsync(byte[] frameAsBytes)
+ {
+
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ _log.Error("The service is shutting down.");
+ break;
+ }
+
+ session.Context.WebSocket.PingAsync(frameAsBytes, _waitTime, null);
+ }
+ }
+#endif
+ private Dictionary broadping(byte[] frameAsBytes)
+ {
+ var ret = new Dictionary();
+
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ _log.Error("The service is shutting down.");
+ break;
+ }
+
+ var res = session.Context.WebSocket.Ping(frameAsBytes, _waitTime);
+ ret.Add(session.ID, res);
+ }
+
+ return ret;
+ }
+ private bool canSet(out string message)
+ {
+ message = null;
+
+ if (_state == ServerState.Start)
+ {
+ message = "The service has already started.";
+ return false;
+ }
+
+ if (_state == ServerState.ShuttingDown)
+ {
+ message = "The service is shutting down.";
+ return false;
+ }
+
+ return true;
+ }
- internal void Start ()
- {
- lock (_sync) {
- _sweepTimer.Enabled = _clean;
- _state = ServerState.Start;
- }
- }
+ private static string createID()
+ {
+ return Guid.NewGuid().ToString("N");
- internal void Stop (ushort code, string reason)
- {
- if (code == 1005) { // == no status
- stop (PayloadData.Empty, true);
- return;
- }
+ }
- stop (new PayloadData (code, reason), !code.IsReserved ());
- }
+ private void setSweepTimer(double interval)
+ {
+ _sweepTimer = new System.Timers.Timer(interval);
+ _sweepTimer.Elapsed += (sender, e) => Sweep();
+ }
- #endregion
+ private void stop(PayloadData payloadData, bool send)
+ {
+ var bytes = send
+ ? WebSocketFrame.CreateCloseFrame(payloadData, false).ToArray()
+ : null;
- #region Public Methods
+ lock (_sync)
+ {
+ _state = ServerState.ShuttingDown;
- ///
- /// Sends to every client in the WebSocket service.
- ///
- ///
- /// An array of that represents the binary data to send.
- ///
- ///
- /// The current state of the manager is not Start.
- ///
- ///
- /// is .
- ///
- public void Broadcast (byte[] data)
- {
- if (_state != ServerState.Start) {
- var msg = "The current state of the manager is not Start.";
- throw new InvalidOperationException (msg);
- }
-
- if (data == null)
- throw new ArgumentNullException ("data");
-
- if (data.LongLength <= WebSocket.FragmentLength)
- broadcast (Opcode.Binary, data, null);
- else
- broadcast (Opcode.Binary, new MemoryStream (data), null);
- }
+ _sweepTimer.Enabled = false;
+ foreach (var session in _sessions.Values.ToList())
+ session.Context.WebSocket.Close(payloadData, bytes);
- ///
- /// Sends to every client in the WebSocket service.
- ///
- ///
- /// A that represents the text data to send.
- ///
- ///
- /// The current state of the manager is not Start.
- ///
- ///
- /// is .
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- public void Broadcast (string data)
- {
- if (_state != ServerState.Start) {
- var msg = "The current state of the manager is not Start.";
- throw new InvalidOperationException (msg);
- }
-
- if (data == null)
- throw new ArgumentNullException ("data");
-
- byte[] bytes;
- if (!data.TryGetUTF8EncodedBytes (out bytes)) {
- var msg = "It could not be UTF-8-encoded.";
- throw new ArgumentException (msg, "data");
- }
-
- if (bytes.LongLength <= WebSocket.FragmentLength)
- broadcast (Opcode.Text, bytes, null);
- else
- broadcast (Opcode.Text, new MemoryStream (bytes), null);
- }
+ _state = ServerState.Stop;
+ }
+ }
- ///
- /// Sends the data from to every client in
- /// the WebSocket service.
- ///
- ///
- /// The data is sent as the binary data.
- ///
- ///
- /// A instance from which to read the data to send.
- ///
- ///
- /// An that specifies the number of bytes to send.
- ///
- ///
- /// The current state of the manager is not Start.
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// cannot be read.
- ///
- ///
- /// -or-
- ///
- ///
- /// is less than 1.
- ///
- ///
- /// -or-
- ///
- ///
- /// No data could be read from .
- ///
- ///
- public void Broadcast (Stream stream, int length)
- {
- if (_state != ServerState.Start) {
- var msg = "The current state of the manager is not Start.";
- throw new InvalidOperationException (msg);
- }
-
- if (stream == null)
- throw new ArgumentNullException ("stream");
-
- if (!stream.CanRead) {
- var msg = "It cannot be read.";
- throw new ArgumentException (msg, "stream");
- }
-
- if (length < 1) {
- var msg = "Less than 1.";
- throw new ArgumentException (msg, "length");
- }
-
- var bytes = stream.ReadBytes (length);
-
- var len = bytes.Length;
- if (len == 0) {
- var msg = "No data could be read from it.";
- throw new ArgumentException (msg, "stream");
- }
-
- if (len < length) {
- _log.Warn (
- String.Format (
- "Only {0} byte(s) of data could be read from the stream.",
- len
- )
- );
- }
-
- if (len <= WebSocket.FragmentLength)
- broadcast (Opcode.Binary, bytes, null);
- else
- broadcast (Opcode.Binary, new MemoryStream (bytes), null);
- }
+ private bool tryGetSession(string id, out IWebSocketSession session)
+ {
+ session = null;
- ///
- /// Sends asynchronously to every client in
- /// the WebSocket service.
- ///
- ///
- /// This method does not wait for the send to be complete.
- ///
- ///
- /// An array of that represents the binary data to send.
- ///
- ///
- ///
- /// An delegate or
- /// if not needed.
- ///
- ///
- /// The delegate invokes the method called when the send is complete.
- ///
- ///
- ///
- /// The current state of the manager is not Start.
- ///
- ///
- /// is .
- ///
- public void BroadcastAsync (byte[] data, Action completed)
- {
- if (_state != ServerState.Start) {
- var msg = "The current state of the manager is not Start.";
- throw new InvalidOperationException (msg);
- }
-
- if (data == null)
- throw new ArgumentNullException ("data");
-
- if (data.LongLength <= WebSocket.FragmentLength)
- broadcastAsync (Opcode.Binary, data, completed);
- else
- broadcastAsync (Opcode.Binary, new MemoryStream (data), completed);
- }
+ if (_state != ServerState.Start)
+ return false;
- ///
- /// Sends asynchronously to every client in
- /// the WebSocket service.
- ///
- ///
- /// This method does not wait for the send to be complete.
- ///
- ///
- /// A that represents the text data to send.
- ///
- ///
- ///
- /// An delegate or
- /// if not needed.
- ///
- ///
- /// The delegate invokes the method called when the send is complete.
- ///
- ///
- ///
- /// The current state of the manager is not Start.
- ///
- ///
- /// is .
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- public void BroadcastAsync (string data, Action completed)
- {
- if (_state != ServerState.Start) {
- var msg = "The current state of the manager is not Start.";
- throw new InvalidOperationException (msg);
- }
-
- if (data == null)
- throw new ArgumentNullException ("data");
-
- byte[] bytes;
- if (!data.TryGetUTF8EncodedBytes (out bytes)) {
- var msg = "It could not be UTF-8-encoded.";
- throw new ArgumentException (msg, "data");
- }
-
- if (bytes.LongLength <= WebSocket.FragmentLength)
- broadcastAsync (Opcode.Text, bytes, completed);
- else
- broadcastAsync (Opcode.Text, new MemoryStream (bytes), completed);
- }
+ lock (_sync)
+ {
+ if (_state != ServerState.Start)
+ return false;
- ///
- /// Sends the data from asynchronously to
- /// every client in the WebSocket service.
- ///
- ///
- ///
- /// The data is sent as the binary data.
- ///
- ///
- /// This method does not wait for the send to be complete.
- ///
- ///
- ///
- /// A instance from which to read the data to send.
- ///
- ///
- /// An that specifies the number of bytes to send.
- ///
- ///
- ///
- /// An delegate or
- /// if not needed.
- ///
- ///
- /// The delegate invokes the method called when the send is complete.
- ///
- ///
- ///
- /// The current state of the manager is not Start.
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// cannot be read.
- ///
- ///
- /// -or-
- ///
- ///
- /// is less than 1.
- ///
- ///
- /// -or-
- ///
- ///
- /// No data could be read from .
- ///
- ///
- public void BroadcastAsync (Stream stream, int length, Action completed)
- {
- if (_state != ServerState.Start) {
- var msg = "The current state of the manager is not Start.";
- throw new InvalidOperationException (msg);
- }
-
- if (stream == null)
- throw new ArgumentNullException ("stream");
-
- if (!stream.CanRead) {
- var msg = "It cannot be read.";
- throw new ArgumentException (msg, "stream");
- }
-
- if (length < 1) {
- var msg = "Less than 1.";
- throw new ArgumentException (msg, "length");
- }
-
- var bytes = stream.ReadBytes (length);
-
- var len = bytes.Length;
- if (len == 0) {
- var msg = "No data could be read from it.";
- throw new ArgumentException (msg, "stream");
- }
-
- if (len < length) {
- _log.Warn (
- String.Format (
- "Only {0} byte(s) of data could be read from the stream.",
- len
- )
- );
- }
-
- if (len <= WebSocket.FragmentLength)
- broadcastAsync (Opcode.Binary, bytes, completed);
- else
- broadcastAsync (Opcode.Binary, new MemoryStream (bytes), completed);
- }
+ return _sessions.TryGetValue(id, out session);
+ }
+ }
- ///
- /// Sends a ping to every client in the WebSocket service.
- ///
- ///
- ///
- /// A Dictionary<string, bool>.
- ///
- ///
- /// It represents a collection of pairs of a session ID and
- /// a value indicating whether a pong has been received from
- /// the client within a time.
- ///
- ///
- ///
- /// The current state of the manager is not Start.
- ///
- [Obsolete ("This method will be removed.")]
- public Dictionary Broadping ()
- {
- if (_state != ServerState.Start) {
- var msg = "The current state of the manager is not Start.";
- throw new InvalidOperationException (msg);
- }
+ #endregion
- return Broadping (WebSocketFrame.EmptyPingBytes, _waitTime);
- }
+ #region Internal Methods
- ///
- /// Sends a ping with to every client in
- /// the WebSocket service.
- ///
- ///
- ///
- /// A Dictionary<string, bool>.
- ///
- ///
- /// It represents a collection of pairs of a session ID and
- /// a value indicating whether a pong has been received from
- /// the client within a time.
- ///
- ///
- ///
- ///
- /// A that represents the message to send.
- ///
- ///
- /// The size must be 125 bytes or less in UTF-8.
- ///
- ///
- ///
- /// The current state of the manager is not Start.
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- ///
- /// The size of is greater than 125 bytes.
- ///
- [Obsolete ("This method will be removed.")]
- public Dictionary Broadping (string message)
- {
- if (_state != ServerState.Start) {
- var msg = "The current state of the manager is not Start.";
- throw new InvalidOperationException (msg);
- }
-
- if (message.IsNullOrEmpty ())
- return Broadping (WebSocketFrame.EmptyPingBytes, _waitTime);
-
- byte[] bytes;
- if (!message.TryGetUTF8EncodedBytes (out bytes)) {
- var msg = "It could not be UTF-8-encoded.";
- throw new ArgumentException (msg, "message");
- }
-
- if (bytes.Length > 125) {
- var msg = "Its size is greater than 125 bytes.";
- throw new ArgumentOutOfRangeException ("message", msg);
- }
-
- var frame = WebSocketFrame.CreatePingFrame (bytes, false);
- return Broadping (frame.ToArray (), _waitTime);
- }
-
- ///
- /// Closes the specified session.
- ///
- ///
- /// A that represents the ID of the session to close.
- ///
- ///
- /// is .
- ///
- ///
- /// is an empty string.
- ///
- ///
- /// The session could not be found.
- ///
- public void CloseSession (string id)
- {
- IWebSocketSession session;
- if (!TryGetSession (id, out session)) {
- var msg = "The session could not be found.";
- throw new InvalidOperationException (msg);
- }
+ internal string Add(IWebSocketSession session)
+ {
+ lock (_sync)
+ {
+ if (_state != ServerState.Start)
+ return null;
- session.Context.WebSocket.Close ();
- }
+ var id = session.Context.QueryString["station_code"];
+ if (string.IsNullOrEmpty(id)) id = createID();
+ if (_sessions.ContainsKey(id)) _sessions[id] = session;
+ else _sessions.Add(id, session);
+ return id;
+ }
+ }
- ///
- /// Closes the specified session with and
- /// .
- ///
- ///
- /// A that represents the ID of the session to close.
- ///
- ///
- ///
- /// A that represents the status code indicating
- /// the reason for the close.
- ///
- ///
- /// The status codes are defined in
- ///
- /// Section 7.4 of RFC 6455.
- ///
- ///
- ///
- ///
- /// A that represents the reason for the close.
- ///
- ///
- /// The size must be 123 bytes or less in UTF-8.
- ///
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// is an empty string.
- ///
- ///
- /// -or-
- ///
- ///
- /// is 1010 (mandatory extension).
- ///
- ///
- /// -or-
- ///
- ///
- /// is 1005 (no status) and there is
- /// .
- ///
- ///
- /// -or-
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- ///
- ///
- /// The session could not be found.
- ///
- ///
- ///
- /// is less than 1000 or greater than 4999.
- ///
- ///
- /// -or-
- ///
- ///
- /// The size of is greater than 123 bytes.
- ///
- ///
- public void CloseSession (string id, ushort code, string reason)
- {
- IWebSocketSession session;
- if (!TryGetSession (id, out session)) {
- var msg = "The session could not be found.";
- throw new InvalidOperationException (msg);
- }
+ internal void Broadcast(
+ Opcode opcode, byte[] data, Dictionary cache
+ )
+ {
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ _log.Error("The service is shutting down.");
+ break;
+ }
+
+ session.Context.WebSocket.Send(opcode, data, cache);
+ }
+ }
- session.Context.WebSocket.Close (code, reason);
- }
+ internal void Broadcast(
+ Opcode opcode, Stream stream, Dictionary cache
+ )
+ {
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ _log.Error("The service is shutting down.");
+ break;
+ }
+
+ session.Context.WebSocket.Send(opcode, stream, cache);
+ }
+ }
- ///
- /// Closes the specified session with and
- /// .
- ///
- ///
- /// A that represents the ID of the session to close.
- ///
- ///
- ///
- /// One of the enum values.
- ///
- ///
- /// It represents the status code indicating the reason for the close.
- ///
- ///
- ///
- ///
- /// A that represents the reason for the close.
- ///
- ///
- /// The size must be 123 bytes or less in UTF-8.
- ///
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// is an empty string.
- ///
- ///
- /// -or-
- ///
- ///
- /// is
- /// .
- ///
- ///
- /// -or-
- ///
- ///
- /// is
- /// and there is
- /// .
- ///
- ///
- /// -or-
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- ///
- ///
- /// The session could not be found.
- ///
- ///
- /// The size of is greater than 123 bytes.
- ///
- public void CloseSession (string id, CloseStatusCode code, string reason)
- {
- IWebSocketSession session;
- if (!TryGetSession (id, out session)) {
- var msg = "The session could not be found.";
- throw new InvalidOperationException (msg);
- }
+ internal Dictionary Broadping(
+ byte[] frameAsBytes, TimeSpan timeout
+ )
+ {
+ var ret = new Dictionary();
+
+ foreach (var session in Sessions)
+ {
+ if (_state != ServerState.Start)
+ {
+ _log.Error("The service is shutting down.");
+ break;
+ }
+
+ var res = session.Context.WebSocket.Ping(frameAsBytes, timeout);
+ ret.Add(session.ID, res);
+ }
+
+ return ret;
+ }
- session.Context.WebSocket.Close (code, reason);
- }
+ internal bool Remove(string id)
+ {
+ lock (_sync)
+ return _sessions.Remove(id);
+ }
- ///
- /// Sends a ping to the client using the specified session.
- ///
- ///
- /// true if the send has done with no error and a pong has been
- /// received from the client within a time; otherwise, false.
- ///
- ///
- /// A that represents the ID of the session.
- ///
- ///
- /// is .
- ///
- ///
- /// is an empty string.
- ///
- ///
- /// The session could not be found.
- ///
- public bool PingTo (string id)
- {
- IWebSocketSession session;
- if (!TryGetSession (id, out session)) {
- var msg = "The session could not be found.";
- throw new InvalidOperationException (msg);
- }
+ internal void Start()
+ {
+ lock (_sync)
+ {
+ _sweepTimer.Enabled = _clean;
+ _state = ServerState.Start;
+ }
+ }
- return session.Context.WebSocket.Ping ();
- }
+ internal void Stop(ushort code, string reason)
+ {
+ if (code == 1005)
+ { // == no status
+ stop(PayloadData.Empty, true);
+ return;
+ }
- ///
- /// Sends a ping with to the client using
- /// the specified session.
- ///
- ///
- /// true if the send has done with no error and a pong has been
- /// received from the client within a time; otherwise, false.
- ///
- ///
- ///
- /// A that represents the message to send.
- ///
- ///
- /// The size must be 125 bytes or less in UTF-8.
- ///
- ///
- ///
- /// A that represents the ID of the session.
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// is an empty string.
- ///
- ///
- /// -or-
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- ///
- ///
- /// The session could not be found.
- ///
- ///
- /// The size of is greater than 125 bytes.
- ///
- public bool PingTo (string message, string id)
- {
- IWebSocketSession session;
- if (!TryGetSession (id, out session)) {
- var msg = "The session could not be found.";
- throw new InvalidOperationException (msg);
- }
+ stop(new PayloadData(code, reason), !code.IsReserved());
+ }
- return session.Context.WebSocket.Ping (message);
- }
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Sends to every client in the WebSocket service.
+ ///
+ ///
+ /// An array of that represents the binary data to send.
+ ///
+ ///
+ /// The current state of the manager is not Start.
+ ///
+ ///
+ /// is .
+ ///
+ public void Broadcast(byte[] data)
+ {
+ if (_state != ServerState.Start)
+ {
+ var msg = "The current state of the manager is not Start.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (data == null)
+ throw new ArgumentNullException("data");
+
+ if (data.LongLength <= WebSocket.FragmentLength)
+ broadcast(Opcode.Binary, data, null);
+ else
+ broadcast(Opcode.Binary, new MemoryStream(data), null);
+ }
- ///
- /// Sends to the client using the specified session.
- ///
- ///
- /// An array of that represents the binary data to send.
- ///
- ///
- /// A that represents the ID of the session.
- ///
- ///
- ///
- /// is .
- ///
- ///
- /// -or-
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// is an empty string.
- ///
- ///
- ///
- /// The session could not be found.
- ///
- ///
- /// -or-
- ///
- ///
- /// The current state of the WebSocket connection is not Open.
- ///
- ///
- public void SendTo (byte[] data, string id)
- {
- IWebSocketSession session;
- if (!TryGetSession (id, out session)) {
- var msg = "The session could not be found.";
- throw new InvalidOperationException (msg);
- }
+ ///
+ /// Sends to every client in the WebSocket service.
+ ///
+ ///
+ /// A that represents the text data to send.
+ ///
+ ///
+ /// The current state of the manager is not Start.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ public void Broadcast(string data)
+ {
+ if (_state != ServerState.Start)
+ {
+ var msg = "The current state of the manager is not Start.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (data == null)
+ throw new ArgumentNullException("data");
+
+ byte[] bytes;
+ if (!data.TryGetUTF8EncodedBytes(out bytes))
+ {
+ var msg = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(msg, "data");
+ }
+
+ if (bytes.LongLength <= WebSocket.FragmentLength)
+ broadcast(Opcode.Text, bytes, null);
+ else
+ broadcast(Opcode.Text, new MemoryStream(bytes), null);
+ }
- session.Context.WebSocket.Send (data);
- }
+ ///
+ /// Sends the data from to every client in
+ /// the WebSocket service.
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ /// The current state of the manager is not Start.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ public void Broadcast(Stream stream, int length)
+ {
+ if (_state != ServerState.Start)
+ {
+ var msg = "The current state of the manager is not Start.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (stream == null)
+ throw new ArgumentNullException("stream");
+
+ if (!stream.CanRead)
+ {
+ var msg = "It cannot be read.";
+ throw new ArgumentException(msg, "stream");
+ }
+
+ if (length < 1)
+ {
+ var msg = "Less than 1.";
+ throw new ArgumentException(msg, "length");
+ }
+
+ var bytes = stream.ReadBytes(length);
+
+ var len = bytes.Length;
+ if (len == 0)
+ {
+ var msg = "No data could be read from it.";
+ throw new ArgumentException(msg, "stream");
+ }
+
+ if (len < length)
+ {
+ _log.Warn(
+ String.Format(
+ "Only {0} byte(s) of data could be read from the stream.",
+ len
+ )
+ );
+ }
+
+ if (len <= WebSocket.FragmentLength)
+ broadcast(Opcode.Binary, bytes, null);
+ else
+ broadcast(Opcode.Binary, new MemoryStream(bytes), null);
+ }
- ///
- /// Sends to the client using the specified session.
- ///
- ///
- /// A that represents the text data to send.
- ///
- ///
- /// A that represents the ID of the session.
- ///
- ///
- ///
- /// is .
- ///
- ///
- /// -or-
- ///
- ///
- /// is .
- ///
- ///
- ///
- ///
- /// is an empty string.
- ///
- ///
- /// -or-
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- ///
- ///
- ///
- /// The session could not be found.
- ///
- ///
- /// -or-
- ///
- ///
- /// The current state of the WebSocket connection is not Open.
- ///
- ///
- public void SendTo (string data, string id)
- {
- IWebSocketSession session;
- if (!TryGetSession (id, out session)) {
- var msg = "The session could not be found.";
- throw new InvalidOperationException (msg);
- }
+ ///
+ /// Sends asynchronously to every client in
+ /// the WebSocket service.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// An array of that represents the binary data to send.
+ ///
+ ///
+ ///
+ /// An delegate or
+ /// if not needed.
+ ///
+ ///
+ /// The delegate invokes the method called when the send is complete.
+ ///
+ ///
+ ///
+ /// The current state of the manager is not Start.
+ ///
+ ///
+ /// is .
+ ///
+ public void BroadcastAsync(byte[] data, Action completed)
+ {
+ if (_state != ServerState.Start)
+ {
+ var msg = "The current state of the manager is not Start.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (data == null)
+ throw new ArgumentNullException("data");
+
+ if (data.LongLength <= WebSocket.FragmentLength)
+ broadcastAsync(Opcode.Binary, data, completed);
+ else
+ broadcastAsync(Opcode.Binary, new MemoryStream(data), completed);
+ }
- session.Context.WebSocket.Send (data);
- }
+ ///
+ /// Sends asynchronously to every client in
+ /// the WebSocket service.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// A that represents the text data to send.
+ ///
+ ///
+ ///
+ /// An delegate or
+ /// if not needed.
+ ///
+ ///
+ /// The delegate invokes the method called when the send is complete.
+ ///
+ ///
+ ///
+ /// The current state of the manager is not Start.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ public void BroadcastAsync(string data, Action completed)
+ {
+ if (_state != ServerState.Start)
+ {
+ var msg = "The current state of the manager is not Start.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (data == null)
+ throw new ArgumentNullException("data");
+
+ byte[] bytes;
+ if (!data.TryGetUTF8EncodedBytes(out bytes))
+ {
+ var msg = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(msg, "data");
+ }
+
+ if (bytes.LongLength <= WebSocket.FragmentLength)
+ broadcastAsync(Opcode.Text, bytes, completed);
+ else
+ broadcastAsync(Opcode.Text, new MemoryStream(bytes), completed);
+ }
- ///
- /// Sends the data from to the client using
- /// the specified session.
- ///
- ///
- /// The data is sent as the binary data.
- ///
- ///
- /// A instance from which to read the data to send.
- ///
- ///
- /// An that specifies the number of bytes to send.
- ///
- ///
- /// A that represents the ID of the session.
- ///
- ///
- ///
- /// is .
- ///
- ///
- /// -or-
- ///
- ///
- /// is .
- ///
- ///
- ///
- ///
- /// is an empty string.
- ///
- ///
- /// -or-
- ///
- ///
- /// cannot be read.
- ///
- ///
- /// -or-
- ///
- ///
- /// is less than 1.
- ///
- ///
- /// -or-
- ///
- ///
- /// No data could be read from .
- ///
- ///
- ///
- ///
- /// The session could not be found.
- ///
- ///
- /// -or-
- ///
- ///
- /// The current state of the WebSocket connection is not Open.
- ///
- ///
- public void SendTo (Stream stream, int length, string id)
- {
- IWebSocketSession session;
- if (!TryGetSession (id, out session)) {
- var msg = "The session could not be found.";
- throw new InvalidOperationException (msg);
- }
+ ///
+ /// Sends the data from asynchronously to
+ /// every client in the WebSocket service.
+ ///
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ ///
+ /// An delegate or
+ /// if not needed.
+ ///
+ ///
+ /// The delegate invokes the method called when the send is complete.
+ ///
+ ///
+ ///
+ /// The current state of the manager is not Start.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ public void BroadcastAsync(Stream stream, int length, Action completed)
+ {
+ if (_state != ServerState.Start)
+ {
+ var msg = "The current state of the manager is not Start.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (stream == null)
+ throw new ArgumentNullException("stream");
+
+ if (!stream.CanRead)
+ {
+ var msg = "It cannot be read.";
+ throw new ArgumentException(msg, "stream");
+ }
+
+ if (length < 1)
+ {
+ var msg = "Less than 1.";
+ throw new ArgumentException(msg, "length");
+ }
+
+ var bytes = stream.ReadBytes(length);
+
+ var len = bytes.Length;
+ if (len == 0)
+ {
+ var msg = "No data could be read from it.";
+ throw new ArgumentException(msg, "stream");
+ }
+
+ if (len < length)
+ {
+ _log.Warn(
+ String.Format(
+ "Only {0} byte(s) of data could be read from the stream.",
+ len
+ )
+ );
+ }
+
+ if (len <= WebSocket.FragmentLength)
+ broadcastAsync(Opcode.Binary, bytes, completed);
+ else
+ broadcastAsync(Opcode.Binary, new MemoryStream(bytes), completed);
+ }
- session.Context.WebSocket.Send (stream, length);
- }
+ ///
+ /// Sends a ping to every client in the WebSocket service.
+ ///
+ ///
+ ///
+ /// A Dictionary<string, bool>.
+ ///
+ ///
+ /// It represents a collection of pairs of a session ID and
+ /// a value indicating whether a pong has been received from
+ /// the client within a time.
+ ///
+ ///
+ ///
+ /// The current state of the manager is not Start.
+ ///
+ [Obsolete("This method will be removed.")]
+ public Dictionary Broadping()
+ {
+ if (_state != ServerState.Start)
+ {
+ var msg = "The current state of the manager is not Start.";
+ throw new InvalidOperationException(msg);
+ }
+
+ return Broadping(WebSocketFrame.EmptyPingBytes, _waitTime);
+ }
- ///
- /// Sends asynchronously to the client using
- /// the specified session.
- ///
- ///
- /// This method does not wait for the send to be complete.
- ///
- ///
- /// An array of that represents the binary data to send.
- ///
- ///
- /// A that represents the ID of the session.
- ///
- ///
- ///
- /// An Action<bool> delegate or
- /// if not needed.
- ///
- ///
- /// The delegate invokes the method called when the send is complete.
- ///
- ///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
- ///
- ///
- ///
- ///
- /// is .
- ///
- ///
- /// -or-
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// is an empty string.
- ///
- ///
- ///
- /// The session could not be found.
- ///
- ///
- /// -or-
- ///
- ///
- /// The current state of the WebSocket connection is not Open.
- ///
- ///
- public void SendToAsync (byte[] data, string id, Action completed)
- {
- IWebSocketSession session;
- if (!TryGetSession (id, out session)) {
- var msg = "The session could not be found.";
- throw new InvalidOperationException (msg);
- }
+ ///
+ /// Sends a ping with to every client in
+ /// the WebSocket service.
+ ///
+ ///
+ ///
+ /// A Dictionary<string, bool>.
+ ///
+ ///
+ /// It represents a collection of pairs of a session ID and
+ /// a value indicating whether a pong has been received from
+ /// the client within a time.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the message to send.
+ ///
+ ///
+ /// The size must be 125 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ /// The current state of the manager is not Start.
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ /// The size of is greater than 125 bytes.
+ ///
+ [Obsolete("This method will be removed.")]
+ public Dictionary Broadping(string message)
+ {
+ if (_state != ServerState.Start)
+ {
+ var msg = "The current state of the manager is not Start.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (message.IsNullOrEmpty())
+ return Broadping(WebSocketFrame.EmptyPingBytes, _waitTime);
+
+ byte[] bytes;
+ if (!message.TryGetUTF8EncodedBytes(out bytes))
+ {
+ var msg = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(msg, "message");
+ }
+
+ if (bytes.Length > 125)
+ {
+ var msg = "Its size is greater than 125 bytes.";
+ throw new ArgumentOutOfRangeException("message", msg);
+ }
+
+ var frame = WebSocketFrame.CreatePingFrame(bytes, false);
+ return Broadping(frame.ToArray(), _waitTime);
+ }
- session.Context.WebSocket.SendAsync (data, completed);
- }
+ ///
+ /// Closes the specified session.
+ ///
+ ///
+ /// A that represents the ID of the session to close.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ public void CloseSession(string id)
+ {
+ IWebSocketSession session;
+ if (!TryGetSession(id, out session))
+ {
+ var msg = "The session could not be found.";
+ throw new InvalidOperationException(msg);
+ }
+
+ session.Context.WebSocket.Close();
+ }
- ///
- /// Sends asynchronously to the client using
- /// the specified session.
- ///
- ///
- /// This method does not wait for the send to be complete.
- ///
- ///
- /// A that represents the text data to send.
- ///
- ///
- /// A that represents the ID of the session.
- ///
- ///
- ///
- /// An Action<bool> delegate or
- /// if not needed.
- ///
- ///
- /// The delegate invokes the method called when the send is complete.
- ///
- ///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
- ///
- ///
- ///
- ///
- /// is .
- ///
- ///
- /// -or-
- ///
- ///
- /// is .
- ///
- ///
- ///
- ///
- /// is an empty string.
- ///
- ///
- /// -or-
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- ///
- ///
- ///
- /// The session could not be found.
- ///
- ///
- /// -or-
- ///
- ///
- /// The current state of the WebSocket connection is not Open.
- ///
- ///
- public void SendToAsync (string data, string id, Action completed)
- {
- IWebSocketSession session;
- if (!TryGetSession (id, out session)) {
- var msg = "The session could not be found.";
- throw new InvalidOperationException (msg);
- }
+ ///
+ /// Closes the specified session with and
+ /// .
+ ///
+ ///
+ /// A that represents the ID of the session to close.
+ ///
+ ///
+ ///
+ /// A that represents the status code indicating
+ /// the reason for the close.
+ ///
+ ///
+ /// The status codes are defined in
+ ///
+ /// Section 7.4 of RFC 6455.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the reason for the close.
+ ///
+ ///
+ /// The size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1010 (mandatory extension).
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1005 (no status) and there is
+ /// .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ ///
+ /// is less than 1000 or greater than 4999.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ ///
+ public void CloseSession(string id, ushort code, string reason)
+ {
+ IWebSocketSession session;
+ if (!TryGetSession(id, out session))
+ {
+ var msg = "The session could not be found.";
+ throw new InvalidOperationException(msg);
+ }
+
+ session.Context.WebSocket.Close(code, reason);
+ }
- session.Context.WebSocket.SendAsync (data, completed);
- }
+ ///
+ /// Closes the specified session with and
+ /// .
+ ///
+ ///
+ /// A that represents the ID of the session to close.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It represents the status code indicating the reason for the close.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the reason for the close.
+ ///
+ ///
+ /// The size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is
+ /// .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is
+ /// and there is
+ /// .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ public void CloseSession(string id, CloseStatusCode code, string reason)
+ {
+ IWebSocketSession session;
+ if (!TryGetSession(id, out session))
+ {
+ var msg = "The session could not be found.";
+ throw new InvalidOperationException(msg);
+ }
+
+ session.Context.WebSocket.Close(code, reason);
+ }
- ///
- /// Sends the data from asynchronously to
- /// the client using the specified session.
- ///
- ///
- ///
- /// The data is sent as the binary data.
- ///
- ///
- /// This method does not wait for the send to be complete.
- ///
- ///
- ///
- /// A instance from which to read the data to send.
- ///
- ///
- /// An that specifies the number of bytes to send.
- ///
- ///
- /// A that represents the ID of the session.
- ///
- ///
- ///
- /// An Action<bool> delegate or
- /// if not needed.
- ///
- ///
- /// The delegate invokes the method called when the send is complete.
- ///
- ///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
- ///
- ///
- ///
- ///
- /// is .
- ///
- ///
- /// -or-
- ///
- ///
- /// is .
- ///
- ///
- ///
- ///
- /// is an empty string.
- ///
- ///
- /// -or-
- ///
- ///
- /// cannot be read.
- ///
- ///
- /// -or-
- ///
- ///
- /// is less than 1.
- ///
- ///
- /// -or-
- ///
- ///
- /// No data could be read from .
- ///
- ///
- ///
- ///
- /// The session could not be found.
- ///
- ///
- /// -or-
- ///
- ///
- /// The current state of the WebSocket connection is not Open.
- ///
- ///
- public void SendToAsync (
- Stream stream, int length, string id, Action completed
- )
- {
- IWebSocketSession session;
- if (!TryGetSession (id, out session)) {
- var msg = "The session could not be found.";
- throw new InvalidOperationException (msg);
- }
+ ///
+ /// Sends a ping to the client using the specified session.
+ ///
+ ///
+ /// true if the send has done with no error and a pong has been
+ /// received from the client within a time; otherwise, false.
+ ///
+ ///
+ /// A that represents the ID of the session.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ public bool PingTo(string id)
+ {
+ IWebSocketSession session;
+ if (!TryGetSession(id, out session))
+ {
+ var msg = "The session could not be found.";
+ throw new InvalidOperationException(msg);
+ }
+
+ return session.Context.WebSocket.Ping();
+ }
- session.Context.WebSocket.SendAsync (stream, length, completed);
- }
+ ///
+ /// Sends a ping with to the client using
+ /// the specified session.
+ ///
+ ///
+ /// true if the send has done with no error and a pong has been
+ /// received from the client within a time; otherwise, false.
+ ///
+ ///
+ ///
+ /// A that represents the message to send.
+ ///
+ ///
+ /// The size must be 125 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ /// A that represents the ID of the session.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// The size of is greater than 125 bytes.
+ ///
+ public bool PingTo(string message, string id)
+ {
+ IWebSocketSession session;
+ if (!TryGetSession(id, out session))
+ {
+ var msg = "The session could not be found.";
+ throw new InvalidOperationException(msg);
+ }
+
+ return session.Context.WebSocket.Ping(message);
+ }
- ///
- /// Cleans up the inactive sessions in the WebSocket service.
- ///
- public void Sweep ()
- {
- if (_sweeping) {
- _log.Info ("The sweeping is already in progress.");
- return;
- }
-
- lock (_forSweep) {
- if (_sweeping) {
- _log.Info ("The sweeping is already in progress.");
- return;
+ ///
+ /// Sends to the client using the specified session.
+ ///
+ ///
+ /// An array of that represents the binary data to send.
+ ///
+ ///
+ /// A that represents the ID of the session.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket connection is not Open.
+ ///
+ ///
+ public void SendTo(byte[] data, string id)
+ {
+ IWebSocketSession session;
+ if (!TryGetSession(id, out session))
+ {
+ var msg = "The session could not be found.";
+ throw new InvalidOperationException(msg);
+ }
+
+ session.Context.WebSocket.Send(data);
}
- _sweeping = true;
- }
+ ///
+ /// Sends to the client using the specified session.
+ ///
+ ///
+ /// A that represents the text data to send.
+ ///
+ ///
+ /// A that represents the ID of the session.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket connection is not Open.
+ ///
+ ///
+ public void SendTo(string data, string id)
+ {
+ IWebSocketSession session;
+ if (!TryGetSession(id, out session))
+ {
+ var msg = "The session could not be found.";
+ throw new InvalidOperationException(msg);
+ }
+
+ session.Context.WebSocket.Send(data);
+ }
- foreach (var id in InactiveIDs) {
- if (_state != ServerState.Start)
- break;
+ ///
+ /// Sends the data from to the client using
+ /// the specified session.
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ /// A that represents the ID of the session.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket connection is not Open.
+ ///
+ ///
+ public void SendTo(Stream stream, int length, string id)
+ {
+ IWebSocketSession session;
+ if (!TryGetSession(id, out session))
+ {
+ var msg = "The session could not be found.";
+ throw new InvalidOperationException(msg);
+ }
+
+ session.Context.WebSocket.Send(stream, length);
+ }
- lock (_sync) {
- if (_state != ServerState.Start)
- break;
+ ///
+ /// Sends asynchronously to the client using
+ /// the specified session.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// An array of that represents the binary data to send.
+ ///
+ ///
+ /// A that represents the ID of the session.
+ ///
+ ///
+ ///
+ /// An Action<bool> delegate or
+ /// if not needed.
+ ///
+ ///
+ /// The delegate invokes the method called when the send is complete.
+ ///
+ ///
+ /// true is passed to the method if the send has done with
+ /// no error; otherwise, false.
+ ///
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket connection is not Open.
+ ///
+ ///
+ public void SendToAsync(byte[] data, string id, Action completed)
+ {
+ IWebSocketSession session;
+ if (!TryGetSession(id, out session))
+ {
+ var msg = "The session could not be found.";
+ throw new InvalidOperationException(msg);
+ }
+
+ session.Context.WebSocket.SendAsync(data, completed);
+ }
- IWebSocketSession session;
- if (_sessions.TryGetValue (id, out session)) {
- var state = session.ConnectionState;
- if (state == WebSocketState.Open)
- session.Context.WebSocket.Close (CloseStatusCode.Abnormal);
- else if (state == WebSocketState.Closing)
- continue;
- else
- _sessions.Remove (id);
- }
+ ///
+ /// Sends asynchronously to the client using
+ /// the specified session.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// A that represents the text data to send.
+ ///
+ ///
+ /// A that represents the ID of the session.
+ ///
+ ///
+ ///
+ /// An Action<bool> delegate or
+ /// if not needed.
+ ///
+ ///
+ /// The delegate invokes the method called when the send is complete.
+ ///
+ ///
+ /// true is passed to the method if the send has done with
+ /// no error; otherwise, false.
+ ///
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket connection is not Open.
+ ///
+ ///
+ public void SendToAsync(string data, string id, Action completed)
+ {
+ IWebSocketSession session;
+ if (!TryGetSession(id, out session))
+ {
+ var msg = "The session could not be found.";
+ throw new InvalidOperationException(msg);
+ }
+
+ session.Context.WebSocket.SendAsync(data, completed);
}
- }
- _sweeping = false;
- }
+ ///
+ /// Sends the data from asynchronously to
+ /// the client using the specified session.
+ ///
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ /// A that represents the ID of the session.
+ ///
+ ///
+ ///
+ /// An Action<bool> delegate or
+ /// if not needed.
+ ///
+ ///
+ /// The delegate invokes the method called when the send is complete.
+ ///
+ ///
+ /// true is passed to the method if the send has done with
+ /// no error; otherwise, false.
+ ///
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ ///
+ ///
+ /// The session could not be found.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The current state of the WebSocket connection is not Open.
+ ///
+ ///
+ public void SendToAsync(
+ Stream stream, int length, string id, Action completed
+ )
+ {
+ IWebSocketSession session;
+ if (!TryGetSession(id, out session))
+ {
+ var msg = "The session could not be found.";
+ throw new InvalidOperationException(msg);
+ }
+
+ session.Context.WebSocket.SendAsync(stream, length, completed);
+ }
- ///
- /// Tries to get the session instance with .
- ///
- ///
- /// true if the session is successfully found; otherwise,
- /// false.
- ///
- ///
- /// A that represents the ID of the session to find.
- ///
- ///
- ///
- /// When this method returns, a
- /// instance or if not found.
- ///
- ///
- /// The session instance provides the function to access
- /// the information in the session.
- ///
- ///
- ///
- /// is .
- ///
- ///
- /// is an empty string.
- ///
- public bool TryGetSession (string id, out IWebSocketSession session)
- {
- if (id == null)
- throw new ArgumentNullException ("id");
+ ///
+ /// Cleans up the inactive sessions in the WebSocket service.
+ ///
+ public void Sweep()
+ {
+ if (_sweeping)
+ {
+ _log.Info("The sweeping is already in progress.");
+ return;
+ }
+
+ lock (_forSweep)
+ {
+ if (_sweeping)
+ {
+ _log.Info("The sweeping is already in progress.");
+ return;
+ }
+
+ _sweeping = true;
+ }
+
+ foreach (var id in InactiveIDs)
+ {
+ if (_state != ServerState.Start)
+ break;
+
+ lock (_sync)
+ {
+ if (_state != ServerState.Start)
+ break;
+
+ IWebSocketSession session;
+ if (_sessions.TryGetValue(id, out session))
+ {
+ var state = session.ConnectionState;
+ if (state == WebSocketState.Open)
+ session.Context.WebSocket.Close(CloseStatusCode.Abnormal);
+ else if (state == WebSocketState.Closing)
+ continue;
+ else
+ _sessions.Remove(id);
+ }
+ }
+ }
+
+ _sweeping = false;
+ }
- if (id.Length == 0)
- throw new ArgumentException ("An empty string.", "id");
+ ///
+ /// Tries to get the session instance with .
+ ///
+ ///
+ /// true if the session is successfully found; otherwise,
+ /// false.
+ ///
+ ///
+ /// A that represents the ID of the session to find.
+ ///
+ ///
+ ///
+ /// When this method returns, a
+ /// instance or if not found.
+ ///
+ ///
+ /// The session instance provides the function to access
+ /// the information in the session.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// is an empty string.
+ ///
+ public bool TryGetSession(string id, out IWebSocketSession session)
+ {
+ if (id == null)
+ throw new ArgumentNullException("id");
+
+ if (id.Length == 0)
+ throw new ArgumentException("An empty string.", "id");
+
+ return tryGetSession(id, out session);
+ }
- return tryGetSession (id, out session);
+ #endregion
}
-
- #endregion
- }
}
diff --git a/websocket-sharp/WebSocket.cs b/websocket-sharp/WebSocket.cs
index 011dee00d..56659fb02 100644
--- a/websocket-sharp/WebSocket.cs
+++ b/websocket-sharp/WebSocket.cs
@@ -50,4044 +50,4439 @@
using System.Security.Cryptography;
using System.Text;
using System.Threading;
+using System.Threading.Tasks;
using WebSocketSharp.Net;
using WebSocketSharp.Net.WebSockets;
namespace WebSocketSharp
{
- ///
- /// Implements the WebSocket interface.
- ///
- ///
- ///
- /// This class provides a set of methods and properties for two-way
- /// communication using the WebSocket protocol.
- ///
- ///
- /// The WebSocket protocol is defined in
- /// RFC 6455.
- ///
- ///
- public class WebSocket : IDisposable
- {
- #region Private Fields
-
- private AuthenticationChallenge _authChallenge;
- private string _base64Key;
- private bool _client;
- private Action _closeContext;
- private CompressionMethod _compression;
- private WebSocketContext _context;
- private CookieCollection _cookies;
- private NetworkCredential _credentials;
- private bool _emitOnPing;
- private bool _enableRedirection;
- private string _extensions;
- private bool _extensionsRequested;
- private object _forMessageEventQueue;
- private object _forPing;
- private object _forSend;
- private object _forState;
- private MemoryStream _fragmentsBuffer;
- private bool _fragmentsCompressed;
- private Opcode _fragmentsOpcode;
- private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- private Func _handshakeRequestChecker;
- private bool _ignoreExtensions;
- private bool _inContinuation;
- private volatile bool _inMessage;
- private volatile Logger _logger;
- private static readonly int _maxRetryCountForConnect;
- private Action _message;
- private Queue _messageEventQueue;
- private uint _nonceCount;
- private string _origin;
- private ManualResetEvent _pongReceived;
- private bool _preAuth;
- private string _protocol;
- private string[] _protocols;
- private bool _protocolsRequested;
- private NetworkCredential _proxyCredentials;
- private Uri _proxyUri;
- private volatile WebSocketState _readyState;
- private ManualResetEvent _receivingExited;
- private int _retryCountForConnect;
- private bool _secure;
- private ClientSslConfiguration _sslConfig;
- private Stream _stream;
- private TcpClient _tcpClient;
- private Uri _uri;
- private const string _version = "13";
- private TimeSpan _waitTime;
-
- #endregion
-
- #region Internal Fields
-
- ///
- /// Represents the empty array of used internally.
- ///
- internal static readonly byte[] EmptyBytes;
-
///
- /// Represents the length used to determine whether the data should be fragmented in sending.
+ /// Implements the WebSocket interface.
///
///
///
- /// The data will be fragmented if that length is greater than the value of this field.
+ /// This class provides a set of methods and properties for two-way
+ /// communication using the WebSocket protocol.
///
///
- /// If you would like to change the value, you must set it to a value between 125 and
- /// Int32.MaxValue - 14 inclusive.
+ /// The WebSocket protocol is defined in
+ /// RFC 6455.
///
///
- internal static readonly int FragmentLength;
-
- ///
- /// Represents the random number generator used internally.
- ///
- internal static readonly RandomNumberGenerator RandomNumber;
-
- #endregion
-
- #region Static Constructor
-
- static WebSocket ()
+ public class WebSocket : IDisposable
{
- _maxRetryCountForConnect = 10;
- EmptyBytes = new byte[0];
- FragmentLength = 1016;
- RandomNumber = new RNGCryptoServiceProvider ();
- }
-
- #endregion
-
- #region Internal Constructors
+ #region Private Fields
+
+ private AuthenticationChallenge _authChallenge;
+ private string _base64Key;
+ private bool _client;
+ private Action _closeContext;
+ private CompressionMethod _compression;
+ private WebSocketContext _context;
+ private CookieCollection _cookies;
+ private NetworkCredential _credentials;
+ private bool _emitOnPing;
+ private bool _enableRedirection;
+ private string _extensions;
+ private bool _extensionsRequested;
+ private object _forMessageEventQueue;
+ private object _forPing;
+ private object _forSend;
+ private object _forState;
+ private MemoryStream _fragmentsBuffer;
+ private bool _fragmentsCompressed;
+ private Opcode _fragmentsOpcode;
+ private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+ private Func _handshakeRequestChecker;
+ private bool _ignoreExtensions;
+ private bool _inContinuation;
+ private volatile bool _inMessage;
+ private volatile Logger _logger;
+ private static readonly int _maxRetryCountForConnect;
+ private Action _message;
+ private Queue _messageEventQueue;
+ private uint _nonceCount;
+ private string _origin;
+ private ManualResetEvent _pongReceived;
+ private bool _preAuth;
+ private string _protocol;
+ private string[] _protocols;
+ private bool _protocolsRequested;
+ private NetworkCredential _proxyCredentials;
+ private Uri _proxyUri;
+ private volatile WebSocketState _readyState;
+ private ManualResetEvent _receivingExited;
+ private int _retryCountForConnect;
+ private bool _secure;
+ private ClientSslConfiguration _sslConfig;
+ private Stream _stream;
+ private TcpClient _tcpClient;
+ private Uri _uri;
+ private const string _version = "13";
+ private TimeSpan _waitTime;
+#if __PING_ASYNC
+ private bool _isAlive = true;
+ private bool _isPinged = true;
+#endif
+ #endregion
+
+ #region Internal Fields
+
+ ///
+ /// Represents the empty array of used internally.
+ ///
+ internal static readonly byte[] EmptyBytes;
+
+ ///
+ /// Represents the length used to determine whether the data should be fragmented in sending.
+ ///
+ ///
+ ///
+ /// The data will be fragmented if that length is greater than the value of this field.
+ ///
+ ///
+ /// If you would like to change the value, you must set it to a value between 125 and
+ /// Int32.MaxValue - 14 inclusive.
+ ///
+ ///
+ internal static readonly int FragmentLength;
+
+ ///
+ /// Represents the random number generator used internally.
+ ///
+ internal static readonly RandomNumberGenerator RandomNumber;
+
+ #endregion
+
+ #region Static Constructor
+
+ static WebSocket()
+ {
+ _maxRetryCountForConnect = 10;
+ EmptyBytes = new byte[0];
+ FragmentLength = 1016;
+ RandomNumber = new RNGCryptoServiceProvider();
+ }
- // As server
- internal WebSocket (HttpListenerWebSocketContext context, string protocol)
- {
- _context = context;
- _protocol = protocol;
+ #endregion
- _closeContext = context.Close;
- _logger = context.Log;
- _message = messages;
- _secure = context.IsSecureConnection;
- _stream = context.Stream;
- _waitTime = TimeSpan.FromSeconds (1);
+ #region Internal Constructors
- init ();
- }
+ // As server
+ internal WebSocket(HttpListenerWebSocketContext context, string protocol)
+ {
+ _context = context;
+ _protocol = protocol;
- // As server
- internal WebSocket (TcpListenerWebSocketContext context, string protocol)
- {
- _context = context;
- _protocol = protocol;
+ _closeContext = context.Close;
+ _logger = context.Log;
+ _message = messages;
+ _secure = context.IsSecureConnection;
+ _stream = context.Stream;
+ _waitTime = TimeSpan.FromSeconds(1);
- _closeContext = context.Close;
- _logger = context.Log;
- _message = messages;
- _secure = context.IsSecureConnection;
- _stream = context.Stream;
- _waitTime = TimeSpan.FromSeconds (1);
+ init();
+ }
- init ();
- }
+ // As server
+ internal WebSocket(TcpListenerWebSocketContext context, string protocol)
+ {
+ _context = context;
+ _protocol = protocol;
- #endregion
+ _closeContext = context.Close;
+ _logger = context.Log;
+ _message = messages;
+ _secure = context.IsSecureConnection;
+ _stream = context.Stream;
+ _waitTime = TimeSpan.FromSeconds(1);
- #region Public Constructors
+ init();
+ }
- ///
- /// Initializes a new instance of the class with
- /// and optionally .
- ///
- ///
- ///
- /// A that specifies the URL to which to connect.
- ///
- ///
- /// The scheme of the URL must be ws or wss.
- ///
- ///
- /// The new instance uses a secure connection if the scheme is wss.
- ///
- ///
- ///
- ///
- /// An array of that specifies the names of
- /// the subprotocols if necessary.
- ///
- ///
- /// Each value of the array must be a token defined in
- ///
- /// RFC 2616.
- ///
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// is an empty string.
- ///
- ///
- /// -or-
- ///
- ///
- /// is an invalid WebSocket URL string.
- ///
- ///
- /// -or-
- ///
- ///
- /// contains a value that is not a token.
- ///
- ///
- /// -or-
- ///
- ///
- /// contains a value twice.
- ///
- ///
- public WebSocket (string url, params string[] protocols)
- {
- if (url == null)
- throw new ArgumentNullException ("url");
+ #endregion
+
+ #region Public Constructors
+
+ ///
+ /// Initializes a new instance of the class with
+ /// and optionally .
+ ///
+ ///
+ ///
+ /// A that specifies the URL to which to connect.
+ ///
+ ///
+ /// The scheme of the URL must be ws or wss.
+ ///
+ ///
+ /// The new instance uses a secure connection if the scheme is wss.
+ ///
+ ///
+ ///
+ ///
+ /// An array of that specifies the names of
+ /// the subprotocols if necessary.
+ ///
+ ///
+ /// Each value of the array must be a token defined in
+ ///
+ /// RFC 2616.
+ ///
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// is an empty string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is an invalid WebSocket URL string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains a value that is not a token.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains a value twice.
+ ///
+ ///
+ public WebSocket(string url, params string[] protocols)
+ {
+ if (url == null)
+ throw new ArgumentNullException("url");
+
+ if (url.Length == 0)
+ throw new ArgumentException("An empty string.", "url");
+
+ string msg;
+ if (!url.TryCreateWebSocketUri(out _uri, out msg))
+ throw new ArgumentException(msg, "url");
+
+ if (protocols != null && protocols.Length > 0)
+ {
+ if (!checkProtocols(protocols, out msg))
+ throw new ArgumentException(msg, "protocols");
+
+ _protocols = protocols;
+ }
- if (url.Length == 0)
- throw new ArgumentException ("An empty string.", "url");
+ _base64Key = CreateBase64Key();
+ _client = true;
+ _logger = new Logger();
+ _message = messagec;
+ _secure = _uri.Scheme == "wss";
+ _waitTime = TimeSpan.FromSeconds(5);
- string msg;
- if (!url.TryCreateWebSocketUri (out _uri, out msg))
- throw new ArgumentException (msg, "url");
+ init();
+ }
- if (protocols != null && protocols.Length > 0) {
- if (!checkProtocols (protocols, out msg))
- throw new ArgumentException (msg, "protocols");
+ #endregion
- _protocols = protocols;
- }
+ #region Internal Properties
- _base64Key = CreateBase64Key ();
- _client = true;
- _logger = new Logger ();
- _message = messagec;
- _secure = _uri.Scheme == "wss";
- _waitTime = TimeSpan.FromSeconds (5);
+ internal CookieCollection CookieCollection
+ {
+ get
+ {
+ return _cookies;
+ }
+ }
- init ();
- }
+ // As server
+ internal Func CustomHandshakeRequestChecker
+ {
+ get
+ {
+ return _handshakeRequestChecker;
+ }
- #endregion
+ set
+ {
+ _handshakeRequestChecker = value;
+ }
+ }
- #region Internal Properties
+ internal bool HasMessage
+ {
+ get
+ {
+ lock (_forMessageEventQueue)
+ return _messageEventQueue.Count > 0;
+ }
+ }
- internal CookieCollection CookieCollection {
- get {
- return _cookies;
- }
- }
+ // As server
+ internal bool IgnoreExtensions
+ {
+ get
+ {
+ return _ignoreExtensions;
+ }
- // As server
- internal Func CustomHandshakeRequestChecker {
- get {
- return _handshakeRequestChecker;
- }
+ set
+ {
+ _ignoreExtensions = value;
+ }
+ }
- set {
- _handshakeRequestChecker = value;
- }
- }
+ internal bool IsConnected
+ {
+ get
+ {
+ return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing;
+ }
+ }
- internal bool HasMessage {
- get {
- lock (_forMessageEventQueue)
- return _messageEventQueue.Count > 0;
- }
- }
+ #endregion
+
+ #region Public Properties
+
+ ///
+ /// Gets or sets the compression method used to compress a message.
+ ///
+ ///
+ /// The set operation does nothing if the connection has already been
+ /// established or it is closing.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It specifies the compression method used to compress a message.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// The set operation is not available if this instance is not a client.
+ ///
+ public CompressionMethod Compression
+ {
+ get
+ {
+ return _compression;
+ }
- // As server
- internal bool IgnoreExtensions {
- get {
- return _ignoreExtensions;
- }
+ set
+ {
+ string msg = null;
+
+ if (!_client)
+ {
+ msg = "This instance is not a client.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
+
+ lock (_forState)
+ {
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
+
+ _compression = value;
+ }
+ }
+ }
- set {
- _ignoreExtensions = value;
- }
- }
+ ///
+ /// Gets the HTTP cookies included in the handshake request/response.
+ ///
+ ///
+ ///
+ /// An
+ /// instance.
+ ///
+ ///
+ /// It provides an enumerator which supports the iteration over
+ /// the collection of the cookies.
+ ///
+ ///
+ public IEnumerable Cookies
+ {
+ get
+ {
+ lock (_cookies.SyncRoot)
+ {
+ foreach (Cookie cookie in _cookies)
+ yield return cookie;
+ }
+ }
+ }
- internal bool IsConnected {
- get {
- return _readyState == WebSocketState.Open || _readyState == WebSocketState.Closing;
- }
- }
+ ///
+ /// Gets the credentials for the HTTP authentication (Basic/Digest).
+ ///
+ ///
+ ///
+ /// A that represents the credentials
+ /// used to authenticate the client.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public NetworkCredential Credentials
+ {
+ get
+ {
+ return _credentials;
+ }
+ }
- #endregion
+ ///
+ /// Gets or sets a value indicating whether a event
+ /// is emitted when a ping is received.
+ ///
+ ///
+ ///
+ /// true if this instance emits a event
+ /// when receives a ping; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ public bool EmitOnPing
+ {
+ get
+ {
+ return _emitOnPing;
+ }
- #region Public Properties
+ set
+ {
+ _emitOnPing = value;
+ }
+ }
- ///
- /// Gets or sets the compression method used to compress a message.
- ///
- ///
- /// The set operation does nothing if the connection has already been
- /// established or it is closing.
- ///
- ///
- ///
- /// One of the enum values.
- ///
- ///
- /// It specifies the compression method used to compress a message.
- ///
- ///
- /// The default value is .
- ///
- ///
- ///
- /// The set operation is not available if this instance is not a client.
- ///
- public CompressionMethod Compression {
- get {
- return _compression;
- }
+ ///
+ /// Gets or sets a value indicating whether the URL redirection for
+ /// the handshake request is allowed.
+ ///
+ ///
+ /// The set operation does nothing if the connection has already been
+ /// established or it is closing.
+ ///
+ ///
+ ///
+ /// true if this instance allows the URL redirection for
+ /// the handshake request; otherwise, false.
+ ///
+ ///
+ /// The default value is false.
+ ///
+ ///
+ ///
+ /// The set operation is not available if this instance is not a client.
+ ///
+ public bool EnableRedirection
+ {
+ get
+ {
+ return _enableRedirection;
+ }
- set {
- string msg = null;
+ set
+ {
+ string msg = null;
+
+ if (!_client)
+ {
+ msg = "This instance is not a client.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
+
+ lock (_forState)
+ {
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
+
+ _enableRedirection = value;
+ }
+ }
+ }
- if (!_client) {
- msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
+ ///
+ /// Gets the extensions selected by server.
+ ///
+ ///
+ /// A that will be a list of the extensions
+ /// negotiated between client and server, or an empty string if
+ /// not specified or selected.
+ ///
+ public string Extensions
+ {
+ get
+ {
+ return _extensions ?? String.Empty;
+ }
}
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ ///
+ /// Gets a value indicating whether the connection is alive.
+ ///
+ ///
+ /// The get operation returns the value by using a ping/pong
+ /// if the current state of the connection is Open.
+ ///
+ ///
+ /// true if the connection is alive; otherwise, false.
+ ///
+#if __PING_ASYNC
+ public bool IsAlive
+ {
+ get
+ {
+ return _isAlive;
+ }
+ }
+#else
+ public bool IsAlive
+ {
+ get
+ {
+ return ping(EmptyBytes);
+ }
+ }
+#endif
+ ///
+ /// Gets a value indicating whether a secure connection is used.
+ ///
+ ///
+ /// true if this instance uses a secure connection; otherwise,
+ /// false.
+ ///
+ public bool IsSecure
+ {
+ get
+ {
+ return _secure;
+ }
}
- lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
+ ///
+ /// Gets the logging function.
+ ///
+ ///
+ /// The default logging level is .
+ ///
+ ///
+ /// A that provides the logging function.
+ ///
+ public Logger Log
+ {
+ get
+ {
+ return _logger;
+ }
- _compression = value;
+ internal set
+ {
+ _logger = value;
+ }
}
- }
- }
- ///
- /// Gets the HTTP cookies included in the handshake request/response.
- ///
- ///
- ///
- /// An
- /// instance.
- ///
- ///
- /// It provides an enumerator which supports the iteration over
- /// the collection of the cookies.
- ///
- ///
- public IEnumerable Cookies {
- get {
- lock (_cookies.SyncRoot) {
- foreach (Cookie cookie in _cookies)
- yield return cookie;
- }
- }
- }
+ ///
+ /// Gets or sets the value of the HTTP Origin header to send with
+ /// the handshake request.
+ ///
+ ///
+ ///
+ /// The HTTP Origin header is defined in
+ ///
+ /// Section 7 of RFC 6454.
+ ///
+ ///
+ /// This instance sends the Origin header if this property has any.
+ ///
+ ///
+ /// The set operation does nothing if the connection has already been
+ /// established or it is closing.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the value of the Origin
+ /// header to send.
+ ///
+ ///
+ /// The syntax is <scheme>://<host>[:<port>].
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ ///
+ /// The set operation is not available if this instance is not a client.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is not an absolute URI string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The value specified for a set operation includes the path segments.
+ ///
+ ///
+ public string Origin
+ {
+ get
+ {
+ return _origin;
+ }
- ///
- /// Gets the credentials for the HTTP authentication (Basic/Digest).
- ///
- ///
- ///
- /// A that represents the credentials
- /// used to authenticate the client.
- ///
- ///
- /// The default value is .
- ///
- ///
- public NetworkCredential Credentials {
- get {
- return _credentials;
- }
- }
+ set
+ {
+ string msg = null;
+
+ if (!_client)
+ {
+ msg = "This instance is not a client.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (!value.IsNullOrEmpty())
+ {
+ Uri uri;
+ if (!Uri.TryCreate(value, UriKind.Absolute, out uri))
+ {
+ msg = "Not an absolute URI string.";
+ throw new ArgumentException(msg, "value");
+ }
+
+ if (uri.Segments.Length > 1)
+ {
+ msg = "It includes the path segments.";
+ throw new ArgumentException(msg, "value");
+ }
+ }
+
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
+
+ lock (_forState)
+ {
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
+
+ _origin = !value.IsNullOrEmpty() ? value.TrimEnd('/') : value;
+ }
+ }
+ }
- ///
- /// Gets or sets a value indicating whether a event
- /// is emitted when a ping is received.
- ///
- ///
- ///
- /// true if this instance emits a event
- /// when receives a ping; otherwise, false.
- ///
- ///
- /// The default value is false.
- ///
- ///
- public bool EmitOnPing {
- get {
- return _emitOnPing;
- }
-
- set {
- _emitOnPing = value;
- }
- }
+ ///
+ /// Gets the name of subprotocol selected by the server.
+ ///
+ ///
+ ///
+ /// A that will be one of the names of
+ /// subprotocols specified by client.
+ ///
+ ///
+ /// An empty string if not specified or selected.
+ ///
+ ///
+ public string Protocol
+ {
+ get
+ {
+ return _protocol ?? String.Empty;
+ }
- ///
- /// Gets or sets a value indicating whether the URL redirection for
- /// the handshake request is allowed.
- ///
- ///
- /// The set operation does nothing if the connection has already been
- /// established or it is closing.
- ///
- ///
- ///
- /// true if this instance allows the URL redirection for
- /// the handshake request; otherwise, false.
- ///
- ///
- /// The default value is false.
- ///
- ///
- ///
- /// The set operation is not available if this instance is not a client.
- ///
- public bool EnableRedirection {
- get {
- return _enableRedirection;
- }
+ internal set
+ {
+ _protocol = value;
+ }
+ }
- set {
- string msg = null;
+ ///
+ /// Gets the current state of the connection.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It indicates the current state of the connection.
+ ///
+ ///
+ /// The default value is .
+ ///
+ ///
+ public WebSocketState ReadyState
+ {
+ get
+ {
+ return _readyState;
+ }
+ }
- if (!_client) {
- msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
+ ///
+ /// Gets the configuration for secure connection.
+ ///
+ ///
+ /// This configuration will be referenced when attempts to connect,
+ /// so it must be configured before any connect method is called.
+ ///
+ ///
+ /// A that represents
+ /// the configuration used to establish a secure connection.
+ ///
+ ///
+ ///
+ /// This instance is not a client.
+ ///
+ ///
+ /// This instance does not use a secure connection.
+ ///
+ ///
+ public ClientSslConfiguration SslConfiguration
+ {
+ get
+ {
+ if (!_client)
+ {
+ var msg = "This instance is not a client.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (!_secure)
+ {
+ var msg = "This instance does not use a secure connection.";
+ throw new InvalidOperationException(msg);
+ }
+
+ return getSslConfiguration();
+ }
}
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ ///
+ /// Gets the URL to which to connect.
+ ///
+ ///
+ /// A that represents the URL to which to connect.
+ ///
+ public Uri Url
+ {
+ get
+ {
+ return _client ? _uri : _context.RequestUri;
+ }
}
- lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
+ ///
+ /// Gets or sets the time to wait for the response to the ping or close.
+ ///
+ ///
+ /// The set operation does nothing if the connection has already been
+ /// established or it is closing.
+ ///
+ ///
+ ///
+ /// A to wait for the response.
+ ///
+ ///
+ /// The default value is the same as 5 seconds if this instance is
+ /// a client.
+ ///
+ ///
+ ///
+ /// The value specified for a set operation is zero or less.
+ ///
+ public TimeSpan WaitTime
+ {
+ get
+ {
+ return _waitTime;
+ }
- _enableRedirection = value;
+ set
+ {
+ if (value <= TimeSpan.Zero)
+ throw new ArgumentOutOfRangeException("value", "Zero or less.");
+
+ string msg;
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
+
+ lock (_forState)
+ {
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
+
+ _waitTime = value;
+ }
+ }
}
- }
- }
-
- ///
- /// Gets the extensions selected by server.
- ///
- ///
- /// A that will be a list of the extensions
- /// negotiated between client and server, or an empty string if
- /// not specified or selected.
- ///
- public string Extensions {
- get {
- return _extensions ?? String.Empty;
- }
- }
- ///
- /// Gets a value indicating whether the connection is alive.
- ///
- ///
- /// The get operation returns the value by using a ping/pong
- /// if the current state of the connection is Open.
- ///
- ///
- /// true if the connection is alive; otherwise, false.
- ///
- public bool IsAlive {
- get {
- return ping (EmptyBytes);
- }
- }
+ #endregion
- ///
- /// Gets a value indicating whether a secure connection is used.
- ///
- ///
- /// true if this instance uses a secure connection; otherwise,
- /// false.
- ///
- public bool IsSecure {
- get {
- return _secure;
- }
- }
+ #region Public Events
- ///
- /// Gets the logging function.
- ///
- ///
- /// The default logging level is .
- ///
- ///
- /// A that provides the logging function.
- ///
- public Logger Log {
- get {
- return _logger;
- }
-
- internal set {
- _logger = value;
- }
- }
+ ///
+ /// Occurs when the WebSocket connection has been closed.
+ ///
+ public event EventHandler OnClose;
- ///
- /// Gets or sets the value of the HTTP Origin header to send with
- /// the handshake request.
- ///
- ///
- ///
- /// The HTTP Origin header is defined in
- ///
- /// Section 7 of RFC 6454.
- ///
- ///
- /// This instance sends the Origin header if this property has any.
- ///
- ///
- /// The set operation does nothing if the connection has already been
- /// established or it is closing.
- ///
- ///
- ///
- ///
- /// A that represents the value of the Origin
- /// header to send.
- ///
- ///
- /// The syntax is <scheme>://<host>[:<port>].
- ///
- ///
- /// The default value is .
- ///
- ///
- ///
- /// The set operation is not available if this instance is not a client.
- ///
- ///
- ///
- /// The value specified for a set operation is not an absolute URI string.
- ///
- ///
- /// -or-
- ///
- ///
- /// The value specified for a set operation includes the path segments.
- ///
- ///
- public string Origin {
- get {
- return _origin;
- }
+ ///
+ /// Occurs when the gets an error.
+ ///
+ public event EventHandler OnError;
- set {
- string msg = null;
+ ///
+ /// Occurs when the receives a message.
+ ///
+ public event EventHandler OnMessage;
- if (!_client) {
- msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
- }
+ ///
+ /// Occurs when the WebSocket connection has been established.
+ ///
+ public event EventHandler OnOpen;
- if (!value.IsNullOrEmpty ()) {
- Uri uri;
- if (!Uri.TryCreate (value, UriKind.Absolute, out uri)) {
- msg = "Not an absolute URI string.";
- throw new ArgumentException (msg, "value");
- }
+ #endregion
- if (uri.Segments.Length > 1) {
- msg = "It includes the path segments.";
- throw new ArgumentException (msg, "value");
- }
- }
+ #region Private Methods
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
+ // As server
+ private bool accept()
+ {
+ if (_readyState == WebSocketState.Open)
+ {
+ var msg = "The handshake request has already been accepted.";
+ _logger.Warn(msg);
- lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
+ return false;
+ }
- _origin = !value.IsNullOrEmpty () ? value.TrimEnd ('/') : value;
- }
- }
- }
+ lock (_forState)
+ {
+ if (_readyState == WebSocketState.Open)
+ {
+ var msg = "The handshake request has already been accepted.";
+ _logger.Warn(msg);
- ///
- /// Gets the name of subprotocol selected by the server.
- ///
- ///
- ///
- /// A that will be one of the names of
- /// subprotocols specified by client.
- ///
- ///
- /// An empty string if not specified or selected.
- ///
- ///
- public string Protocol {
- get {
- return _protocol ?? String.Empty;
- }
-
- internal set {
- _protocol = value;
- }
- }
+ return false;
+ }
- ///
- /// Gets the current state of the connection.
- ///
- ///
- ///
- /// One of the enum values.
- ///
- ///
- /// It indicates the current state of the connection.
- ///
- ///
- /// The default value is .
- ///
- ///
- public WebSocketState ReadyState {
- get {
- return _readyState;
- }
- }
+ if (_readyState == WebSocketState.Closing)
+ {
+ var msg = "The close process has set in.";
+ _logger.Error(msg);
- ///
- /// Gets the configuration for secure connection.
- ///
- ///
- /// This configuration will be referenced when attempts to connect,
- /// so it must be configured before any connect method is called.
- ///
- ///
- /// A that represents
- /// the configuration used to establish a secure connection.
- ///
- ///
- ///
- /// This instance is not a client.
- ///
- ///
- /// This instance does not use a secure connection.
- ///
- ///
- public ClientSslConfiguration SslConfiguration {
- get {
- if (!_client) {
- var msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
- }
+ msg = "An interruption has occurred while attempting to accept.";
+ error(msg, null);
- if (!_secure) {
- var msg = "This instance does not use a secure connection.";
- throw new InvalidOperationException (msg);
- }
+ return false;
+ }
- return getSslConfiguration ();
- }
- }
+ if (_readyState == WebSocketState.Closed)
+ {
+ var msg = "The connection has been closed.";
+ _logger.Error(msg);
- ///
- /// Gets the URL to which to connect.
- ///
- ///
- /// A that represents the URL to which to connect.
- ///
- public Uri Url {
- get {
- return _client ? _uri : _context.RequestUri;
- }
- }
+ msg = "An interruption has occurred while attempting to accept.";
+ error(msg, null);
- ///
- /// Gets or sets the time to wait for the response to the ping or close.
- ///
- ///
- /// The set operation does nothing if the connection has already been
- /// established or it is closing.
- ///
- ///
- ///
- /// A to wait for the response.
- ///
- ///
- /// The default value is the same as 5 seconds if this instance is
- /// a client.
- ///
- ///
- ///
- /// The value specified for a set operation is zero or less.
- ///
- public TimeSpan WaitTime {
- get {
- return _waitTime;
- }
-
- set {
- if (value <= TimeSpan.Zero)
- throw new ArgumentOutOfRangeException ("value", "Zero or less.");
-
- string msg;
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
-
- lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
-
- _waitTime = value;
- }
- }
- }
+ return false;
+ }
- #endregion
+ try
+ {
+ if (!acceptHandshake())
+ return false;
+ }
+ catch (Exception ex)
+ {
+ _logger.Fatal(ex.Message);
+ _logger.Debug(ex.ToString());
- #region Public Events
+ var msg = "An exception has occurred while attempting to accept.";
+ fatal(msg, ex);
- ///
- /// Occurs when the WebSocket connection has been closed.
- ///
- public event EventHandler OnClose;
+ return false;
+ }
- ///
- /// Occurs when the gets an error.
- ///
- public event EventHandler OnError;
+ _readyState = WebSocketState.Open;
+ return true;
+ }
+ }
- ///
- /// Occurs when the receives a message.
- ///
- public event EventHandler OnMessage;
+ // As server
+ private bool acceptHandshake()
+ {
+ _logger.Debug(
+ String.Format(
+ "A handshake request from {0}:\n{1}", _context.UserEndPoint, _context
+ )
+ );
- ///
- /// Occurs when the WebSocket connection has been established.
- ///
- public event EventHandler OnOpen;
+ string msg;
+ if (!checkHandshakeRequest(_context, out msg))
+ {
+ _logger.Error(msg);
- #endregion
+ refuseHandshake(
+ CloseStatusCode.ProtocolError,
+ "A handshake error has occurred while attempting to accept."
+ );
- #region Private Methods
+ return false;
+ }
- // As server
- private bool accept ()
- {
- if (_readyState == WebSocketState.Open) {
- var msg = "The handshake request has already been accepted.";
- _logger.Warn (msg);
+ if (!customCheckHandshakeRequest(_context, out msg))
+ {
+ _logger.Error(msg);
- return false;
- }
+ refuseHandshake(
+ CloseStatusCode.PolicyViolation,
+ "A handshake error has occurred while attempting to accept."
+ );
- lock (_forState) {
- if (_readyState == WebSocketState.Open) {
- var msg = "The handshake request has already been accepted.";
- _logger.Warn (msg);
+ return false;
+ }
- return false;
- }
+ _base64Key = _context.Headers["Sec-WebSocket-Key"];
- if (_readyState == WebSocketState.Closing) {
- var msg = "The close process has set in.";
- _logger.Error (msg);
+ if (_protocol != null)
+ {
+ var vals = _context.SecWebSocketProtocols;
+ processSecWebSocketProtocolClientHeader(vals);
+ }
- msg = "An interruption has occurred while attempting to accept.";
- error (msg, null);
+ if (!_ignoreExtensions)
+ {
+ var val = _context.Headers["Sec-WebSocket-Extensions"];
+ processSecWebSocketExtensionsClientHeader(val);
+ }
- return false;
+ return sendHttpResponse(createHandshakeResponse());
}
- if (_readyState == WebSocketState.Closed) {
- var msg = "The connection has been closed.";
- _logger.Error (msg);
-
- msg = "An interruption has occurred while attempting to accept.";
- error (msg, null);
-
- return false;
- }
+ private bool canSet(out string message)
+ {
+ message = null;
- try {
- if (!acceptHandshake ())
- return false;
- }
- catch (Exception ex) {
- _logger.Fatal (ex.Message);
- _logger.Debug (ex.ToString ());
+ if (_readyState == WebSocketState.Open)
+ {
+ message = "The connection has already been established.";
+ return false;
+ }
- var msg = "An exception has occurred while attempting to accept.";
- fatal (msg, ex);
+ if (_readyState == WebSocketState.Closing)
+ {
+ message = "The connection is closing.";
+ return false;
+ }
- return false;
+ return true;
}
- _readyState = WebSocketState.Open;
- return true;
- }
- }
-
- // As server
- private bool acceptHandshake ()
- {
- _logger.Debug (
- String.Format (
- "A handshake request from {0}:\n{1}", _context.UserEndPoint, _context
+ // As server
+ private bool checkHandshakeRequest(
+ WebSocketContext context, out string message
)
- );
+ {
+ message = null;
- string msg;
- if (!checkHandshakeRequest (_context, out msg)) {
- _logger.Error (msg);
+ if (!context.IsWebSocketRequest)
+ {
+ message = "Not a handshake request.";
+ return false;
+ }
- refuseHandshake (
- CloseStatusCode.ProtocolError,
- "A handshake error has occurred while attempting to accept."
- );
+ if (context.RequestUri == null)
+ {
+ message = "It specifies an invalid Request-URI.";
+ return false;
+ }
- return false;
- }
+ var headers = context.Headers;
- if (!customCheckHandshakeRequest (_context, out msg)) {
- _logger.Error (msg);
+ var key = headers["Sec-WebSocket-Key"];
+ if (key == null)
+ {
+ message = "It includes no Sec-WebSocket-Key header.";
+ return false;
+ }
- refuseHandshake (
- CloseStatusCode.PolicyViolation,
- "A handshake error has occurred while attempting to accept."
- );
+ if (key.Length == 0)
+ {
+ message = "It includes an invalid Sec-WebSocket-Key header.";
+ return false;
+ }
- return false;
- }
+ var version = headers["Sec-WebSocket-Version"];
+ if (version == null)
+ {
+ message = "It includes no Sec-WebSocket-Version header.";
+ return false;
+ }
- _base64Key = _context.Headers["Sec-WebSocket-Key"];
-
- if (_protocol != null) {
- var vals = _context.SecWebSocketProtocols;
- processSecWebSocketProtocolClientHeader (vals);
- }
+ if (version != _version)
+ {
+ message = "It includes an invalid Sec-WebSocket-Version header.";
+ return false;
+ }
- if (!_ignoreExtensions) {
- var val = _context.Headers["Sec-WebSocket-Extensions"];
- processSecWebSocketExtensionsClientHeader (val);
- }
+ var protocol = headers["Sec-WebSocket-Protocol"];
+ if (protocol != null && protocol.Length == 0)
+ {
+ message = "It includes an invalid Sec-WebSocket-Protocol header.";
+ return false;
+ }
- return sendHttpResponse (createHandshakeResponse ());
- }
+ if (!_ignoreExtensions)
+ {
+ var extensions = headers["Sec-WebSocket-Extensions"];
+ if (extensions != null && extensions.Length == 0)
+ {
+ message = "It includes an invalid Sec-WebSocket-Extensions header.";
+ return false;
+ }
+ }
- private bool canSet (out string message)
- {
- message = null;
+ return true;
+ }
- if (_readyState == WebSocketState.Open) {
- message = "The connection has already been established.";
- return false;
- }
+ // As client
+ private bool checkHandshakeResponse(HttpResponse response, out string message)
+ {
+ message = null;
- if (_readyState == WebSocketState.Closing) {
- message = "The connection is closing.";
- return false;
- }
+ if (response.IsRedirect)
+ {
+ message = "Indicates the redirection.";
+ return false;
+ }
- return true;
- }
+ if (response.IsUnauthorized)
+ {
+ message = "Requires the authentication.";
+ return false;
+ }
- // As server
- private bool checkHandshakeRequest (
- WebSocketContext context, out string message
- )
- {
- message = null;
-
- if (!context.IsWebSocketRequest) {
- message = "Not a handshake request.";
- return false;
- }
-
- if (context.RequestUri == null) {
- message = "It specifies an invalid Request-URI.";
- return false;
- }
-
- var headers = context.Headers;
-
- var key = headers["Sec-WebSocket-Key"];
- if (key == null) {
- message = "It includes no Sec-WebSocket-Key header.";
- return false;
- }
-
- if (key.Length == 0) {
- message = "It includes an invalid Sec-WebSocket-Key header.";
- return false;
- }
-
- var version = headers["Sec-WebSocket-Version"];
- if (version == null) {
- message = "It includes no Sec-WebSocket-Version header.";
- return false;
- }
-
- if (version != _version) {
- message = "It includes an invalid Sec-WebSocket-Version header.";
- return false;
- }
-
- var protocol = headers["Sec-WebSocket-Protocol"];
- if (protocol != null && protocol.Length == 0) {
- message = "It includes an invalid Sec-WebSocket-Protocol header.";
- return false;
- }
-
- if (!_ignoreExtensions) {
- var extensions = headers["Sec-WebSocket-Extensions"];
- if (extensions != null && extensions.Length == 0) {
- message = "It includes an invalid Sec-WebSocket-Extensions header.";
- return false;
- }
- }
-
- return true;
- }
+ if (!response.IsWebSocketResponse)
+ {
+ message = "Not a WebSocket handshake response.";
+ return false;
+ }
- // As client
- private bool checkHandshakeResponse (HttpResponse response, out string message)
- {
- message = null;
-
- if (response.IsRedirect) {
- message = "Indicates the redirection.";
- return false;
- }
-
- if (response.IsUnauthorized) {
- message = "Requires the authentication.";
- return false;
- }
-
- if (!response.IsWebSocketResponse) {
- message = "Not a WebSocket handshake response.";
- return false;
- }
-
- var headers = response.Headers;
- if (!validateSecWebSocketAcceptHeader (headers["Sec-WebSocket-Accept"])) {
- message = "Includes no Sec-WebSocket-Accept header, or it has an invalid value.";
- return false;
- }
-
- if (!validateSecWebSocketProtocolServerHeader (headers["Sec-WebSocket-Protocol"])) {
- message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value.";
- return false;
- }
-
- if (!validateSecWebSocketExtensionsServerHeader (headers["Sec-WebSocket-Extensions"])) {
- message = "Includes an invalid Sec-WebSocket-Extensions header.";
- return false;
- }
-
- if (!validateSecWebSocketVersionServerHeader (headers["Sec-WebSocket-Version"])) {
- message = "Includes an invalid Sec-WebSocket-Version header.";
- return false;
- }
-
- return true;
- }
+ var headers = response.Headers;
+ if (!validateSecWebSocketAcceptHeader(headers["Sec-WebSocket-Accept"]))
+ {
+ message = "Includes no Sec-WebSocket-Accept header, or it has an invalid value.";
+ return false;
+ }
- private static bool checkProtocols (string[] protocols, out string message)
- {
- message = null;
+ if (!validateSecWebSocketProtocolServerHeader(headers["Sec-WebSocket-Protocol"]))
+ {
+ message = "Includes no Sec-WebSocket-Protocol header, or it has an invalid value.";
+ return false;
+ }
- Func cond = protocol => protocol.IsNullOrEmpty ()
- || !protocol.IsToken ();
+ if (!validateSecWebSocketExtensionsServerHeader(headers["Sec-WebSocket-Extensions"]))
+ {
+ message = "Includes an invalid Sec-WebSocket-Extensions header.";
+ return false;
+ }
- if (protocols.Contains (cond)) {
- message = "It contains a value that is not a token.";
- return false;
- }
+ if (!validateSecWebSocketVersionServerHeader(headers["Sec-WebSocket-Version"]))
+ {
+ message = "Includes an invalid Sec-WebSocket-Version header.";
+ return false;
+ }
- if (protocols.ContainsTwice ()) {
- message = "It contains a value twice.";
- return false;
- }
+ return true;
+ }
- return true;
- }
+ private static bool checkProtocols(string[] protocols, out string message)
+ {
+ message = null;
- private bool checkReceivedFrame (WebSocketFrame frame, out string message)
- {
- message = null;
-
- var masked = frame.IsMasked;
- if (_client && masked) {
- message = "A frame from the server is masked.";
- return false;
- }
-
- if (!_client && !masked) {
- message = "A frame from a client is not masked.";
- return false;
- }
-
- if (_inContinuation && frame.IsData) {
- message = "A data frame has been received while receiving continuation frames.";
- return false;
- }
-
- if (frame.IsCompressed && _compression == CompressionMethod.None) {
- message = "A compressed frame has been received without any agreement for it.";
- return false;
- }
-
- if (frame.Rsv2 == Rsv.On) {
- message = "The RSV2 of a frame is non-zero without any negotiation for it.";
- return false;
- }
-
- if (frame.Rsv3 == Rsv.On) {
- message = "The RSV3 of a frame is non-zero without any negotiation for it.";
- return false;
- }
-
- return true;
- }
+ Func cond = protocol => protocol.IsNullOrEmpty()
+ || !protocol.IsToken();
- private void close (ushort code, string reason)
- {
- if (_readyState == WebSocketState.Closing) {
- _logger.Info ("The closing is already in progress.");
- return;
- }
-
- if (_readyState == WebSocketState.Closed) {
- _logger.Info ("The connection has already been closed.");
- return;
- }
-
- if (code == 1005) { // == no status
- close (PayloadData.Empty, true, true, false);
- return;
- }
-
- var send = !code.IsReserved ();
- close (new PayloadData (code, reason), send, send, false);
- }
+ if (protocols.Contains(cond))
+ {
+ message = "It contains a value that is not a token.";
+ return false;
+ }
- private void close (
- PayloadData payloadData, bool send, bool receive, bool received
- )
- {
- lock (_forState) {
- if (_readyState == WebSocketState.Closing) {
- _logger.Info ("The closing is already in progress.");
- return;
- }
+ if (protocols.ContainsTwice())
+ {
+ message = "It contains a value twice.";
+ return false;
+ }
- if (_readyState == WebSocketState.Closed) {
- _logger.Info ("The connection has already been closed.");
- return;
+ return true;
}
- send = send && _readyState == WebSocketState.Open;
- receive = send && receive;
+ private bool checkReceivedFrame(WebSocketFrame frame, out string message)
+ {
+ message = null;
- _readyState = WebSocketState.Closing;
- }
-
- _logger.Trace ("Begin closing the connection.");
+ var masked = frame.IsMasked;
+ if (_client && masked)
+ {
+ message = "A frame from the server is masked.";
+ return false;
+ }
- var res = closeHandshake (payloadData, send, receive, received);
- releaseResources ();
+ if (!_client && !masked)
+ {
+ message = "A frame from a client is not masked.";
+ return false;
+ }
- _logger.Trace ("End closing the connection.");
+ if (_inContinuation && frame.IsData)
+ {
+ message = "A data frame has been received while receiving continuation frames.";
+ return false;
+ }
- _readyState = WebSocketState.Closed;
+ if (frame.IsCompressed && _compression == CompressionMethod.None)
+ {
+ message = "A compressed frame has been received without any agreement for it.";
+ return false;
+ }
- var e = new CloseEventArgs (payloadData, res);
+ if (frame.Rsv2 == Rsv.On)
+ {
+ message = "The RSV2 of a frame is non-zero without any negotiation for it.";
+ return false;
+ }
- try {
- OnClose.Emit (this, e);
- }
- catch (Exception ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
- }
- }
+ if (frame.Rsv3 == Rsv.On)
+ {
+ message = "The RSV3 of a frame is non-zero without any negotiation for it.";
+ return false;
+ }
- private void closeAsync (ushort code, string reason)
- {
- if (_readyState == WebSocketState.Closing) {
- _logger.Info ("The closing is already in progress.");
- return;
- }
-
- if (_readyState == WebSocketState.Closed) {
- _logger.Info ("The connection has already been closed.");
- return;
- }
-
- if (code == 1005) { // == no status
- closeAsync (PayloadData.Empty, true, true, false);
- return;
- }
-
- var send = !code.IsReserved ();
- closeAsync (new PayloadData (code, reason), send, send, false);
- }
+ return true;
+ }
- private void closeAsync (
- PayloadData payloadData, bool send, bool receive, bool received
- )
- {
- Action closer = close;
- closer.BeginInvoke (
- payloadData, send, receive, received, ar => closer.EndInvoke (ar), null
- );
- }
+ private void close(ushort code, string reason)
+ {
+ if (_readyState == WebSocketState.Closing)
+ {
+ _logger.Info("The closing is already in progress.");
+ return;
+ }
- private bool closeHandshake (byte[] frameAsBytes, bool receive, bool received)
- {
- var sent = frameAsBytes != null && sendBytes (frameAsBytes);
+ if (_readyState == WebSocketState.Closed)
+ {
+ _logger.Info("The connection has already been closed.");
+ return;
+ }
- var wait = !received && sent && receive && _receivingExited != null;
- if (wait)
- received = _receivingExited.WaitOne (_waitTime);
+ if (code == 1005)
+ { // == no status
+ close(PayloadData.Empty, true, true, false);
+ return;
+ }
- var ret = sent && received;
+ var send = !code.IsReserved();
+ close(new PayloadData(code, reason), send, send, false);
+ }
- _logger.Debug (
- String.Format (
- "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received
+ private void close(
+ PayloadData payloadData, bool send, bool receive, bool received
)
- );
+ {
+ lock (_forState)
+ {
+ if (_readyState == WebSocketState.Closing)
+ {
+ _logger.Info("The closing is already in progress.");
+ return;
+ }
+
+ if (_readyState == WebSocketState.Closed)
+ {
+ _logger.Info("The connection has already been closed.");
+ return;
+ }
+
+ send = send && _readyState == WebSocketState.Open;
+ receive = send && receive;
+
+ _readyState = WebSocketState.Closing;
+ }
- return ret;
- }
+ _logger.Trace("Begin closing the connection.");
- private bool closeHandshake (
- PayloadData payloadData, bool send, bool receive, bool received
- )
- {
- var sent = false;
- if (send) {
- var frame = WebSocketFrame.CreateCloseFrame (payloadData, _client);
- sent = sendBytes (frame.ToArray ());
+ var res = closeHandshake(payloadData, send, receive, received);
+ releaseResources();
- if (_client)
- frame.Unmask ();
- }
+ _logger.Trace("End closing the connection.");
- var wait = !received && sent && receive && _receivingExited != null;
- if (wait)
- received = _receivingExited.WaitOne (_waitTime);
+ _readyState = WebSocketState.Closed;
- var ret = sent && received;
+ var e = new CloseEventArgs(payloadData, res);
- _logger.Debug (
- String.Format (
- "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received
- )
- );
+ try
+ {
+ OnClose.Emit(this, e);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex.Message);
+ _logger.Debug(ex.ToString());
+ }
+ }
- return ret;
- }
+ private void closeAsync(ushort code, string reason)
+ {
+ if (_readyState == WebSocketState.Closing)
+ {
+ _logger.Info("The closing is already in progress.");
+ return;
+ }
- // As client
- private bool connect ()
- {
- if (_readyState == WebSocketState.Open) {
- var msg = "The connection has already been established.";
- _logger.Warn (msg);
+ if (_readyState == WebSocketState.Closed)
+ {
+ _logger.Info("The connection has already been closed.");
+ return;
+ }
- return false;
- }
+ if (code == 1005)
+ { // == no status
+ closeAsync(PayloadData.Empty, true, true, false);
+ return;
+ }
- lock (_forState) {
- if (_readyState == WebSocketState.Open) {
- var msg = "The connection has already been established.";
- _logger.Warn (msg);
+ var send = !code.IsReserved();
+ closeAsync(new PayloadData(code, reason), send, send, false);
+ }
- return false;
+ private void closeAsync(
+ PayloadData payloadData, bool send, bool receive, bool received
+ )
+ {
+ Action closer = close;
+ closer.BeginInvoke(
+ payloadData, send, receive, received, ar => closer.EndInvoke(ar), null
+ );
}
- if (_readyState == WebSocketState.Closing) {
- var msg = "The close process has set in.";
- _logger.Error (msg);
+ private bool closeHandshake(byte[] frameAsBytes, bool receive, bool received)
+ {
+ var sent = frameAsBytes != null && sendBytes(frameAsBytes);
+
+ var wait = !received && sent && receive && _receivingExited != null;
+ if (wait)
+ received = _receivingExited.WaitOne(_waitTime);
- msg = "An interruption has occurred while attempting to connect.";
- error (msg, null);
+ var ret = sent && received;
+
+ _logger.Debug(
+ String.Format(
+ "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received
+ )
+ );
- return false;
+ return ret;
}
- if (_retryCountForConnect > _maxRetryCountForConnect) {
- var msg = "An opportunity for reconnecting has been lost.";
- _logger.Error (msg);
+ private bool closeHandshake(
+ PayloadData payloadData, bool send, bool receive, bool received
+ )
+ {
+ var sent = false;
+ if (send)
+ {
+ var frame = WebSocketFrame.CreateCloseFrame(payloadData, _client);
+ sent = sendBytes(frame.ToArray());
+
+ if (_client)
+ frame.Unmask();
+ }
- msg = "An interruption has occurred while attempting to connect.";
- error (msg, null);
+ var wait = !received && sent && receive && _receivingExited != null;
+ if (wait)
+ received = _receivingExited.WaitOne(_waitTime);
- return false;
- }
+ var ret = sent && received;
- _readyState = WebSocketState.Connecting;
+ _logger.Debug(
+ String.Format(
+ "Was clean?: {0}\n sent: {1}\n received: {2}", ret, sent, received
+ )
+ );
- try {
- doHandshake ();
+ return ret;
}
- catch (Exception ex) {
- _retryCountForConnect++;
- _logger.Fatal (ex.Message);
- _logger.Debug (ex.ToString ());
+ // As client
+ private bool connect()
+ {
+ if (_readyState == WebSocketState.Open)
+ {
+ var msg = "The connection has already been established.";
+ _logger.Warn(msg);
- var msg = "An exception has occurred while attempting to connect.";
- fatal (msg, ex);
+ return false;
+ }
- return false;
- }
+ lock (_forState)
+ {
+ if (_readyState == WebSocketState.Open)
+ {
+ var msg = "The connection has already been established.";
+ _logger.Warn(msg);
- _retryCountForConnect = 1;
- _readyState = WebSocketState.Open;
+ return false;
+ }
- return true;
- }
- }
+ if (_readyState == WebSocketState.Closing)
+ {
+ var msg = "The close process has set in.";
+ _logger.Error(msg);
- // As client
- private string createExtensions ()
- {
- var buff = new StringBuilder (80);
+ msg = "An interruption has occurred while attempting to connect.";
+ error(msg, null);
- if (_compression != CompressionMethod.None) {
- var str = _compression.ToExtensionString (
- "server_no_context_takeover", "client_no_context_takeover");
+ return false;
+ }
- buff.AppendFormat ("{0}, ", str);
- }
+ if (_retryCountForConnect > _maxRetryCountForConnect)
+ {
+ var msg = "An opportunity for reconnecting has been lost.";
+ _logger.Error(msg);
- var len = buff.Length;
- if (len > 2) {
- buff.Length = len - 2;
- return buff.ToString ();
- }
+ msg = "An interruption has occurred while attempting to connect.";
+ error(msg, null);
- return null;
- }
+ return false;
+ }
- // As server
- private HttpResponse createHandshakeFailureResponse (HttpStatusCode code)
- {
- var ret = HttpResponse.CreateCloseResponse (code);
- ret.Headers["Sec-WebSocket-Version"] = _version;
+ _readyState = WebSocketState.Connecting;
- return ret;
- }
+ try
+ {
+ doHandshake();
+ }
+ catch (Exception ex)
+ {
+ //_retryCountForConnect++;
- // As client
- private HttpRequest createHandshakeRequest ()
- {
- var ret = HttpRequest.CreateWebSocketRequest (_uri);
+ _logger.Fatal(ex.Message);
+ _logger.Debug(ex.ToString());
- var headers = ret.Headers;
- if (!_origin.IsNullOrEmpty ())
- headers["Origin"] = _origin;
+ var msg = "An exception has occurred while attempting to connect.";
+ fatal(msg, ex);
- headers["Sec-WebSocket-Key"] = _base64Key;
+ return false;
+ }
- _protocolsRequested = _protocols != null;
- if (_protocolsRequested)
- headers["Sec-WebSocket-Protocol"] = _protocols.ToString (", ");
+ _retryCountForConnect = 1;
+ _readyState = WebSocketState.Open;
- _extensionsRequested = _compression != CompressionMethod.None;
- if (_extensionsRequested)
- headers["Sec-WebSocket-Extensions"] = createExtensions ();
+ return true;
+ }
+ }
- headers["Sec-WebSocket-Version"] = _version;
+ // As client
+ private string createExtensions()
+ {
+ var buff = new StringBuilder(80);
- AuthenticationResponse authRes = null;
- if (_authChallenge != null && _credentials != null) {
- authRes = new AuthenticationResponse (_authChallenge, _credentials, _nonceCount);
- _nonceCount = authRes.NonceCount;
- }
- else if (_preAuth) {
- authRes = new AuthenticationResponse (_credentials);
- }
+ if (_compression != CompressionMethod.None)
+ {
+ var str = _compression.ToExtensionString(
+ "server_no_context_takeover", "client_no_context_takeover");
- if (authRes != null)
- headers["Authorization"] = authRes.ToString ();
+ buff.AppendFormat("{0}, ", str);
+ }
- if (_cookies.Count > 0)
- ret.SetCookies (_cookies);
+ var len = buff.Length;
+ if (len > 2)
+ {
+ buff.Length = len - 2;
+ return buff.ToString();
+ }
- return ret;
- }
+ return null;
+ }
- // As server
- private HttpResponse createHandshakeResponse ()
- {
- var ret = HttpResponse.CreateWebSocketResponse ();
+ // As server
+ private HttpResponse createHandshakeFailureResponse(HttpStatusCode code)
+ {
+ var ret = HttpResponse.CreateCloseResponse(code);
+ ret.Headers["Sec-WebSocket-Version"] = _version;
- var headers = ret.Headers;
- headers["Sec-WebSocket-Accept"] = CreateResponseKey (_base64Key);
+ return ret;
+ }
- if (_protocol != null)
- headers["Sec-WebSocket-Protocol"] = _protocol;
+ // As client
+ private HttpRequest createHandshakeRequest()
+ {
+ var ret = HttpRequest.CreateWebSocketRequest(_uri);
- if (_extensions != null)
- headers["Sec-WebSocket-Extensions"] = _extensions;
+ var headers = ret.Headers;
+ if (!_origin.IsNullOrEmpty())
+ headers["Origin"] = _origin;
- if (_cookies.Count > 0)
- ret.SetCookies (_cookies);
+ headers["Sec-WebSocket-Key"] = _base64Key;
- return ret;
- }
+ _protocolsRequested = _protocols != null;
+ if (_protocolsRequested)
+ headers["Sec-WebSocket-Protocol"] = _protocols.ToString(", ");
- // As server
- private bool customCheckHandshakeRequest (
- WebSocketContext context, out string message
- )
- {
- message = null;
+ _extensionsRequested = _compression != CompressionMethod.None;
+ if (_extensionsRequested)
+ headers["Sec-WebSocket-Extensions"] = createExtensions();
- if (_handshakeRequestChecker == null)
- return true;
+ headers["Sec-WebSocket-Version"] = _version;
- message = _handshakeRequestChecker (context);
- return message == null;
- }
+ AuthenticationResponse authRes = null;
+ if (_authChallenge != null && _credentials != null)
+ {
+ authRes = new AuthenticationResponse(_authChallenge, _credentials, _nonceCount);
+ _nonceCount = authRes.NonceCount;
+ }
+ else if (_preAuth)
+ {
+ authRes = new AuthenticationResponse(_credentials);
+ }
- private MessageEventArgs dequeueFromMessageEventQueue ()
- {
- lock (_forMessageEventQueue)
- return _messageEventQueue.Count > 0 ? _messageEventQueue.Dequeue () : null;
- }
+ if (authRes != null)
+ headers["Authorization"] = authRes.ToString();
- // As client
- private void doHandshake ()
- {
- setClientStream ();
- var res = sendHandshakeRequest ();
+ if (_cookies.Count > 0)
+ ret.SetCookies(_cookies);
- string msg;
- if (!checkHandshakeResponse (res, out msg))
- throw new WebSocketException (CloseStatusCode.ProtocolError, msg);
+ return ret;
+ }
- if (_protocolsRequested)
- _protocol = res.Headers["Sec-WebSocket-Protocol"];
+ // As server
+ private HttpResponse createHandshakeResponse()
+ {
+ var ret = HttpResponse.CreateWebSocketResponse();
- if (_extensionsRequested)
- processSecWebSocketExtensionsServerHeader (res.Headers["Sec-WebSocket-Extensions"]);
+ var headers = ret.Headers;
+ headers["Sec-WebSocket-Accept"] = CreateResponseKey(_base64Key);
- processCookies (res.Cookies);
- }
+ if (_protocol != null)
+ headers["Sec-WebSocket-Protocol"] = _protocol;
- private void enqueueToMessageEventQueue (MessageEventArgs e)
- {
- lock (_forMessageEventQueue)
- _messageEventQueue.Enqueue (e);
- }
+ if (_extensions != null)
+ headers["Sec-WebSocket-Extensions"] = _extensions;
- private void error (string message, Exception exception)
- {
- try {
- OnError.Emit (this, new ErrorEventArgs (message, exception));
- }
- catch (Exception ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
- }
- }
+ if (_cookies.Count > 0)
+ ret.SetCookies(_cookies);
- private void fatal (string message, Exception exception)
- {
- var code = exception is WebSocketException
- ? ((WebSocketException) exception).Code
- : CloseStatusCode.Abnormal;
+ return ret;
+ }
- fatal (message, (ushort) code);
- }
+ // As server
+ private bool customCheckHandshakeRequest(
+ WebSocketContext context, out string message
+ )
+ {
+ message = null;
- private void fatal (string message, ushort code)
- {
- var payload = new PayloadData (code, message);
- close (payload, !code.IsReserved (), false, false);
- }
+ if (_handshakeRequestChecker == null)
+ return true;
- private void fatal (string message, CloseStatusCode code)
- {
- fatal (message, (ushort) code);
- }
+ message = _handshakeRequestChecker(context);
+ return message == null;
+ }
- private ClientSslConfiguration getSslConfiguration ()
- {
- if (_sslConfig == null)
- _sslConfig = new ClientSslConfiguration (_uri.DnsSafeHost);
+ private MessageEventArgs dequeueFromMessageEventQueue()
+ {
+ lock (_forMessageEventQueue)
+ return _messageEventQueue.Count > 0 ? _messageEventQueue.Dequeue() : null;
+ }
- return _sslConfig;
- }
+ // As client
+ private void doHandshake()
+ {
+ setClientStream();
+ var res = sendHandshakeRequest();
- private void init ()
- {
- _compression = CompressionMethod.None;
- _cookies = new CookieCollection ();
- _forPing = new object ();
- _forSend = new object ();
- _forState = new object ();
- _messageEventQueue = new Queue ();
- _forMessageEventQueue = ((ICollection) _messageEventQueue).SyncRoot;
- _readyState = WebSocketState.Connecting;
- }
+ string msg;
+ if (!checkHandshakeResponse(res, out msg))
+ throw new WebSocketException(CloseStatusCode.ProtocolError, msg);
- private void message ()
- {
- MessageEventArgs e = null;
- lock (_forMessageEventQueue) {
- if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
- return;
+ if (_protocolsRequested)
+ _protocol = res.Headers["Sec-WebSocket-Protocol"];
- _inMessage = true;
- e = _messageEventQueue.Dequeue ();
- }
+ if (_extensionsRequested)
+ processSecWebSocketExtensionsServerHeader(res.Headers["Sec-WebSocket-Extensions"]);
- _message (e);
- }
+ processCookies(res.Cookies);
+ }
- private void messagec (MessageEventArgs e)
- {
- do {
- try {
- OnMessage.Emit (this, e);
+ private void enqueueToMessageEventQueue(MessageEventArgs e)
+ {
+ lock (_forMessageEventQueue)
+ _messageEventQueue.Enqueue(e);
}
- catch (Exception ex) {
- _logger.Error (ex.ToString ());
- error ("An error has occurred during an OnMessage event.", ex);
+
+ private void error(string message, Exception exception)
+ {
+ try
+ {
+ OnError.Emit(this, new ErrorEventArgs(message, exception));
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex.Message);
+ _logger.Debug(ex.ToString());
+ }
}
- lock (_forMessageEventQueue) {
- if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) {
- _inMessage = false;
- break;
- }
+ private void fatal(string message, Exception exception)
+ {
+ var code = exception is WebSocketException
+ ? ((WebSocketException)exception).Code
+ : CloseStatusCode.Abnormal;
- e = _messageEventQueue.Dequeue ();
+ fatal(message, (ushort)code);
}
- }
- while (true);
- }
-
- private void messages (MessageEventArgs e)
- {
- try {
- OnMessage.Emit (this, e);
- }
- catch (Exception ex) {
- _logger.Error (ex.ToString ());
- error ("An error has occurred during an OnMessage event.", ex);
- }
- lock (_forMessageEventQueue) {
- if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) {
- _inMessage = false;
- return;
+ private void fatal(string message, ushort code)
+ {
+ var payload = new PayloadData(code, message);
+ close(payload, !code.IsReserved(), false, false);
}
- e = _messageEventQueue.Dequeue ();
- }
+ private void fatal(string message, CloseStatusCode code)
+ {
+ fatal(message, (ushort)code);
+ }
- ThreadPool.QueueUserWorkItem (state => messages (e));
- }
+ private ClientSslConfiguration getSslConfiguration()
+ {
+ if (_sslConfig == null)
+ _sslConfig = new ClientSslConfiguration(_uri.DnsSafeHost);
- private void open ()
- {
- _inMessage = true;
- startReceiving ();
- try {
- OnOpen.Emit (this, EventArgs.Empty);
- }
- catch (Exception ex) {
- _logger.Error (ex.ToString ());
- error ("An error has occurred during the OnOpen event.", ex);
- }
-
- MessageEventArgs e = null;
- lock (_forMessageEventQueue) {
- if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open) {
- _inMessage = false;
- return;
- }
-
- e = _messageEventQueue.Dequeue ();
- }
-
- _message.BeginInvoke (e, ar => _message.EndInvoke (ar), null);
- }
+ return _sslConfig;
+ }
- private bool ping (byte[] data)
- {
- if (_readyState != WebSocketState.Open)
- return false;
+ private void init()
+ {
+ _compression = CompressionMethod.None;
+ _cookies = new CookieCollection();
+ _forPing = new object();
+ _forSend = new object();
+ _forState = new object();
+ _messageEventQueue = new Queue();
+ _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;
+ _readyState = WebSocketState.Connecting;
+#if __PING_ASYNC
+ _isAlive = true;
+ _isPinged = true;
+#endif
+ }
- var pongReceived = _pongReceived;
- if (pongReceived == null)
- return false;
+ private void message()
+ {
+ MessageEventArgs e = null;
+ lock (_forMessageEventQueue)
+ {
+ if (_inMessage || _messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
+ return;
- lock (_forPing) {
- try {
- pongReceived.Reset ();
- if (!send (Fin.Final, Opcode.Ping, data, false))
- return false;
+ _inMessage = true;
+ e = _messageEventQueue.Dequeue();
+ }
- return pongReceived.WaitOne (_waitTime);
- }
- catch (ObjectDisposedException) {
- return false;
+ _message(e);
}
- }
- }
- private bool processCloseFrame (WebSocketFrame frame)
- {
- var payload = frame.PayloadData;
- close (payload, !payload.HasReservedCode, false, true);
+ private void messagec(MessageEventArgs e)
+ {
+ do
+ {
+ try
+ {
+ OnMessage.Emit(this, e);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex.ToString());
+ error("An error has occurred during an OnMessage event.", ex);
+ }
+
+ lock (_forMessageEventQueue)
+ {
+ if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
+ {
+ _inMessage = false;
+ break;
+ }
+
+ e = _messageEventQueue.Dequeue();
+ }
+ }
+ while (true);
+ }
- return false;
- }
+ private void messages(MessageEventArgs e)
+ {
+ try
+ {
+ OnMessage.Emit(this, e);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex.ToString());
+ error("An error has occurred during an OnMessage event.", ex);
+ }
- // As client
- private void processCookies (CookieCollection cookies)
- {
- if (cookies.Count == 0)
- return;
+ lock (_forMessageEventQueue)
+ {
+ if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
+ {
+ _inMessage = false;
+ return;
+ }
- _cookies.SetOrRemove (cookies);
- }
+ e = _messageEventQueue.Dequeue();
+ }
- private bool processDataFrame (WebSocketFrame frame)
- {
- enqueueToMessageEventQueue (
- frame.IsCompressed
- ? new MessageEventArgs (
- frame.Opcode, frame.PayloadData.ApplicationData.Decompress (_compression))
- : new MessageEventArgs (frame));
+ ThreadPool.QueueUserWorkItem(state => messages(e));
+ }
- return true;
- }
+ private void open()
+ {
+ _inMessage = true;
+ startReceiving();
+ try
+ {
+ OnOpen.Emit(this, EventArgs.Empty);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex.ToString());
+ error("An error has occurred during the OnOpen event.", ex);
+ }
- private bool processFragmentFrame (WebSocketFrame frame)
- {
- if (!_inContinuation) {
- // Must process first fragment.
- if (frame.IsContinuation)
- return true;
+ MessageEventArgs e = null;
+ lock (_forMessageEventQueue)
+ {
+ if (_messageEventQueue.Count == 0 || _readyState != WebSocketState.Open)
+ {
+ _inMessage = false;
+ return;
+ }
- _fragmentsOpcode = frame.Opcode;
- _fragmentsCompressed = frame.IsCompressed;
- _fragmentsBuffer = new MemoryStream ();
- _inContinuation = true;
- }
+ e = _messageEventQueue.Dequeue();
+ }
- _fragmentsBuffer.WriteBytes (frame.PayloadData.ApplicationData, 1024);
- if (frame.IsFinal) {
- using (_fragmentsBuffer) {
- var data = _fragmentsCompressed
- ? _fragmentsBuffer.DecompressToArray (_compression)
- : _fragmentsBuffer.ToArray ();
+ _message.BeginInvoke(e, ar => _message.EndInvoke(ar), null);
+ }
- enqueueToMessageEventQueue (new MessageEventArgs (_fragmentsOpcode, data));
+ private bool ping(byte[] data)
+ {
+ if (_readyState != WebSocketState.Open)
+ return false;
+
+ var pongReceived = _pongReceived;
+ if (pongReceived == null)
+ return false;
+
+ lock (_forPing)
+ {
+ try
+ {
+ pongReceived.Reset();
+ if (!send(Fin.Final, Opcode.Ping, data, false))
+ return false;
+
+ return pongReceived.WaitOne(_waitTime);
+ }
+ catch (ObjectDisposedException)
+ {
+ return false;
+ }
+ }
}
- _fragmentsBuffer = null;
- _inContinuation = false;
- }
+ private bool processCloseFrame(WebSocketFrame frame)
+ {
+ var payload = frame.PayloadData;
+ close(payload, !payload.HasReservedCode, false, true);
- return true;
- }
+ return false;
+ }
- private bool processPingFrame (WebSocketFrame frame)
- {
- _logger.Trace ("A ping was received.");
+ // As client
+ private void processCookies(CookieCollection cookies)
+ {
+ if (cookies.Count == 0)
+ return;
+
+ _cookies.SetOrRemove(cookies);
+ }
- var pong = WebSocketFrame.CreatePongFrame (frame.PayloadData, _client);
+ private bool processDataFrame(WebSocketFrame frame)
+ {
+ enqueueToMessageEventQueue(
+ frame.IsCompressed
+ ? new MessageEventArgs(
+ frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression))
+ : new MessageEventArgs(frame));
- lock (_forState) {
- if (_readyState != WebSocketState.Open) {
- _logger.Error ("The connection is closing.");
- return true;
+ return true;
}
- if (!sendBytes (pong.ToArray ()))
- return false;
- }
+ private bool processFragmentFrame(WebSocketFrame frame)
+ {
+ if (!_inContinuation)
+ {
+ // Must process first fragment.
+ if (frame.IsContinuation)
+ return true;
+
+ _fragmentsOpcode = frame.Opcode;
+ _fragmentsCompressed = frame.IsCompressed;
+ _fragmentsBuffer = new MemoryStream();
+ _inContinuation = true;
+ }
- _logger.Trace ("A pong to this ping has been sent.");
+ _fragmentsBuffer.WriteBytes(frame.PayloadData.ApplicationData, 1024);
+ if (frame.IsFinal)
+ {
+ using (_fragmentsBuffer)
+ {
+ var data = _fragmentsCompressed
+ ? _fragmentsBuffer.DecompressToArray(_compression)
+ : _fragmentsBuffer.ToArray();
- if (_emitOnPing) {
- if (_client)
- pong.Unmask ();
+ enqueueToMessageEventQueue(new MessageEventArgs(_fragmentsOpcode, data));
+ }
- enqueueToMessageEventQueue (new MessageEventArgs (frame));
- }
+ _fragmentsBuffer = null;
+ _inContinuation = false;
+ }
- return true;
- }
+ return true;
+ }
- private bool processPongFrame (WebSocketFrame frame)
- {
- _logger.Trace ("A pong was received.");
+ private bool processPingFrame(WebSocketFrame frame)
+ {
+ _logger.Trace("A ping was received.");
- try {
- _pongReceived.Set ();
- }
- catch (NullReferenceException ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
+ var pong = WebSocketFrame.CreatePongFrame(frame.PayloadData, _client);
- return false;
- }
- catch (ObjectDisposedException ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
+ lock (_forState)
+ {
+ if (_readyState != WebSocketState.Open)
+ {
+ _logger.Error("The connection is closing.");
+ return true;
+ }
- return false;
- }
+ if (!sendBytes(pong.ToArray()))
+ return false;
+ }
- _logger.Trace ("It has been signaled.");
+ _logger.Trace("A pong to this ping has been sent.");
- return true;
- }
+ if (_emitOnPing)
+ {
+ if (_client)
+ pong.Unmask();
- private bool processReceivedFrame (WebSocketFrame frame)
- {
- string msg;
- if (!checkReceivedFrame (frame, out msg))
- throw new WebSocketException (CloseStatusCode.ProtocolError, msg);
-
- frame.Unmask ();
- return frame.IsFragment
- ? processFragmentFrame (frame)
- : frame.IsData
- ? processDataFrame (frame)
- : frame.IsPing
- ? processPingFrame (frame)
- : frame.IsPong
- ? processPongFrame (frame)
- : frame.IsClose
- ? processCloseFrame (frame)
- : processUnsupportedFrame (frame);
- }
+ enqueueToMessageEventQueue(new MessageEventArgs(frame));
+ }
- // As server
- private void processSecWebSocketExtensionsClientHeader (string value)
- {
- if (value == null)
- return;
+ return true;
+ }
- var buff = new StringBuilder (80);
- var comp = false;
+ private bool processPongFrame(WebSocketFrame frame)
+ {
+ _logger.Trace("A pong was received.");
- foreach (var elm in value.SplitHeaderValue (',')) {
- var extension = elm.Trim ();
- if (extension.Length == 0)
- continue;
+ try
+ {
+ _pongReceived.Set();
+ }
+ catch (NullReferenceException ex)
+ {
+ _logger.Error(ex.Message);
+ _logger.Debug(ex.ToString());
- if (!comp) {
- if (extension.IsCompressionExtension (CompressionMethod.Deflate)) {
- _compression = CompressionMethod.Deflate;
+ return false;
+ }
+ catch (ObjectDisposedException ex)
+ {
+ _logger.Error(ex.Message);
+ _logger.Debug(ex.ToString());
- buff.AppendFormat (
- "{0}, ",
- _compression.ToExtensionString (
- "client_no_context_takeover", "server_no_context_takeover"
- )
- );
+ return false;
+ }
- comp = true;
- }
+ _logger.Trace("It has been signaled.");
+
+ return true;
}
- }
- var len = buff.Length;
- if (len <= 2)
- return;
+ private bool processReceivedFrame(WebSocketFrame frame)
+ {
+ string msg;
+ if (!checkReceivedFrame(frame, out msg))
+ throw new WebSocketException(CloseStatusCode.ProtocolError, msg);
+
+ frame.Unmask();
+ return frame.IsFragment
+ ? processFragmentFrame(frame)
+ : frame.IsData
+ ? processDataFrame(frame)
+ : frame.IsPing
+ ? processPingFrame(frame)
+ : frame.IsPong
+ ? processPongFrame(frame)
+ : frame.IsClose
+ ? processCloseFrame(frame)
+ : processUnsupportedFrame(frame);
+ }
- buff.Length = len - 2;
- _extensions = buff.ToString ();
- }
+ // As server
+ private void processSecWebSocketExtensionsClientHeader(string value)
+ {
+ if (value == null)
+ return;
- // As client
- private void processSecWebSocketExtensionsServerHeader (string value)
- {
- if (value == null) {
- _compression = CompressionMethod.None;
- return;
- }
+ var buff = new StringBuilder(80);
+ var comp = false;
+
+ foreach (var elm in value.SplitHeaderValue(','))
+ {
+ var extension = elm.Trim();
+ if (extension.Length == 0)
+ continue;
+
+ if (!comp)
+ {
+ if (extension.IsCompressionExtension(CompressionMethod.Deflate))
+ {
+ _compression = CompressionMethod.Deflate;
+
+ buff.AppendFormat(
+ "{0}, ",
+ _compression.ToExtensionString(
+ "client_no_context_takeover", "server_no_context_takeover"
+ )
+ );
+
+ comp = true;
+ }
+ }
+ }
- _extensions = value;
- }
+ var len = buff.Length;
+ if (len <= 2)
+ return;
- // As server
- private void processSecWebSocketProtocolClientHeader (
- IEnumerable values
- )
- {
- if (values.Contains (val => val == _protocol))
- return;
+ buff.Length = len - 2;
+ _extensions = buff.ToString();
+ }
- _protocol = null;
- }
+ // As client
+ private void processSecWebSocketExtensionsServerHeader(string value)
+ {
+ if (value == null)
+ {
+ _compression = CompressionMethod.None;
+ return;
+ }
- private bool processUnsupportedFrame (WebSocketFrame frame)
- {
- _logger.Fatal ("An unsupported frame:" + frame.PrintToString (false));
- fatal ("There is no way to handle it.", CloseStatusCode.PolicyViolation);
+ _extensions = value;
+ }
- return false;
- }
+ // As server
+ private void processSecWebSocketProtocolClientHeader(
+ IEnumerable values
+ )
+ {
+ if (values.Contains(val => val == _protocol))
+ return;
- // As server
- private void refuseHandshake (CloseStatusCode code, string reason)
- {
- _readyState = WebSocketState.Closing;
+ _protocol = null;
+ }
- var res = createHandshakeFailureResponse (HttpStatusCode.BadRequest);
- sendHttpResponse (res);
+ private bool processUnsupportedFrame(WebSocketFrame frame)
+ {
+ _logger.Fatal("An unsupported frame:" + frame.PrintToString(false));
+ fatal("There is no way to handle it.", CloseStatusCode.PolicyViolation);
- releaseServerResources ();
+ return false;
+ }
- _readyState = WebSocketState.Closed;
+ // As server
+ private void refuseHandshake(CloseStatusCode code, string reason)
+ {
+ _readyState = WebSocketState.Closing;
- var e = new CloseEventArgs ((ushort) code, reason, false);
+ var res = createHandshakeFailureResponse(HttpStatusCode.BadRequest);
+ sendHttpResponse(res);
- try {
- OnClose.Emit (this, e);
- }
- catch (Exception ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
- }
- }
+ releaseServerResources();
- // As client
- private void releaseClientResources ()
- {
- if (_stream != null) {
- _stream.Dispose ();
- _stream = null;
- }
-
- if (_tcpClient != null) {
- _tcpClient.Close ();
- _tcpClient = null;
- }
- }
+ _readyState = WebSocketState.Closed;
- private void releaseCommonResources ()
- {
- if (_fragmentsBuffer != null) {
- _fragmentsBuffer.Dispose ();
- _fragmentsBuffer = null;
- _inContinuation = false;
- }
-
- if (_pongReceived != null) {
- _pongReceived.Close ();
- _pongReceived = null;
- }
-
- if (_receivingExited != null) {
- _receivingExited.Close ();
- _receivingExited = null;
- }
- }
+ var e = new CloseEventArgs((ushort)code, reason, false);
- private void releaseResources ()
- {
- if (_client)
- releaseClientResources ();
- else
- releaseServerResources ();
+ try
+ {
+ OnClose.Emit(this, e);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex.Message);
+ _logger.Debug(ex.ToString());
+ }
+ }
- releaseCommonResources ();
- }
+ // As client
+ private void releaseClientResources()
+ {
+ if (_stream != null)
+ {
+ _stream.Dispose();
+ _stream = null;
+ }
- // As server
- private void releaseServerResources ()
- {
- if (_closeContext == null)
- return;
+ if (_tcpClient != null)
+ {
+ _tcpClient.Close();
+ _tcpClient = null;
+ }
+ }
- _closeContext ();
- _closeContext = null;
- _stream = null;
- _context = null;
- }
+ private void releaseCommonResources()
+ {
+ if (_fragmentsBuffer != null)
+ {
+ _fragmentsBuffer.Dispose();
+ _fragmentsBuffer = null;
+ _inContinuation = false;
+ }
- private bool send (Opcode opcode, Stream stream)
- {
- lock (_forSend) {
- var src = stream;
- var compressed = false;
- var sent = false;
- try {
- if (_compression != CompressionMethod.None) {
- stream = stream.Compress (_compression);
- compressed = true;
- }
+ if (_pongReceived != null)
+ {
+ _pongReceived.Close();
+ _pongReceived = null;
+ }
- sent = send (opcode, stream, compressed);
- if (!sent)
- error ("A send has been interrupted.", null);
- }
- catch (Exception ex) {
- _logger.Error (ex.ToString ());
- error ("An error has occurred during a send.", ex);
+ if (_receivingExited != null)
+ {
+ _receivingExited.Close();
+ _receivingExited = null;
+ }
}
- finally {
- if (compressed)
- stream.Dispose ();
- src.Dispose ();
+ private void releaseResources()
+ {
+ if (_client)
+ releaseClientResources();
+ else
+ releaseServerResources();
+
+ releaseCommonResources();
}
- return sent;
- }
- }
+ // As server
+ private void releaseServerResources()
+ {
+ if (_closeContext == null)
+ return;
- private bool send (Opcode opcode, Stream stream, bool compressed)
- {
- var len = stream.Length;
- if (len == 0)
- return send (Fin.Final, opcode, EmptyBytes, false);
-
- var quo = len / FragmentLength;
- var rem = (int) (len % FragmentLength);
-
- byte[] buff = null;
- if (quo == 0) {
- buff = new byte[rem];
- return stream.Read (buff, 0, rem) == rem
- && send (Fin.Final, opcode, buff, compressed);
- }
-
- if (quo == 1 && rem == 0) {
- buff = new byte[FragmentLength];
- return stream.Read (buff, 0, FragmentLength) == FragmentLength
- && send (Fin.Final, opcode, buff, compressed);
- }
-
- /* Send fragments */
-
- // Begin
- buff = new byte[FragmentLength];
- var sent = stream.Read (buff, 0, FragmentLength) == FragmentLength
- && send (Fin.More, opcode, buff, compressed);
-
- if (!sent)
- return false;
-
- var n = rem == 0 ? quo - 2 : quo - 1;
- for (long i = 0; i < n; i++) {
- sent = stream.Read (buff, 0, FragmentLength) == FragmentLength
- && send (Fin.More, Opcode.Cont, buff, false);
-
- if (!sent)
- return false;
- }
-
- // End
- if (rem == 0)
- rem = FragmentLength;
- else
- buff = new byte[rem];
-
- return stream.Read (buff, 0, rem) == rem
- && send (Fin.Final, Opcode.Cont, buff, false);
- }
+ _closeContext();
+ _closeContext = null;
+ //_stream.Dispose();
+ _stream = null;
+ _context = null;
+ }
- private bool send (Fin fin, Opcode opcode, byte[] data, bool compressed)
- {
- lock (_forState) {
- if (_readyState != WebSocketState.Open) {
- _logger.Error ("The connection is closing.");
- return false;
+ private bool send(Opcode opcode, Stream stream)
+ {
+ lock (_forSend)
+ {
+ var src = stream;
+ var compressed = false;
+ var sent = false;
+ try
+ {
+ if (_compression != CompressionMethod.None)
+ {
+ stream = stream.Compress(_compression);
+ compressed = true;
+ }
+
+ sent = send(opcode, stream, compressed);
+ if (!sent)
+ error("A send has been interrupted.", null);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex.ToString());
+ error("An error has occurred during a send.", ex);
+ }
+ finally
+ {
+ if (compressed)
+ stream.Dispose();
+
+ src.Dispose();
+ }
+
+ return sent;
+ }
}
- var frame = new WebSocketFrame (fin, opcode, data, compressed, _client);
- return sendBytes (frame.ToArray ());
- }
- }
+ private bool send(Opcode opcode, Stream stream, bool compressed)
+ {
+ var len = stream.Length;
+ if (len == 0)
+ return send(Fin.Final, opcode, EmptyBytes, false);
+
+ var quo = len / FragmentLength;
+ var rem = (int)(len % FragmentLength);
+
+ byte[] buff = null;
+ if (quo == 0)
+ {
+ buff = new byte[rem];
+ return stream.Read(buff, 0, rem) == rem
+ && send(Fin.Final, opcode, buff, compressed);
+ }
- private void sendAsync (Opcode opcode, Stream stream, Action completed)
- {
- Func sender = send;
- sender.BeginInvoke (
- opcode,
- stream,
- ar => {
- try {
- var sent = sender.EndInvoke (ar);
- if (completed != null)
- completed (sent);
- }
- catch (Exception ex) {
- _logger.Error (ex.ToString ());
- error (
- "An error has occurred during the callback for an async send.",
- ex
- );
- }
- },
- null
- );
- }
+ if (quo == 1 && rem == 0)
+ {
+ buff = new byte[FragmentLength];
+ return stream.Read(buff, 0, FragmentLength) == FragmentLength
+ && send(Fin.Final, opcode, buff, compressed);
+ }
- private bool sendBytes (byte[] bytes)
- {
- try {
- _stream.Write (bytes, 0, bytes.Length);
- }
- catch (Exception ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
+ /* Send fragments */
- return false;
- }
+ // Begin
+ buff = new byte[FragmentLength];
+ var sent = stream.Read(buff, 0, FragmentLength) == FragmentLength
+ && send(Fin.More, opcode, buff, compressed);
- return true;
- }
+ if (!sent)
+ return false;
- // As client
- private HttpResponse sendHandshakeRequest ()
- {
- var req = createHandshakeRequest ();
- var res = sendHttpRequest (req, 90000);
- if (res.IsUnauthorized) {
- var chal = res.Headers["WWW-Authenticate"];
- _logger.Warn (String.Format ("Received an authentication requirement for '{0}'.", chal));
- if (chal.IsNullOrEmpty ()) {
- _logger.Error ("No authentication challenge is specified.");
- return res;
- }
-
- _authChallenge = AuthenticationChallenge.Parse (chal);
- if (_authChallenge == null) {
- _logger.Error ("An invalid authentication challenge is specified.");
- return res;
- }
-
- if (_credentials != null &&
- (!_preAuth || _authChallenge.Scheme == AuthenticationSchemes.Digest)) {
- if (res.HasConnectionClose) {
- releaseClientResources ();
- setClientStream ();
- }
-
- var authRes = new AuthenticationResponse (_authChallenge, _credentials, _nonceCount);
- _nonceCount = authRes.NonceCount;
- req.Headers["Authorization"] = authRes.ToString ();
- res = sendHttpRequest (req, 15000);
- }
- }
-
- if (res.IsRedirect) {
- var url = res.Headers["Location"];
- _logger.Warn (String.Format ("Received a redirection to '{0}'.", url));
- if (_enableRedirection) {
- if (url.IsNullOrEmpty ()) {
- _logger.Error ("No url to redirect is located.");
- return res;
- }
+ var n = rem == 0 ? quo - 2 : quo - 1;
+ for (long i = 0; i < n; i++)
+ {
+ sent = stream.Read(buff, 0, FragmentLength) == FragmentLength
+ && send(Fin.More, Opcode.Cont, buff, false);
- Uri uri;
- string msg;
- if (!url.TryCreateWebSocketUri (out uri, out msg)) {
- _logger.Error ("An invalid url to redirect is located: " + msg);
- return res;
- }
+ if (!sent)
+ return false;
+ }
- releaseClientResources ();
+ // End
+ if (rem == 0)
+ rem = FragmentLength;
+ else
+ buff = new byte[rem];
- _uri = uri;
- _secure = uri.Scheme == "wss";
+ return stream.Read(buff, 0, rem) == rem
+ && send(Fin.Final, Opcode.Cont, buff, false);
+ }
- setClientStream ();
- return sendHandshakeRequest ();
+ private bool send(Fin fin, Opcode opcode, byte[] data, bool compressed)
+ {
+ lock (_forState)
+ {
+ if (_readyState != WebSocketState.Open)
+ {
+ _logger.Error("The connection is closing.");
+ return false;
+ }
+
+ var frame = new WebSocketFrame(fin, opcode, data, compressed, _client);
+ return sendBytes(frame.ToArray());
+ }
}
- }
- return res;
- }
+ private void sendAsync(Opcode opcode, Stream stream, Action completed)
+ {
+ Func sender = send;
+ sender.BeginInvoke(
+ opcode,
+ stream,
+ ar =>
+ {
+ try
+ {
+ var sent = sender.EndInvoke(ar);
+ if (completed != null)
+ completed(sent);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex.ToString());
+ error(
+ "An error has occurred during the callback for an async send.",
+ ex
+ );
+ }
+ },
+ null
+ );
+ }
- // As client
- private HttpResponse sendHttpRequest (HttpRequest request, int millisecondsTimeout)
- {
- _logger.Debug ("A request to the server:\n" + request.ToString ());
- var res = request.GetResponse (_stream, millisecondsTimeout);
- _logger.Debug ("A response to this request:\n" + res.ToString ());
+ private bool sendBytes(byte[] bytes)
+ {
+ try
+ {
+ _stream.Write(bytes, 0, bytes.Length);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex.Message);
+ _logger.Debug(ex.ToString());
- return res;
- }
+ return false;
+ }
- // As server
- private bool sendHttpResponse (HttpResponse response)
- {
- _logger.Debug (
- String.Format (
- "A response to {0}:\n{1}", _context.UserEndPoint, response
- )
- );
+ return true;
+ }
- return sendBytes (response.ToByteArray ());
- }
+ // As client
+ private HttpResponse sendHandshakeRequest()
+ {
+ var req = createHandshakeRequest();
+ var res = sendHttpRequest(req, 90000);
+ if (res.IsUnauthorized)
+ {
+ var chal = res.Headers["WWW-Authenticate"];
+ _logger.Warn(String.Format("Received an authentication requirement for '{0}'.", chal));
+ if (chal.IsNullOrEmpty())
+ {
+ _logger.Error("No authentication challenge is specified.");
+ return res;
+ }
+
+ _authChallenge = AuthenticationChallenge.Parse(chal);
+ if (_authChallenge == null)
+ {
+ _logger.Error("An invalid authentication challenge is specified.");
+ return res;
+ }
+
+ if (_credentials != null &&
+ (!_preAuth || _authChallenge.Scheme == AuthenticationSchemes.Digest))
+ {
+ if (res.HasConnectionClose)
+ {
+ releaseClientResources();
+ setClientStream();
+ }
+
+ var authRes = new AuthenticationResponse(_authChallenge, _credentials, _nonceCount);
+ _nonceCount = authRes.NonceCount;
+ req.Headers["Authorization"] = authRes.ToString();
+ res = sendHttpRequest(req, 15000);
+ }
+ }
- // As client
- private void sendProxyConnectRequest ()
- {
- var req = HttpRequest.CreateConnectRequest (_uri);
- var res = sendHttpRequest (req, 90000);
- if (res.IsProxyAuthenticationRequired) {
- var chal = res.Headers["Proxy-Authenticate"];
- _logger.Warn (
- String.Format ("Received a proxy authentication requirement for '{0}'.", chal));
-
- if (chal.IsNullOrEmpty ())
- throw new WebSocketException ("No proxy authentication challenge is specified.");
-
- var authChal = AuthenticationChallenge.Parse (chal);
- if (authChal == null)
- throw new WebSocketException ("An invalid proxy authentication challenge is specified.");
-
- if (_proxyCredentials != null) {
- if (res.HasConnectionClose) {
- releaseClientResources ();
- _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port);
- _stream = _tcpClient.GetStream ();
- }
-
- var authRes = new AuthenticationResponse (authChal, _proxyCredentials, 0);
- req.Headers["Proxy-Authorization"] = authRes.ToString ();
- res = sendHttpRequest (req, 15000);
- }
-
- if (res.IsProxyAuthenticationRequired)
- throw new WebSocketException ("A proxy authentication is required.");
- }
-
- if (res.StatusCode[0] != '2')
- throw new WebSocketException (
- "The proxy has failed a connection to the requested host and port.");
- }
+ if (res.IsRedirect)
+ {
+ var url = res.Headers["Location"];
+ _logger.Warn(String.Format("Received a redirection to '{0}'.", url));
+ if (_enableRedirection)
+ {
+ if (url.IsNullOrEmpty())
+ {
+ _logger.Error("No url to redirect is located.");
+ return res;
+ }
+
+ Uri uri;
+ string msg;
+ if (!url.TryCreateWebSocketUri(out uri, out msg))
+ {
+ _logger.Error("An invalid url to redirect is located: " + msg);
+ return res;
+ }
+
+ releaseClientResources();
+
+ _uri = uri;
+ _secure = uri.Scheme == "wss";
+
+ setClientStream();
+ return sendHandshakeRequest();
+ }
+ }
- // As client
- private void setClientStream ()
- {
- if (_proxyUri != null) {
- _tcpClient = new TcpClient (_proxyUri.DnsSafeHost, _proxyUri.Port);
- _stream = _tcpClient.GetStream ();
- sendProxyConnectRequest ();
- }
- else {
- _tcpClient = new TcpClient (_uri.DnsSafeHost, _uri.Port);
- _stream = _tcpClient.GetStream ();
- }
-
- if (_secure) {
- var conf = getSslConfiguration ();
- var host = conf.TargetHost;
- if (host != _uri.DnsSafeHost)
- throw new WebSocketException (
- CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified.");
-
- try {
- var sslStream = new SslStream (
- _stream,
- false,
- conf.ServerCertificateValidationCallback,
- conf.ClientCertificateSelectionCallback);
-
- sslStream.AuthenticateAsClient (
- host,
- conf.ClientCertificates,
- conf.EnabledSslProtocols,
- conf.CheckCertificateRevocation);
-
- _stream = sslStream;
- }
- catch (Exception ex) {
- throw new WebSocketException (CloseStatusCode.TlsHandshakeFailure, ex);
- }
- }
- }
+ return res;
+ }
- private void startReceiving ()
- {
- if (_messageEventQueue.Count > 0)
- _messageEventQueue.Clear ();
-
- _pongReceived = new ManualResetEvent (false);
- _receivingExited = new ManualResetEvent (false);
-
- Action receive = null;
- receive =
- () =>
- WebSocketFrame.ReadFrameAsync (
- _stream,
- false,
- frame => {
- if (!processReceivedFrame (frame) || _readyState == WebSocketState.Closed) {
- var exited = _receivingExited;
- if (exited != null)
- exited.Set ();
+ // As client
+ private HttpResponse sendHttpRequest(HttpRequest request, int millisecondsTimeout)
+ {
+ _logger.Debug("A request to the server:\n" + request.ToString());
+ var res = request.GetResponse(_stream, millisecondsTimeout);
+ _logger.Debug("A response to this request:\n" + res.ToString());
- return;
- }
+ return res;
+ }
- // Receive next asap because the Ping or Close needs a response to it.
- receive ();
+ // As server
+ private bool sendHttpResponse(HttpResponse response)
+ {
+ _logger.Debug(
+ String.Format(
+ "A response to {0}:\n{1}", _context.UserEndPoint, response
+ )
+ );
- if (_inMessage || !HasMessage || _readyState != WebSocketState.Open)
- return;
+ return sendBytes(response.ToByteArray());
+ }
- message ();
- },
- ex => {
- _logger.Fatal (ex.ToString ());
- fatal ("An exception has occurred while receiving.", ex);
+ // As client
+ private void sendProxyConnectRequest()
+ {
+ var req = HttpRequest.CreateConnectRequest(_uri);
+ var res = sendHttpRequest(req, 90000);
+ if (res.IsProxyAuthenticationRequired)
+ {
+ var chal = res.Headers["Proxy-Authenticate"];
+ _logger.Warn(
+ String.Format("Received a proxy authentication requirement for '{0}'.", chal));
+
+ if (chal.IsNullOrEmpty())
+ throw new WebSocketException("No proxy authentication challenge is specified.");
+
+ var authChal = AuthenticationChallenge.Parse(chal);
+ if (authChal == null)
+ throw new WebSocketException("An invalid proxy authentication challenge is specified.");
+
+ if (_proxyCredentials != null)
+ {
+ if (res.HasConnectionClose)
+ {
+ releaseClientResources();
+ _tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
+ _stream = _tcpClient.GetStream();
+ }
+
+ var authRes = new AuthenticationResponse(authChal, _proxyCredentials, 0);
+ req.Headers["Proxy-Authorization"] = authRes.ToString();
+ res = sendHttpRequest(req, 15000);
+ }
+
+ if (res.IsProxyAuthenticationRequired)
+ throw new WebSocketException("A proxy authentication is required.");
}
- );
- receive ();
- }
+ if (res.StatusCode[0] != '2')
+ throw new WebSocketException(
+ "The proxy has failed a connection to the requested host and port.");
+ }
- // As client
- private bool validateSecWebSocketAcceptHeader (string value)
- {
- return value != null && value == CreateResponseKey (_base64Key);
- }
+ // As client
+ private void setClientStream()
+ {
+ if (_proxyUri != null)
+ {
+ _tcpClient = new TcpClient(_proxyUri.DnsSafeHost, _proxyUri.Port);
+ _stream = _tcpClient.GetStream();
+ sendProxyConnectRequest();
+ }
+ else
+ {
+ _tcpClient = new TcpClient(_uri.DnsSafeHost, _uri.Port);
+ _stream = _tcpClient.GetStream();
+ }
- // As client
- private bool validateSecWebSocketExtensionsServerHeader (string value)
- {
- if (value == null)
- return true;
+ if (_secure)
+ {
+ var conf = getSslConfiguration();
+ var host = conf.TargetHost;
+ if (host != _uri.DnsSafeHost)
+ throw new WebSocketException(
+ CloseStatusCode.TlsHandshakeFailure, "An invalid host name is specified.");
+
+ try
+ {
+ var sslStream = new SslStream(
+ _stream,
+ false,
+ conf.ServerCertificateValidationCallback,
+ conf.ClientCertificateSelectionCallback);
+
+ sslStream.AuthenticateAsClient(
+ host,
+ conf.ClientCertificates,
+ conf.EnabledSslProtocols,
+ conf.CheckCertificateRevocation);
+
+ _stream = sslStream;
+ }
+ catch (Exception ex)
+ {
+ throw new WebSocketException(CloseStatusCode.TlsHandshakeFailure, ex);
+ }
+ }
+ }
+
+ private void startReceiving()
+ {
+ if (_messageEventQueue.Count > 0)
+ _messageEventQueue.Clear();
+
+ _pongReceived = new ManualResetEvent(false);
+ _receivingExited = new ManualResetEvent(false);
+
+ Action receive = null;
+ receive =
+ () =>
+ WebSocketFrame.ReadFrameAsync(
+ _stream,
+ false,
+ frame =>
+ {
+ if (!processReceivedFrame(frame) || _readyState == WebSocketState.Closed)
+ {
+ var exited = _receivingExited;
+ if (exited != null)
+ exited.Set();
+
+ return;
+ }
+
+ // Receive next asap because the Ping or Close needs a response to it.
+ receive();
+
+ if (_inMessage || !HasMessage || _readyState != WebSocketState.Open)
+ return;
+
+ message();
+ },
+ ex =>
+ {
+ _logger.Error(ex.ToString());
+ fatal("An exception has occurred while receiving.", ex);
+ }
+ );
+
+ receive();
+ }
- if (value.Length == 0)
- return false;
+ // As client
+ private bool validateSecWebSocketAcceptHeader(string value)
+ {
+ return value != null && value == CreateResponseKey(_base64Key);
+ }
- if (!_extensionsRequested)
- return false;
+ // As client
+ private bool validateSecWebSocketExtensionsServerHeader(string value)
+ {
+ if (value == null)
+ return true;
+
+ if (value.Length == 0)
+ return false;
+
+ if (!_extensionsRequested)
+ return false;
+
+ var comp = _compression != CompressionMethod.None;
+ foreach (var e in value.SplitHeaderValue(','))
+ {
+ var ext = e.Trim();
+ if (comp && ext.IsCompressionExtension(_compression))
+ {
+ if (!ext.Contains("server_no_context_takeover"))
+ {
+ _logger.Error("The server hasn't sent back 'server_no_context_takeover'.");
+ return false;
+ }
+
+ if (!ext.Contains("client_no_context_takeover"))
+ _logger.Warn("The server hasn't sent back 'client_no_context_takeover'.");
+
+ var method = _compression.ToExtensionString();
+ var invalid =
+ ext.SplitHeaderValue(';').Contains(
+ t =>
+ {
+ t = t.Trim();
+ return t != method
+ && t != "server_no_context_takeover"
+ && t != "client_no_context_takeover";
+ }
+ );
+
+ if (invalid)
+ return false;
+ }
+ else
+ {
+ return false;
+ }
+ }
- var comp = _compression != CompressionMethod.None;
- foreach (var e in value.SplitHeaderValue (',')) {
- var ext = e.Trim ();
- if (comp && ext.IsCompressionExtension (_compression)) {
- if (!ext.Contains ("server_no_context_takeover")) {
- _logger.Error ("The server hasn't sent back 'server_no_context_takeover'.");
- return false;
- }
-
- if (!ext.Contains ("client_no_context_takeover"))
- _logger.Warn ("The server hasn't sent back 'client_no_context_takeover'.");
-
- var method = _compression.ToExtensionString ();
- var invalid =
- ext.SplitHeaderValue (';').Contains (
- t => {
- t = t.Trim ();
- return t != method
- && t != "server_no_context_takeover"
- && t != "client_no_context_takeover";
- }
- );
+ return true;
+ }
- if (invalid)
- return false;
+ // As client
+ private bool validateSecWebSocketProtocolServerHeader(string value)
+ {
+ if (value == null)
+ return !_protocolsRequested;
+
+ if (value.Length == 0)
+ return false;
+
+ return _protocolsRequested && _protocols.Contains(p => p == value);
}
- else {
- return false;
+
+ // As client
+ private bool validateSecWebSocketVersionServerHeader(string value)
+ {
+ return value == null || value == _version;
}
- }
- return true;
- }
+ #endregion
- // As client
- private bool validateSecWebSocketProtocolServerHeader (string value)
- {
- if (value == null)
- return !_protocolsRequested;
+ #region Internal Methods
- if (value.Length == 0)
- return false;
+ // As server
+ internal void Close(HttpResponse response)
+ {
+ _readyState = WebSocketState.Closing;
- return _protocolsRequested && _protocols.Contains (p => p == value);
- }
+ sendHttpResponse(response);
+ releaseServerResources();
- // As client
- private bool validateSecWebSocketVersionServerHeader (string value)
- {
- return value == null || value == _version;
- }
+ _readyState = WebSocketState.Closed;
+ }
+
+ // As server
+ internal void Close(HttpStatusCode code)
+ {
+ Close(createHandshakeFailureResponse(code));
+ }
- #endregion
+ // As server
+ internal void Close(PayloadData payloadData, byte[] frameAsBytes)
+ {
+ lock (_forState)
+ {
+ if (_readyState == WebSocketState.Closing)
+ {
+ _logger.Info("The closing is already in progress.");
+ return;
+ }
+
+ if (_readyState == WebSocketState.Closed)
+ {
+ _logger.Info("The connection has already been closed.");
+ return;
+ }
+
+ _readyState = WebSocketState.Closing;
+ }
- #region Internal Methods
+ _logger.Trace("Begin closing the connection.");
- // As server
- internal void Close (HttpResponse response)
- {
- _readyState = WebSocketState.Closing;
+ var sent = frameAsBytes != null && sendBytes(frameAsBytes);
+ var received = sent && _receivingExited != null
+ ? _receivingExited.WaitOne(_waitTime)
+ : false;
- sendHttpResponse (response);
- releaseServerResources ();
+ var res = sent && received;
- _readyState = WebSocketState.Closed;
- }
+ _logger.Debug(
+ String.Format(
+ "Was clean?: {0}\n sent: {1}\n received: {2}", res, sent, received
+ )
+ );
- // As server
- internal void Close (HttpStatusCode code)
- {
- Close (createHandshakeFailureResponse (code));
- }
+ releaseServerResources();
+ releaseCommonResources();
- // As server
- internal void Close (PayloadData payloadData, byte[] frameAsBytes)
- {
- lock (_forState) {
- if (_readyState == WebSocketState.Closing) {
- _logger.Info ("The closing is already in progress.");
- return;
+ _logger.Trace("End closing the connection.");
+
+ _readyState = WebSocketState.Closed;
+
+ var e = new CloseEventArgs(payloadData, res);
+
+ try
+ {
+ OnClose.Emit(this, e);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error(ex.Message);
+ _logger.Debug(ex.ToString());
+ }
+ }
+
+ // As client
+ internal static string CreateBase64Key()
+ {
+ var src = new byte[16];
+ RandomNumber.GetBytes(src);
+
+ return Convert.ToBase64String(src);
}
- if (_readyState == WebSocketState.Closed) {
- _logger.Info ("The connection has already been closed.");
- return;
+ internal static string CreateResponseKey(string base64Key)
+ {
+ var buff = new StringBuilder(base64Key, 64);
+ buff.Append(_guid);
+ SHA1 sha1 = new SHA1CryptoServiceProvider();
+ var src = sha1.ComputeHash(buff.ToString().GetUTF8EncodedBytes());
+
+ return Convert.ToBase64String(src);
}
- _readyState = WebSocketState.Closing;
- }
+ // As server
+ internal void InternalAccept()
+ {
+ try
+ {
+ if (!acceptHandshake())
+ return;
+ }
+ catch (Exception ex)
+ {
+ _logger.Fatal(ex.Message);
+ _logger.Debug(ex.ToString());
+
+ var msg = "An exception has occurred while attempting to accept.";
+ fatal(msg, ex);
- _logger.Trace ("Begin closing the connection.");
+ return;
+ }
- var sent = frameAsBytes != null && sendBytes (frameAsBytes);
- var received = sent && _receivingExited != null
- ? _receivingExited.WaitOne (_waitTime)
- : false;
+ _readyState = WebSocketState.Open;
+
+ open();
+ }
- var res = sent && received;
+ // As server
+#if __PING_ASYNC
+ internal async void PingAsync(byte[] frameAsBytes, TimeSpan timeout, Action callback)
+ {
+ var b = await pingAsync(frameAsBytes, timeout);
+ if (b == true) _isAlive = true;
+ else b = false;
+ //Console.WriteLine("{0} iiiiiiiii{1}iiiiiiiiiiiiiiiiiiiii",DateTime.Now.ToString("hh:MM:ss"), _isAlive);
+ if (null != callback) callback(b);
+ }
+ private async Task pingAsync(byte[] frameAsBytes, TimeSpan timeout)
+ {
+ if (_readyState != WebSocketState.Open)
+ return false;
+ if (_isPinged == false) return false;
+ var pongReceived = _pongReceived;
+ if (pongReceived == null)
+ return false;
+ return await Task.Run(() =>
+ {
+ lock (_forPing)
+ {
+ try
+ {
+ pongReceived.Reset();
+
+ lock (_forState)
+ {
+ if (_readyState != WebSocketState.Open)
+ return false;
+
+ if (!sendBytes(frameAsBytes))
+ return false;
+ }
+
+ return pongReceived.WaitOne(timeout);
+
+ }
+ catch (ObjectDisposedException)
+ {
+ return false;
+ }
+ finally
+ {
+ _isPinged = true;
+ }
+ }
+ });
- _logger.Debug (
- String.Format (
- "Was clean?: {0}\n sent: {1}\n received: {2}", res, sent, received
+ }
+#endif
+ internal bool Ping(byte[] frameAsBytes, TimeSpan timeout)
+ {
+ if (_readyState != WebSocketState.Open)
+ return false;
+
+ var pongReceived = _pongReceived;
+ if (pongReceived == null)
+ return false;
+
+ lock (_forPing)
+ {
+ try
+ {
+ pongReceived.Reset();
+
+ lock (_forState)
+ {
+ if (_readyState != WebSocketState.Open)
+ return false;
+
+ if (!sendBytes(frameAsBytes))
+ return false;
+ }
+
+ return pongReceived.WaitOne(timeout);
+ }
+ catch (ObjectDisposedException)
+ {
+ return false;
+ }
+ }
+ }
+ // As server
+ internal void Send(
+ Opcode opcode, byte[] data, Dictionary cache
)
- );
+ {
+ lock (_forSend)
+ {
+ lock (_forState)
+ {
+ if (_readyState != WebSocketState.Open)
+ {
+ _logger.Error("The connection is closing.");
+ return;
+ }
+
+ byte[] found;
+ if (!cache.TryGetValue(_compression, out found))
+ {
+ found = new WebSocketFrame(
+ Fin.Final,
+ opcode,
+ data.Compress(_compression),
+ _compression != CompressionMethod.None,
+ false
+ )
+ .ToArray();
- releaseServerResources ();
- releaseCommonResources ();
+ cache.Add(_compression, found);
+ }
- _logger.Trace ("End closing the connection.");
+ sendBytes(found);
+ }
+ }
+ }
- _readyState = WebSocketState.Closed;
+ // As server
+ internal void Send(
+ Opcode opcode, Stream stream, Dictionary cache
+ )
+ {
+ lock (_forSend)
+ {
+ Stream found;
+ if (!cache.TryGetValue(_compression, out found))
+ {
+ found = stream.Compress(_compression);
+ cache.Add(_compression, found);
+ }
+ else
+ {
+ found.Position = 0;
+ }
+
+ send(opcode, found, _compression != CompressionMethod.None);
+ }
+ }
- var e = new CloseEventArgs (payloadData, res);
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Accepts the handshake request.
+ ///
+ ///
+ /// This method does nothing if the handshake request has already been
+ /// accepted.
+ ///
+ ///
+ ///
+ /// This instance is a client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The close process is in progress.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The connection has already been closed.
+ ///
+ ///
+ public void Accept()
+ {
+ if (_client)
+ {
+ var msg = "This instance is a client.";
+ throw new InvalidOperationException(msg);
+ }
- try {
- OnClose.Emit (this, e);
- }
- catch (Exception ex) {
- _logger.Error (ex.Message);
- _logger.Debug (ex.ToString ());
- }
- }
+ if (_readyState == WebSocketState.Closing)
+ {
+ var msg = "The close process is in progress.";
+ throw new InvalidOperationException(msg);
+ }
- // As client
- internal static string CreateBase64Key ()
- {
- var src = new byte[16];
- RandomNumber.GetBytes (src);
+ if (_readyState == WebSocketState.Closed)
+ {
+ var msg = "The connection has already been closed.";
+ throw new InvalidOperationException(msg);
+ }
- return Convert.ToBase64String (src);
- }
+ if (accept())
+ open();
+ }
- internal static string CreateResponseKey (string base64Key)
- {
- var buff = new StringBuilder (base64Key, 64);
- buff.Append (_guid);
- SHA1 sha1 = new SHA1CryptoServiceProvider ();
- var src = sha1.ComputeHash (buff.ToString ().GetUTF8EncodedBytes ());
+ ///
+ /// Accepts the handshake request asynchronously.
+ ///
+ ///
+ ///
+ /// This method does not wait for the accept process to be complete.
+ ///
+ ///
+ /// This method does nothing if the handshake request has already been
+ /// accepted.
+ ///
+ ///
+ ///
+ ///
+ /// This instance is a client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The close process is in progress.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The connection has already been closed.
+ ///
+ ///
+ public void AcceptAsync()
+ {
+ if (_client)
+ {
+ var msg = "This instance is a client.";
+ throw new InvalidOperationException(msg);
+ }
- return Convert.ToBase64String (src);
- }
+ if (_readyState == WebSocketState.Closing)
+ {
+ var msg = "The close process is in progress.";
+ throw new InvalidOperationException(msg);
+ }
- // As server
- internal void InternalAccept ()
- {
- try {
- if (!acceptHandshake ())
- return;
- }
- catch (Exception ex) {
- _logger.Fatal (ex.Message);
- _logger.Debug (ex.ToString ());
+ if (_readyState == WebSocketState.Closed)
+ {
+ var msg = "The connection has already been closed.";
+ throw new InvalidOperationException(msg);
+ }
- var msg = "An exception has occurred while attempting to accept.";
- fatal (msg, ex);
+ Func acceptor = accept;
+ acceptor.BeginInvoke(
+ ar =>
+ {
+ if (acceptor.EndInvoke(ar))
+ open();
+ },
+ null
+ );
+ }
- return;
- }
+ ///
+ /// Closes the connection.
+ ///
+ ///
+ /// This method does nothing if the current state of the connection is
+ /// Closing or Closed.
+ ///
+ public void Close()
+ {
+ close(1005, String.Empty);
+ }
- _readyState = WebSocketState.Open;
+ ///
+ /// Closes the connection with the specified code.
+ ///
+ ///
+ /// This method does nothing if the current state of the connection is
+ /// Closing or Closed.
+ ///
+ ///
+ ///
+ /// A that represents the status code indicating
+ /// the reason for the close.
+ ///
+ ///
+ /// The status codes are defined in
+ ///
+ /// Section 7.4 of RFC 6455.
+ ///
+ ///
+ ///
+ /// is less than 1000 or greater than 4999.
+ ///
+ ///
+ ///
+ /// is 1011 (server error).
+ /// It cannot be used by clients.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1010 (mandatory extension).
+ /// It cannot be used by servers.
+ ///
+ ///
+ public void Close(ushort code)
+ {
+ if (!code.IsCloseStatusCode())
+ {
+ var msg = "Less than 1000 or greater than 4999.";
+ throw new ArgumentOutOfRangeException("code", msg);
+ }
- open ();
- }
+ if (_client && code == 1011)
+ {
+ var msg = "1011 cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
- // As server
- internal bool Ping (byte[] frameAsBytes, TimeSpan timeout)
- {
- if (_readyState != WebSocketState.Open)
- return false;
+ if (!_client && code == 1010)
+ {
+ var msg = "1010 cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
- var pongReceived = _pongReceived;
- if (pongReceived == null)
- return false;
+ close(code, String.Empty);
+ }
- lock (_forPing) {
- try {
- pongReceived.Reset ();
+ ///
+ /// Closes the connection with the specified code.
+ ///
+ ///
+ /// This method does nothing if the current state of the connection is
+ /// Closing or Closed.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It represents the status code indicating the reason for the close.
+ ///
+ ///
+ ///
+ ///
+ /// is
+ /// .
+ /// It cannot be used by clients.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is
+ /// .
+ /// It cannot be used by servers.
+ ///
+ ///
+ public void Close(CloseStatusCode code)
+ {
+ if (_client && code == CloseStatusCode.ServerError)
+ {
+ var msg = "ServerError cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
- lock (_forState) {
- if (_readyState != WebSocketState.Open)
- return false;
+ if (!_client && code == CloseStatusCode.MandatoryExtension)
+ {
+ var msg = "MandatoryExtension cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
+
+ close((ushort)code, String.Empty);
+ }
+
+ ///
+ /// Closes the connection with the specified code and reason.
+ ///
+ ///
+ /// This method does nothing if the current state of the connection is
+ /// Closing or Closed.
+ ///
+ ///
+ ///
+ /// A that represents the status code indicating
+ /// the reason for the close.
+ ///
+ ///
+ /// The status codes are defined in
+ ///
+ /// Section 7.4 of RFC 6455.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the reason for the close.
+ ///
+ ///
+ /// The size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ ///
+ /// is less than 1000 or greater than 4999.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ ///
+ ///
+ ///
+ /// is 1011 (server error).
+ /// It cannot be used by clients.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1010 (mandatory extension).
+ /// It cannot be used by servers.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1005 (no status) and there is reason.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ public void Close(ushort code, string reason)
+ {
+ if (!code.IsCloseStatusCode())
+ {
+ var msg = "Less than 1000 or greater than 4999.";
+ throw new ArgumentOutOfRangeException("code", msg);
+ }
+
+ if (_client && code == 1011)
+ {
+ var msg = "1011 cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
+
+ if (!_client && code == 1010)
+ {
+ var msg = "1010 cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
+
+ if (reason.IsNullOrEmpty())
+ {
+ close(code, String.Empty);
+ return;
+ }
+
+ if (code == 1005)
+ {
+ var msg = "1005 cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
+
+ byte[] bytes;
+ if (!reason.TryGetUTF8EncodedBytes(out bytes))
+ {
+ var msg = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(msg, "reason");
+ }
+
+ if (bytes.Length > 123)
+ {
+ var msg = "Its size is greater than 123 bytes.";
+ throw new ArgumentOutOfRangeException("reason", msg);
+ }
+
+ close(code, reason);
+ }
- if (!sendBytes (frameAsBytes))
- return false;
- }
+ ///
+ /// Closes the connection with the specified code and reason.
+ ///
+ ///
+ /// This method does nothing if the current state of the connection is
+ /// Closing or Closed.
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It represents the status code indicating the reason for the close.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the reason for the close.
+ ///
+ ///
+ /// The size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ ///
+ /// is
+ /// .
+ /// It cannot be used by clients.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is
+ /// .
+ /// It cannot be used by servers.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is
+ /// and there is reason.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ public void Close(CloseStatusCode code, string reason)
+ {
+ if (_client && code == CloseStatusCode.ServerError)
+ {
+ var msg = "ServerError cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
+
+ if (!_client && code == CloseStatusCode.MandatoryExtension)
+ {
+ var msg = "MandatoryExtension cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
+
+ if (reason.IsNullOrEmpty())
+ {
+ close((ushort)code, String.Empty);
+ return;
+ }
+
+ if (code == CloseStatusCode.NoStatus)
+ {
+ var msg = "NoStatus cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
- return pongReceived.WaitOne (timeout);
+ byte[] bytes;
+ if (!reason.TryGetUTF8EncodedBytes(out bytes))
+ {
+ var msg = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(msg, "reason");
+ }
+
+ if (bytes.Length > 123)
+ {
+ var msg = "Its size is greater than 123 bytes.";
+ throw new ArgumentOutOfRangeException("reason", msg);
+ }
+
+ close((ushort)code, reason);
}
- catch (ObjectDisposedException) {
- return false;
+
+ ///
+ /// Closes the connection asynchronously.
+ ///
+ ///
+ ///
+ /// This method does not wait for the close to be complete.
+ ///
+ ///
+ /// This method does nothing if the current state of the connection is
+ /// Closing or Closed.
+ ///
+ ///
+ public void CloseAsync()
+ {
+ closeAsync(1005, String.Empty);
}
- }
- }
- // As server
- internal void Send (
- Opcode opcode, byte[] data, Dictionary cache
- )
- {
- lock (_forSend) {
- lock (_forState) {
- if (_readyState != WebSocketState.Open) {
- _logger.Error ("The connection is closing.");
- return;
- }
-
- byte[] found;
- if (!cache.TryGetValue (_compression, out found)) {
- found = new WebSocketFrame (
- Fin.Final,
- opcode,
- data.Compress (_compression),
- _compression != CompressionMethod.None,
- false
- )
- .ToArray ();
-
- cache.Add (_compression, found);
- }
-
- sendBytes (found);
- }
- }
- }
+ ///
+ /// Closes the connection asynchronously with the specified code.
+ ///
+ ///
+ ///
+ /// This method does not wait for the close to be complete.
+ ///
+ ///
+ /// This method does nothing if the current state of the connection is
+ /// Closing or Closed.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the status code indicating
+ /// the reason for the close.
+ ///
+ ///
+ /// The status codes are defined in
+ ///
+ /// Section 7.4 of RFC 6455.
+ ///
+ ///
+ ///
+ /// is less than 1000 or greater than 4999.
+ ///
+ ///
+ ///
+ /// is 1011 (server error).
+ /// It cannot be used by clients.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1010 (mandatory extension).
+ /// It cannot be used by servers.
+ ///
+ ///
+ public void CloseAsync(ushort code)
+ {
+ if (!code.IsCloseStatusCode())
+ {
+ var msg = "Less than 1000 or greater than 4999.";
+ throw new ArgumentOutOfRangeException("code", msg);
+ }
- // As server
- internal void Send (
- Opcode opcode, Stream stream, Dictionary cache
- )
- {
- lock (_forSend) {
- Stream found;
- if (!cache.TryGetValue (_compression, out found)) {
- found = stream.Compress (_compression);
- cache.Add (_compression, found);
+ if (_client && code == 1011)
+ {
+ var msg = "1011 cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
+
+ if (!_client && code == 1010)
+ {
+ var msg = "1010 cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
+
+ closeAsync(code, String.Empty);
}
- else {
- found.Position = 0;
+
+ ///
+ /// Closes the connection asynchronously with the specified code.
+ ///
+ ///
+ ///
+ /// This method does not wait for the close to be complete.
+ ///
+ ///
+ /// This method does nothing if the current state of the connection is
+ /// Closing or Closed.
+ ///
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It represents the status code indicating the reason for the close.
+ ///
+ ///
+ ///
+ ///
+ /// is
+ /// .
+ /// It cannot be used by clients.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is
+ /// .
+ /// It cannot be used by servers.
+ ///
+ ///
+ public void CloseAsync(CloseStatusCode code)
+ {
+ if (_client && code == CloseStatusCode.ServerError)
+ {
+ var msg = "ServerError cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
+
+ if (!_client && code == CloseStatusCode.MandatoryExtension)
+ {
+ var msg = "MandatoryExtension cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
+
+ closeAsync((ushort)code, String.Empty);
}
- send (opcode, found, _compression != CompressionMethod.None);
- }
- }
+ ///
+ /// Closes the connection asynchronously with the specified code and reason.
+ ///
+ ///
+ ///
+ /// This method does not wait for the close to be complete.
+ ///
+ ///
+ /// This method does nothing if the current state of the connection is
+ /// Closing or Closed.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the status code indicating
+ /// the reason for the close.
+ ///
+ ///
+ /// The status codes are defined in
+ ///
+ /// Section 7.4 of RFC 6455.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the reason for the close.
+ ///
+ ///
+ /// The size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ ///
+ /// is less than 1000 or greater than 4999.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ ///
+ ///
+ ///
+ /// is 1011 (server error).
+ /// It cannot be used by clients.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1010 (mandatory extension).
+ /// It cannot be used by servers.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is 1005 (no status) and there is reason.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ public void CloseAsync(ushort code, string reason)
+ {
+ if (!code.IsCloseStatusCode())
+ {
+ var msg = "Less than 1000 or greater than 4999.";
+ throw new ArgumentOutOfRangeException("code", msg);
+ }
- #endregion
+ if (_client && code == 1011)
+ {
+ var msg = "1011 cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
- #region Public Methods
+ if (!_client && code == 1010)
+ {
+ var msg = "1010 cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
- ///
- /// Accepts the handshake request.
- ///
- ///
- /// This method does nothing if the handshake request has already been
- /// accepted.
- ///
- ///
- ///
- /// This instance is a client.
- ///
- ///
- /// -or-
- ///
- ///
- /// The close process is in progress.
- ///
- ///
- /// -or-
- ///
- ///
- /// The connection has already been closed.
- ///
- ///
- public void Accept ()
- {
- if (_client) {
- var msg = "This instance is a client.";
- throw new InvalidOperationException (msg);
- }
-
- if (_readyState == WebSocketState.Closing) {
- var msg = "The close process is in progress.";
- throw new InvalidOperationException (msg);
- }
-
- if (_readyState == WebSocketState.Closed) {
- var msg = "The connection has already been closed.";
- throw new InvalidOperationException (msg);
- }
-
- if (accept ())
- open ();
- }
+ if (reason.IsNullOrEmpty())
+ {
+ closeAsync(code, String.Empty);
+ return;
+ }
- ///
- /// Accepts the handshake request asynchronously.
- ///
- ///
- ///
- /// This method does not wait for the accept process to be complete.
- ///
- ///
- /// This method does nothing if the handshake request has already been
- /// accepted.
- ///
- ///
- ///
- ///
- /// This instance is a client.
- ///
- ///
- /// -or-
- ///
- ///
- /// The close process is in progress.
- ///
- ///
- /// -or-
- ///
- ///
- /// The connection has already been closed.
- ///
- ///
- public void AcceptAsync ()
- {
- if (_client) {
- var msg = "This instance is a client.";
- throw new InvalidOperationException (msg);
- }
-
- if (_readyState == WebSocketState.Closing) {
- var msg = "The close process is in progress.";
- throw new InvalidOperationException (msg);
- }
-
- if (_readyState == WebSocketState.Closed) {
- var msg = "The connection has already been closed.";
- throw new InvalidOperationException (msg);
- }
-
- Func acceptor = accept;
- acceptor.BeginInvoke (
- ar => {
- if (acceptor.EndInvoke (ar))
- open ();
- },
- null
- );
- }
+ if (code == 1005)
+ {
+ var msg = "1005 cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
- ///
- /// Closes the connection.
- ///
- ///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
- ///
- public void Close ()
- {
- close (1005, String.Empty);
- }
+ byte[] bytes;
+ if (!reason.TryGetUTF8EncodedBytes(out bytes))
+ {
+ var msg = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(msg, "reason");
+ }
- ///
- /// Closes the connection with the specified code.
- ///
- ///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
- ///
- ///
- ///
- /// A that represents the status code indicating
- /// the reason for the close.
- ///
- ///
- /// The status codes are defined in
- ///
- /// Section 7.4 of RFC 6455.
- ///
- ///
- ///
- /// is less than 1000 or greater than 4999.
- ///
- ///
- ///
- /// is 1011 (server error).
- /// It cannot be used by clients.
- ///
- ///
- /// -or-
- ///
- ///
- /// is 1010 (mandatory extension).
- /// It cannot be used by servers.
- ///
- ///
- public void Close (ushort code)
- {
- if (!code.IsCloseStatusCode ()) {
- var msg = "Less than 1000 or greater than 4999.";
- throw new ArgumentOutOfRangeException ("code", msg);
- }
-
- if (_client && code == 1011) {
- var msg = "1011 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (!_client && code == 1010) {
- var msg = "1010 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- close (code, String.Empty);
- }
+ if (bytes.Length > 123)
+ {
+ var msg = "Its size is greater than 123 bytes.";
+ throw new ArgumentOutOfRangeException("reason", msg);
+ }
- ///
- /// Closes the connection with the specified code.
- ///
- ///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
- ///
- ///
- ///
- /// One of the enum values.
- ///
- ///
- /// It represents the status code indicating the reason for the close.
- ///
- ///
- ///
- ///
- /// is
- /// .
- /// It cannot be used by clients.
- ///
- ///
- /// -or-
- ///
- ///
- /// is
- /// .
- /// It cannot be used by servers.
- ///
- ///
- public void Close (CloseStatusCode code)
- {
- if (_client && code == CloseStatusCode.ServerError) {
- var msg = "ServerError cannot be used.";
- throw new ArgumentException (msg, "code");
- }
+ closeAsync(code, reason);
+ }
- if (!_client && code == CloseStatusCode.MandatoryExtension) {
- var msg = "MandatoryExtension cannot be used.";
- throw new ArgumentException (msg, "code");
- }
+ ///
+ /// Closes the connection asynchronously with the specified code and reason.
+ ///
+ ///
+ ///
+ /// This method does not wait for the close to be complete.
+ ///
+ ///
+ /// This method does nothing if the current state of the connection is
+ /// Closing or Closed.
+ ///
+ ///
+ ///
+ ///
+ /// One of the enum values.
+ ///
+ ///
+ /// It represents the status code indicating the reason for the close.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the reason for the close.
+ ///
+ ///
+ /// The size must be 123 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ ///
+ /// is
+ /// .
+ /// It cannot be used by clients.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is
+ /// .
+ /// It cannot be used by servers.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is
+ /// and there is reason.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ ///
+ /// The size of is greater than 123 bytes.
+ ///
+ public void CloseAsync(CloseStatusCode code, string reason)
+ {
+ if (_client && code == CloseStatusCode.ServerError)
+ {
+ var msg = "ServerError cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
- close ((ushort) code, String.Empty);
- }
+ if (!_client && code == CloseStatusCode.MandatoryExtension)
+ {
+ var msg = "MandatoryExtension cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
- ///
- /// Closes the connection with the specified code and reason.
- ///
- ///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
- ///
- ///
- ///
- /// A that represents the status code indicating
- /// the reason for the close.
- ///
- ///
- /// The status codes are defined in
- ///
- /// Section 7.4 of RFC 6455.
- ///
- ///
- ///
- ///
- /// A that represents the reason for the close.
- ///
- ///
- /// The size must be 123 bytes or less in UTF-8.
- ///
- ///
- ///
- ///
- /// is less than 1000 or greater than 4999.
- ///
- ///
- /// -or-
- ///
- ///
- /// The size of is greater than 123 bytes.
- ///
- ///
- ///
- ///
- /// is 1011 (server error).
- /// It cannot be used by clients.
- ///
- ///
- /// -or-
- ///
- ///
- /// is 1010 (mandatory extension).
- /// It cannot be used by servers.
- ///
- ///
- /// -or-
- ///
- ///
- /// is 1005 (no status) and there is reason.
- ///
- ///
- /// -or-
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- ///
- public void Close (ushort code, string reason)
- {
- if (!code.IsCloseStatusCode ()) {
- var msg = "Less than 1000 or greater than 4999.";
- throw new ArgumentOutOfRangeException ("code", msg);
- }
-
- if (_client && code == 1011) {
- var msg = "1011 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (!_client && code == 1010) {
- var msg = "1010 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (reason.IsNullOrEmpty ()) {
- close (code, String.Empty);
- return;
- }
-
- if (code == 1005) {
- var msg = "1005 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- byte[] bytes;
- if (!reason.TryGetUTF8EncodedBytes (out bytes)) {
- var msg = "It could not be UTF-8-encoded.";
- throw new ArgumentException (msg, "reason");
- }
-
- if (bytes.Length > 123) {
- var msg = "Its size is greater than 123 bytes.";
- throw new ArgumentOutOfRangeException ("reason", msg);
- }
-
- close (code, reason);
- }
+ if (reason.IsNullOrEmpty())
+ {
+ closeAsync((ushort)code, String.Empty);
+ return;
+ }
- ///
- /// Closes the connection with the specified code and reason.
- ///
- ///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
- ///
- ///
- ///
- /// One of the enum values.
- ///
- ///
- /// It represents the status code indicating the reason for the close.
- ///
- ///
- ///
- ///
- /// A that represents the reason for the close.
- ///
- ///
- /// The size must be 123 bytes or less in UTF-8.
- ///
- ///
- ///
- ///
- /// is
- /// .
- /// It cannot be used by clients.
- ///
- ///
- /// -or-
- ///
- ///
- /// is
- /// .
- /// It cannot be used by servers.
- ///
- ///
- /// -or-
- ///
- ///
- /// is
- /// and there is reason.
- ///
- ///
- /// -or-
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- ///
- ///
- /// The size of is greater than 123 bytes.
- ///
- public void Close (CloseStatusCode code, string reason)
- {
- if (_client && code == CloseStatusCode.ServerError) {
- var msg = "ServerError cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (!_client && code == CloseStatusCode.MandatoryExtension) {
- var msg = "MandatoryExtension cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (reason.IsNullOrEmpty ()) {
- close ((ushort) code, String.Empty);
- return;
- }
-
- if (code == CloseStatusCode.NoStatus) {
- var msg = "NoStatus cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- byte[] bytes;
- if (!reason.TryGetUTF8EncodedBytes (out bytes)) {
- var msg = "It could not be UTF-8-encoded.";
- throw new ArgumentException (msg, "reason");
- }
-
- if (bytes.Length > 123) {
- var msg = "Its size is greater than 123 bytes.";
- throw new ArgumentOutOfRangeException ("reason", msg);
- }
-
- close ((ushort) code, reason);
- }
+ if (code == CloseStatusCode.NoStatus)
+ {
+ var msg = "NoStatus cannot be used.";
+ throw new ArgumentException(msg, "code");
+ }
- ///
- /// Closes the connection asynchronously.
- ///
- ///
- ///
- /// This method does not wait for the close to be complete.
- ///
- ///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
- ///
- ///
- public void CloseAsync ()
- {
- closeAsync (1005, String.Empty);
- }
+ byte[] bytes;
+ if (!reason.TryGetUTF8EncodedBytes(out bytes))
+ {
+ var msg = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(msg, "reason");
+ }
- ///
- /// Closes the connection asynchronously with the specified code.
- ///
- ///
- ///
- /// This method does not wait for the close to be complete.
- ///
- ///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
- ///
- ///
- ///
- ///
- /// A that represents the status code indicating
- /// the reason for the close.
- ///
- ///
- /// The status codes are defined in
- ///
- /// Section 7.4 of RFC 6455.
- ///
- ///
- ///
- /// is less than 1000 or greater than 4999.
- ///
- ///
- ///
- /// is 1011 (server error).
- /// It cannot be used by clients.
- ///
- ///
- /// -or-
- ///
- ///
- /// is 1010 (mandatory extension).
- /// It cannot be used by servers.
- ///
- ///
- public void CloseAsync (ushort code)
- {
- if (!code.IsCloseStatusCode ()) {
- var msg = "Less than 1000 or greater than 4999.";
- throw new ArgumentOutOfRangeException ("code", msg);
- }
-
- if (_client && code == 1011) {
- var msg = "1011 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (!_client && code == 1010) {
- var msg = "1010 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- closeAsync (code, String.Empty);
- }
+ if (bytes.Length > 123)
+ {
+ var msg = "Its size is greater than 123 bytes.";
+ throw new ArgumentOutOfRangeException("reason", msg);
+ }
- ///
- /// Closes the connection asynchronously with the specified code.
- ///
- ///
- ///
- /// This method does not wait for the close to be complete.
- ///
- ///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
- ///
- ///
- ///
- ///
- /// One of the enum values.
- ///
- ///
- /// It represents the status code indicating the reason for the close.
- ///
- ///
- ///
- ///
- /// is
- /// .
- /// It cannot be used by clients.
- ///
- ///
- /// -or-
- ///
- ///
- /// is
- /// .
- /// It cannot be used by servers.
- ///
- ///
- public void CloseAsync (CloseStatusCode code)
- {
- if (_client && code == CloseStatusCode.ServerError) {
- var msg = "ServerError cannot be used.";
- throw new ArgumentException (msg, "code");
- }
+ closeAsync((ushort)code, reason);
+ }
- if (!_client && code == CloseStatusCode.MandatoryExtension) {
- var msg = "MandatoryExtension cannot be used.";
- throw new ArgumentException (msg, "code");
- }
+ ///
+ /// Establishes a connection.
+ ///
+ ///
+ /// This method does nothing if the connection has already been established.
+ ///
+ ///
+ ///
+ /// This instance is not a client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The close process is in progress.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// A series of reconnecting has failed.
+ ///
+ ///
+ public void Connect()
+ {
+ if (!_client)
+ {
+ var msg = "This instance is not a client.";
+ throw new InvalidOperationException(msg);
+ }
- closeAsync ((ushort) code, String.Empty);
- }
+ if (_readyState == WebSocketState.Closing)
+ {
+ var msg = "The close process is in progress.";
+ throw new InvalidOperationException(msg);
+ }
- ///
- /// Closes the connection asynchronously with the specified code and reason.
- ///
- ///
- ///
- /// This method does not wait for the close to be complete.
- ///
- ///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
- ///
- ///
- ///
- ///
- /// A that represents the status code indicating
- /// the reason for the close.
- ///
- ///
- /// The status codes are defined in
- ///
- /// Section 7.4 of RFC 6455.
- ///
- ///
- ///
- ///
- /// A that represents the reason for the close.
- ///
- ///
- /// The size must be 123 bytes or less in UTF-8.
- ///
- ///
- ///
- ///
- /// is less than 1000 or greater than 4999.
- ///
- ///
- /// -or-
- ///
- ///
- /// The size of is greater than 123 bytes.
- ///
- ///
- ///
- ///
- /// is 1011 (server error).
- /// It cannot be used by clients.
- ///
- ///
- /// -or-
- ///
- ///
- /// is 1010 (mandatory extension).
- /// It cannot be used by servers.
- ///
- ///
- /// -or-
- ///
- ///
- /// is 1005 (no status) and there is reason.
- ///
- ///
- /// -or-
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- ///
- public void CloseAsync (ushort code, string reason)
- {
- if (!code.IsCloseStatusCode ()) {
- var msg = "Less than 1000 or greater than 4999.";
- throw new ArgumentOutOfRangeException ("code", msg);
- }
-
- if (_client && code == 1011) {
- var msg = "1011 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (!_client && code == 1010) {
- var msg = "1010 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (reason.IsNullOrEmpty ()) {
- closeAsync (code, String.Empty);
- return;
- }
-
- if (code == 1005) {
- var msg = "1005 cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- byte[] bytes;
- if (!reason.TryGetUTF8EncodedBytes (out bytes)) {
- var msg = "It could not be UTF-8-encoded.";
- throw new ArgumentException (msg, "reason");
- }
-
- if (bytes.Length > 123) {
- var msg = "Its size is greater than 123 bytes.";
- throw new ArgumentOutOfRangeException ("reason", msg);
- }
-
- closeAsync (code, reason);
- }
+ if (_retryCountForConnect > _maxRetryCountForConnect)
+ {
+ var msg = "A series of reconnecting has failed.";
+ throw new InvalidOperationException(msg);
+ }
- ///
- /// Closes the connection asynchronously with the specified code and reason.
- ///
- ///
- ///
- /// This method does not wait for the close to be complete.
- ///
- ///
- /// This method does nothing if the current state of the connection is
- /// Closing or Closed.
- ///
- ///
- ///
- ///
- /// One of the enum values.
- ///
- ///
- /// It represents the status code indicating the reason for the close.
- ///
- ///
- ///
- ///
- /// A that represents the reason for the close.
- ///
- ///
- /// The size must be 123 bytes or less in UTF-8.
- ///
- ///
- ///
- ///
- /// is
- /// .
- /// It cannot be used by clients.
- ///
- ///
- /// -or-
- ///
- ///
- /// is
- /// .
- /// It cannot be used by servers.
- ///
- ///
- /// -or-
- ///
- ///
- /// is
- /// and there is reason.
- ///
- ///
- /// -or-
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- ///
- ///
- /// The size of is greater than 123 bytes.
- ///
- public void CloseAsync (CloseStatusCode code, string reason)
- {
- if (_client && code == CloseStatusCode.ServerError) {
- var msg = "ServerError cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (!_client && code == CloseStatusCode.MandatoryExtension) {
- var msg = "MandatoryExtension cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- if (reason.IsNullOrEmpty ()) {
- closeAsync ((ushort) code, String.Empty);
- return;
- }
-
- if (code == CloseStatusCode.NoStatus) {
- var msg = "NoStatus cannot be used.";
- throw new ArgumentException (msg, "code");
- }
-
- byte[] bytes;
- if (!reason.TryGetUTF8EncodedBytes (out bytes)) {
- var msg = "It could not be UTF-8-encoded.";
- throw new ArgumentException (msg, "reason");
- }
-
- if (bytes.Length > 123) {
- var msg = "Its size is greater than 123 bytes.";
- throw new ArgumentOutOfRangeException ("reason", msg);
- }
-
- closeAsync ((ushort) code, reason);
- }
+ if (connect())
+ open();
+ }
- ///
- /// Establishes a connection.
- ///
- ///
- /// This method does nothing if the connection has already been established.
- ///
- ///
- ///
- /// This instance is not a client.
- ///
- ///
- /// -or-
- ///
- ///
- /// The close process is in progress.
- ///
- ///
- /// -or-
- ///
- ///
- /// A series of reconnecting has failed.
- ///
- ///
- public void Connect ()
- {
- if (!_client) {
- var msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
- }
-
- if (_readyState == WebSocketState.Closing) {
- var msg = "The close process is in progress.";
- throw new InvalidOperationException (msg);
- }
-
- if (_retryCountForConnect > _maxRetryCountForConnect) {
- var msg = "A series of reconnecting has failed.";
- throw new InvalidOperationException (msg);
- }
-
- if (connect ())
- open ();
- }
+ ///
+ /// Establishes a connection asynchronously.
+ ///
+ ///
+ ///
+ /// This method does not wait for the connect process to be complete.
+ ///
+ ///
+ /// This method does nothing if the connection has already been
+ /// established.
+ ///
+ ///
+ ///
+ ///
+ /// This instance is not a client.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The close process is in progress.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// A series of reconnecting has failed.
+ ///
+ ///
+ public void ConnectAsync()
+ {
+ if (!_client)
+ {
+ var msg = "This instance is not a client.";
+ throw new InvalidOperationException(msg);
+ }
- ///
- /// Establishes a connection asynchronously.
- ///
- ///
- ///
- /// This method does not wait for the connect process to be complete.
- ///
- ///
- /// This method does nothing if the connection has already been
- /// established.
- ///
- ///
- ///
- ///
- /// This instance is not a client.
- ///
- ///
- /// -or-
- ///
- ///
- /// The close process is in progress.
- ///
- ///
- /// -or-
- ///
- ///
- /// A series of reconnecting has failed.
- ///
- ///
- public void ConnectAsync ()
- {
- if (!_client) {
- var msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
- }
-
- if (_readyState == WebSocketState.Closing) {
- var msg = "The close process is in progress.";
- throw new InvalidOperationException (msg);
- }
-
- if (_retryCountForConnect > _maxRetryCountForConnect) {
- var msg = "A series of reconnecting has failed.";
- throw new InvalidOperationException (msg);
- }
-
- Func connector = connect;
- connector.BeginInvoke (
- ar => {
- if (connector.EndInvoke (ar))
- open ();
- },
- null
- );
- }
+ if (_readyState == WebSocketState.Closing)
+ {
+ var msg = "The close process is in progress.";
+ throw new InvalidOperationException(msg);
+ }
- ///
- /// Sends a ping using the WebSocket connection.
- ///
- ///
- /// true if the send has done with no error and a pong has been
- /// received within a time; otherwise, false.
- ///
- public bool Ping ()
- {
- return ping (EmptyBytes);
- }
+ if (_retryCountForConnect > _maxRetryCountForConnect)
+ {
+ var msg = "A series of reconnecting has failed.";
+ throw new InvalidOperationException(msg);
+ }
- ///
- /// Sends a ping with using the WebSocket
- /// connection.
- ///
- ///
- /// true if the send has done with no error and a pong has been
- /// received within a time; otherwise, false.
- ///
- ///
- ///
- /// A that represents the message to send.
- ///
- ///
- /// The size must be 125 bytes or less in UTF-8.
- ///
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- ///
- /// The size of is greater than 125 bytes.
- ///
- public bool Ping (string message)
- {
- if (message.IsNullOrEmpty ())
- return ping (EmptyBytes);
+ Func connector = connect;
+ connector.BeginInvoke(
+ ar =>
+ {
+ if (connector.EndInvoke(ar))
+ open();
+ },
+ null
+ );
+ }
- byte[] bytes;
- if (!message.TryGetUTF8EncodedBytes (out bytes)) {
- var msg = "It could not be UTF-8-encoded.";
- throw new ArgumentException (msg, "message");
- }
+ ///
+ /// Sends a ping using the WebSocket connection.
+ ///
+ ///
+ /// true if the send has done with no error and a pong has been
+ /// received within a time; otherwise, false.
+ ///
+ public bool Ping()
+ {
+ return ping(EmptyBytes);
+ }
- if (bytes.Length > 125) {
- var msg = "Its size is greater than 125 bytes.";
- throw new ArgumentOutOfRangeException ("message", msg);
- }
+ ///
+ /// Sends a ping with using the WebSocket
+ /// connection.
+ ///
+ ///
+ /// true if the send has done with no error and a pong has been
+ /// received within a time; otherwise, false.
+ ///
+ ///
+ ///
+ /// A that represents the message to send.
+ ///
+ ///
+ /// The size must be 125 bytes or less in UTF-8.
+ ///
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ ///
+ /// The size of is greater than 125 bytes.
+ ///
+ public bool Ping(string message)
+ {
+ if (message.IsNullOrEmpty())
+ return ping(EmptyBytes);
+
+ byte[] bytes;
+ if (!message.TryGetUTF8EncodedBytes(out bytes))
+ {
+ var msg = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(msg, "message");
+ }
- return ping (bytes);
- }
+ if (bytes.Length > 125)
+ {
+ var msg = "Its size is greater than 125 bytes.";
+ throw new ArgumentOutOfRangeException("message", msg);
+ }
- ///
- /// Sends the specified data using the WebSocket connection.
- ///
- ///
- /// An array of that represents the binary data to send.
- ///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
- public void Send (byte[] data)
- {
- if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
- throw new InvalidOperationException (msg);
- }
+ return ping(bytes);
+ }
- if (data == null)
- throw new ArgumentNullException ("data");
+ ///
+ /// Sends the specified data using the WebSocket connection.
+ ///
+ ///
+ /// An array of that represents the binary data to send.
+ ///
+ ///
+ /// The current state of the connection is not Open.
+ ///
+ ///
+ /// is .
+ ///
+ public void Send(byte[] data)
+ {
+ if (_readyState != WebSocketState.Open)
+ {
+ var msg = "The current state of the connection is not Open.";
+ throw new InvalidOperationException(msg);
+ }
- send (Opcode.Binary, new MemoryStream (data));
- }
+ if (data == null)
+ throw new ArgumentNullException("data");
- ///
- /// Sends the specified file using the WebSocket connection.
- ///
- ///
- ///
- /// A that specifies the file to send.
- ///
- ///
- /// The file is sent as the binary data.
- ///
- ///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// The file does not exist.
- ///
- ///
- /// -or-
- ///
- ///
- /// The file could not be opened.
- ///
- ///
- public void Send (FileInfo fileInfo)
- {
- if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
- throw new InvalidOperationException (msg);
- }
-
- if (fileInfo == null)
- throw new ArgumentNullException ("fileInfo");
-
- if (!fileInfo.Exists) {
- var msg = "The file does not exist.";
- throw new ArgumentException (msg, "fileInfo");
- }
-
- FileStream stream;
- if (!fileInfo.TryOpenRead (out stream)) {
- var msg = "The file could not be opened.";
- throw new ArgumentException (msg, "fileInfo");
- }
-
- send (Opcode.Binary, stream);
- }
+ send(Opcode.Binary, new MemoryStream(data));
+ }
- ///
- /// Sends the specified data using the WebSocket connection.
- ///
- ///
- /// A that represents the text data to send.
- ///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- public void Send (string data)
- {
- if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
- throw new InvalidOperationException (msg);
- }
+ ///
+ /// Sends the specified file using the WebSocket connection.
+ ///
+ ///
+ ///
+ /// A that specifies the file to send.
+ ///
+ ///
+ /// The file is sent as the binary data.
+ ///
+ ///
+ ///
+ /// The current state of the connection is not Open.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// The file does not exist.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The file could not be opened.
+ ///
+ ///
+ public void Send(FileInfo fileInfo)
+ {
+ if (_readyState != WebSocketState.Open)
+ {
+ var msg = "The current state of the connection is not Open.";
+ throw new InvalidOperationException(msg);
+ }
- if (data == null)
- throw new ArgumentNullException ("data");
+ if (fileInfo == null)
+ throw new ArgumentNullException("fileInfo");
- byte[] bytes;
- if (!data.TryGetUTF8EncodedBytes (out bytes)) {
- var msg = "It could not be UTF-8-encoded.";
- throw new ArgumentException (msg, "data");
- }
+ if (!fileInfo.Exists)
+ {
+ var msg = "The file does not exist.";
+ throw new ArgumentException(msg, "fileInfo");
+ }
- send (Opcode.Text, new MemoryStream (bytes));
- }
+ FileStream stream;
+ if (!fileInfo.TryOpenRead(out stream))
+ {
+ var msg = "The file could not be opened.";
+ throw new ArgumentException(msg, "fileInfo");
+ }
- ///
- /// Sends the data from the specified stream using the WebSocket connection.
- ///
- ///
- ///
- /// A instance from which to read the data to send.
- ///
- ///
- /// The data is sent as the binary data.
- ///
- ///
- ///
- /// An that specifies the number of bytes to send.
- ///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// cannot be read.
- ///
- ///
- /// -or-
- ///
- ///
- /// is less than 1.
- ///
- ///
- /// -or-
- ///
- ///
- /// No data could be read from .
- ///
- ///
- public void Send (Stream stream, int length)
- {
- if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
- throw new InvalidOperationException (msg);
- }
-
- if (stream == null)
- throw new ArgumentNullException ("stream");
-
- if (!stream.CanRead) {
- var msg = "It cannot be read.";
- throw new ArgumentException (msg, "stream");
- }
-
- if (length < 1) {
- var msg = "Less than 1.";
- throw new ArgumentException (msg, "length");
- }
-
- var bytes = stream.ReadBytes (length);
-
- var len = bytes.Length;
- if (len == 0) {
- var msg = "No data could be read from it.";
- throw new ArgumentException (msg, "stream");
- }
-
- if (len < length) {
- _logger.Warn (
- String.Format (
- "Only {0} byte(s) of data could be read from the stream.",
- len
- )
- );
- }
-
- send (Opcode.Binary, new MemoryStream (bytes));
- }
+ send(Opcode.Binary, stream);
+ }
- ///
- /// Sends the specified data asynchronously using the WebSocket connection.
- ///
- ///
- /// This method does not wait for the send to be complete.
- ///
- ///
- /// An array of that represents the binary data to send.
- ///
- ///
- ///
- /// An Action<bool> delegate or
- /// if not needed.
- ///
- ///
- /// The delegate invokes the method called when the send is complete.
- ///
- ///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
- ///
- ///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
- public void SendAsync (byte[] data, Action completed)
- {
- if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
- throw new InvalidOperationException (msg);
- }
+ ///
+ /// Sends the specified data using the WebSocket connection.
+ ///
+ ///
+ /// A that represents the text data to send.
+ ///
+ ///
+ /// The current state of the connection is not Open.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ public void Send(string data)
+ {
+ if (_readyState != WebSocketState.Open)
+ {
+ var msg = "The current state of the connection is not Open.";
+ throw new InvalidOperationException(msg);
+ }
- if (data == null)
- throw new ArgumentNullException ("data");
+ if (data == null)
+ throw new ArgumentNullException("data");
- sendAsync (Opcode.Binary, new MemoryStream (data), completed);
- }
+ byte[] bytes;
+ if (!data.TryGetUTF8EncodedBytes(out bytes))
+ {
+ var msg = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(msg, "data");
+ }
- ///
- /// Sends the specified file asynchronously using the WebSocket connection.
- ///
- ///
- /// This method does not wait for the send to be complete.
- ///
- ///
- ///
- /// A that specifies the file to send.
- ///
- ///
- /// The file is sent as the binary data.
- ///
- ///
- ///
- ///
- /// An Action<bool> delegate or
- /// if not needed.
- ///
- ///
- /// The delegate invokes the method called when the send is complete.
- ///
- ///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
- ///
- ///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// The file does not exist.
- ///
- ///
- /// -or-
- ///
- ///
- /// The file could not be opened.
- ///
- ///
- public void SendAsync (FileInfo fileInfo, Action completed)
- {
- if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
- throw new InvalidOperationException (msg);
- }
-
- if (fileInfo == null)
- throw new ArgumentNullException ("fileInfo");
-
- if (!fileInfo.Exists) {
- var msg = "The file does not exist.";
- throw new ArgumentException (msg, "fileInfo");
- }
-
- FileStream stream;
- if (!fileInfo.TryOpenRead (out stream)) {
- var msg = "The file could not be opened.";
- throw new ArgumentException (msg, "fileInfo");
- }
-
- sendAsync (Opcode.Binary, stream, completed);
- }
+ send(Opcode.Text, new MemoryStream(bytes));
+ }
- ///
- /// Sends the specified data asynchronously using the WebSocket connection.
- ///
- ///
- /// This method does not wait for the send to be complete.
- ///
- ///
- /// A that represents the text data to send.
- ///
- ///
- ///
- /// An Action<bool> delegate or
- /// if not needed.
- ///
- ///
- /// The delegate invokes the method called when the send is complete.
- ///
- ///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
- ///
- ///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
- ///
- /// could not be UTF-8-encoded.
- ///
- public void SendAsync (string data, Action completed)
- {
- if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
- throw new InvalidOperationException (msg);
- }
+ ///
+ /// Sends the data from the specified stream using the WebSocket connection.
+ ///
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ /// The current state of the connection is not Open.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ public void Send(Stream stream, int length)
+ {
+ if (_readyState != WebSocketState.Open)
+ {
+ var msg = "The current state of the connection is not Open.";
+ throw new InvalidOperationException(msg);
+ }
- if (data == null)
- throw new ArgumentNullException ("data");
+ if (stream == null)
+ throw new ArgumentNullException("stream");
- byte[] bytes;
- if (!data.TryGetUTF8EncodedBytes (out bytes)) {
- var msg = "It could not be UTF-8-encoded.";
- throw new ArgumentException (msg, "data");
- }
+ if (!stream.CanRead)
+ {
+ var msg = "It cannot be read.";
+ throw new ArgumentException(msg, "stream");
+ }
- sendAsync (Opcode.Text, new MemoryStream (bytes), completed);
- }
+ if (length < 1)
+ {
+ var msg = "Less than 1.";
+ throw new ArgumentException(msg, "length");
+ }
- ///
- /// Sends the data from the specified stream asynchronously using
- /// the WebSocket connection.
- ///
- ///
- /// This method does not wait for the send to be complete.
- ///
- ///
- ///
- /// A instance from which to read the data to send.
- ///
- ///
- /// The data is sent as the binary data.
- ///
- ///
- ///
- /// An that specifies the number of bytes to send.
- ///
- ///
- ///
- /// An Action<bool> delegate or
- /// if not needed.
- ///
- ///
- /// The delegate invokes the method called when the send is complete.
- ///
- ///
- /// true is passed to the method if the send has done with
- /// no error; otherwise, false.
- ///
- ///
- ///
- /// The current state of the connection is not Open.
- ///
- ///
- /// is .
- ///
- ///
- ///
- /// cannot be read.
- ///
- ///
- /// -or-
- ///
- ///
- /// is less than 1.
- ///
- ///
- /// -or-
- ///
- ///
- /// No data could be read from .
- ///
- ///
- public void SendAsync (Stream stream, int length, Action completed)
- {
- if (_readyState != WebSocketState.Open) {
- var msg = "The current state of the connection is not Open.";
- throw new InvalidOperationException (msg);
- }
-
- if (stream == null)
- throw new ArgumentNullException ("stream");
-
- if (!stream.CanRead) {
- var msg = "It cannot be read.";
- throw new ArgumentException (msg, "stream");
- }
-
- if (length < 1) {
- var msg = "Less than 1.";
- throw new ArgumentException (msg, "length");
- }
-
- var bytes = stream.ReadBytes (length);
-
- var len = bytes.Length;
- if (len == 0) {
- var msg = "No data could be read from it.";
- throw new ArgumentException (msg, "stream");
- }
-
- if (len < length) {
- _logger.Warn (
- String.Format (
- "Only {0} byte(s) of data could be read from the stream.",
- len
- )
- );
- }
-
- sendAsync (Opcode.Binary, new MemoryStream (bytes), completed);
- }
+ var bytes = stream.ReadBytes(length);
- ///
- /// Sets an HTTP cookie to send with the handshake request.
- ///
- ///
- /// This method does nothing if the connection has already been
- /// established or it is closing.
- ///
- ///
- /// A that represents the cookie to send.
- ///
- ///
- /// This instance is not a client.
- ///
- ///
- /// is .
- ///
- public void SetCookie (Cookie cookie)
- {
- string msg = null;
+ var len = bytes.Length;
+ if (len == 0)
+ {
+ var msg = "No data could be read from it.";
+ throw new ArgumentException(msg, "stream");
+ }
+
+ if (len < length)
+ {
+ _logger.Warn(
+ String.Format(
+ "Only {0} byte(s) of data could be read from the stream.",
+ len
+ )
+ );
+ }
- if (!_client) {
- msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
- }
+ send(Opcode.Binary, new MemoryStream(bytes));
+ }
- if (cookie == null)
- throw new ArgumentNullException ("cookie");
+ ///
+ /// Sends the specified data asynchronously using the WebSocket connection.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// An array of that represents the binary data to send.
+ ///
+ ///
+ ///
+ /// An Action<bool> delegate or
+ /// if not needed.
+ ///
+ ///
+ /// The delegate invokes the method called when the send is complete.
+ ///
+ ///
+ /// true is passed to the method if the send has done with
+ /// no error; otherwise, false.
+ ///
+ ///
+ ///
+ /// The current state of the connection is not Open.
+ ///
+ ///
+ /// is .
+ ///
+ public void SendAsync(byte[] data, Action completed)
+ {
+ if (_readyState != WebSocketState.Open)
+ {
+ var msg = "The current state of the connection is not Open.";
+ throw new InvalidOperationException(msg);
+ }
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
+ if (data == null)
+ throw new ArgumentNullException("data");
- lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ sendAsync(Opcode.Binary, new MemoryStream(data), completed);
}
- lock (_cookies.SyncRoot)
- _cookies.SetOrRemove (cookie);
- }
- }
+ ///
+ /// Sends the specified file asynchronously using the WebSocket connection.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ ///
+ /// A that specifies the file to send.
+ ///
+ ///
+ /// The file is sent as the binary data.
+ ///
+ ///
+ ///
+ ///
+ /// An Action<bool> delegate or
+ /// if not needed.
+ ///
+ ///
+ /// The delegate invokes the method called when the send is complete.
+ ///
+ ///
+ /// true is passed to the method if the send has done with
+ /// no error; otherwise, false.
+ ///
+ ///
+ ///
+ /// The current state of the connection is not Open.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// The file does not exist.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The file could not be opened.
+ ///
+ ///
+ public void SendAsync(FileInfo fileInfo, Action completed)
+ {
+ if (_readyState != WebSocketState.Open)
+ {
+ var msg = "The current state of the connection is not Open.";
+ throw new InvalidOperationException(msg);
+ }
- ///
- /// Sets the credentials for the HTTP authentication (Basic/Digest).
- ///
- ///
- /// This method does nothing if the connection has already been
- /// established or it is closing.
- ///
- ///
- ///
- /// A that represents the username associated with
- /// the credentials.
- ///
- ///
- /// or an empty string if initializes
- /// the credentials.
- ///
- ///
- ///
- ///
- /// A that represents the password for the username
- /// associated with the credentials.
- ///
- ///
- /// or an empty string if not necessary.
- ///
- ///
- ///
- /// true if sends the credentials for the Basic authentication in
- /// advance with the first handshake request; otherwise, false.
- ///
- ///
- /// This instance is not a client.
- ///
- ///
- ///
- /// contains an invalid character.
- ///
- ///
- /// -or-
- ///
- ///
- /// contains an invalid character.
- ///
- ///
- public void SetCredentials (string username, string password, bool preAuth)
- {
- string msg = null;
+ if (fileInfo == null)
+ throw new ArgumentNullException("fileInfo");
- if (!_client) {
- msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
- }
+ if (!fileInfo.Exists)
+ {
+ var msg = "The file does not exist.";
+ throw new ArgumentException(msg, "fileInfo");
+ }
- if (!username.IsNullOrEmpty ()) {
- if (username.Contains (':') || !username.IsText ()) {
- msg = "It contains an invalid character.";
- throw new ArgumentException (msg, "username");
- }
- }
+ FileStream stream;
+ if (!fileInfo.TryOpenRead(out stream))
+ {
+ var msg = "The file could not be opened.";
+ throw new ArgumentException(msg, "fileInfo");
+ }
- if (!password.IsNullOrEmpty ()) {
- if (!password.IsText ()) {
- msg = "It contains an invalid character.";
- throw new ArgumentException (msg, "password");
+ sendAsync(Opcode.Binary, stream, completed);
}
- }
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
+ ///
+ /// Sends the specified data asynchronously using the WebSocket connection.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ /// A that represents the text data to send.
+ ///
+ ///
+ ///
+ /// An Action<bool> delegate or
+ /// if not needed.
+ ///
+ ///
+ /// The delegate invokes the method called when the send is complete.
+ ///
+ ///
+ /// true is passed to the method if the send has done with
+ /// no error; otherwise, false.
+ ///
+ ///
+ ///
+ /// The current state of the connection is not Open.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ /// could not be UTF-8-encoded.
+ ///
+ public void SendAsync(string data, Action completed)
+ {
+ if (_readyState != WebSocketState.Open)
+ {
+ var msg = "The current state of the connection is not Open.";
+ throw new InvalidOperationException(msg);
+ }
- lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
+ if (data == null)
+ throw new ArgumentNullException("data");
- if (username.IsNullOrEmpty ()) {
- _credentials = null;
- _preAuth = false;
+ byte[] bytes;
+ if (!data.TryGetUTF8EncodedBytes(out bytes))
+ {
+ var msg = "It could not be UTF-8-encoded.";
+ throw new ArgumentException(msg, "data");
+ }
- return;
+ sendAsync(Opcode.Text, new MemoryStream(bytes), completed);
}
- _credentials = new NetworkCredential (
- username, password, _uri.PathAndQuery
- );
+ ///
+ /// Sends the data from the specified stream asynchronously using
+ /// the WebSocket connection.
+ ///
+ ///
+ /// This method does not wait for the send to be complete.
+ ///
+ ///
+ ///
+ /// A instance from which to read the data to send.
+ ///
+ ///
+ /// The data is sent as the binary data.
+ ///
+ ///
+ ///
+ /// An that specifies the number of bytes to send.
+ ///
+ ///
+ ///
+ /// An Action<bool> delegate or
+ /// if not needed.
+ ///
+ ///
+ /// The delegate invokes the method called when the send is complete.
+ ///
+ ///
+ /// true is passed to the method if the send has done with
+ /// no error; otherwise, false.
+ ///
+ ///
+ ///
+ /// The current state of the connection is not Open.
+ ///
+ ///
+ /// is .
+ ///
+ ///
+ ///
+ /// cannot be read.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// is less than 1.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// No data could be read from .
+ ///
+ ///
+ public void SendAsync(Stream stream, int length, Action completed)
+ {
+ if (_readyState != WebSocketState.Open)
+ {
+ var msg = "The current state of the connection is not Open.";
+ throw new InvalidOperationException(msg);
+ }
- _preAuth = preAuth;
- }
- }
+ if (stream == null)
+ throw new ArgumentNullException("stream");
- ///
- /// Sets the URL of the HTTP proxy server through which to connect and
- /// the credentials for the HTTP proxy authentication (Basic/Digest).
- ///
- ///
- /// This method does nothing if the connection has already been
- /// established or it is closing.
- ///
- ///
- ///
- /// A that represents the URL of the proxy server
- /// through which to connect.
- ///
- ///
- /// The syntax is http://<host>[:<port>].
- ///
- ///
- /// or an empty string if initializes the URL and
- /// the credentials.
- ///
- ///
- ///
- ///
- /// A that represents the username associated with
- /// the credentials.
- ///
- ///
- /// or an empty string if the credentials are not
- /// necessary.
- ///
- ///
- ///
- ///
- /// A that represents the password for the username
- /// associated with the credentials.
- ///
- ///
- /// or an empty string if not necessary.
- ///
- ///
- ///
- /// This instance is not a client.
- ///
- ///
- ///
- /// is not an absolute URI string.
- ///
- ///
- /// -or-
- ///
- ///
- /// The scheme of is not http.
- ///
- ///
- /// -or-
- ///
- ///
- /// includes the path segments.
- ///
- ///
- /// -or-
- ///
- ///
- /// contains an invalid character.
- ///
- ///
- /// -or-
- ///
- ///
- /// contains an invalid character.
- ///
- ///
- public void SetProxy (string url, string username, string password)
- {
- string msg = null;
+ if (!stream.CanRead)
+ {
+ var msg = "It cannot be read.";
+ throw new ArgumentException(msg, "stream");
+ }
- if (!_client) {
- msg = "This instance is not a client.";
- throw new InvalidOperationException (msg);
- }
+ if (length < 1)
+ {
+ var msg = "Less than 1.";
+ throw new ArgumentException(msg, "length");
+ }
- Uri uri = null;
+ var bytes = stream.ReadBytes(length);
- if (!url.IsNullOrEmpty ()) {
- if (!Uri.TryCreate (url, UriKind.Absolute, out uri)) {
- msg = "Not an absolute URI string.";
- throw new ArgumentException (msg, "url");
- }
+ var len = bytes.Length;
+ if (len == 0)
+ {
+ var msg = "No data could be read from it.";
+ throw new ArgumentException(msg, "stream");
+ }
- if (uri.Scheme != "http") {
- msg = "The scheme part is not http.";
- throw new ArgumentException (msg, "url");
- }
+ if (len < length)
+ {
+ _logger.Warn(
+ String.Format(
+ "Only {0} byte(s) of data could be read from the stream.",
+ len
+ )
+ );
+ }
- if (uri.Segments.Length > 1) {
- msg = "It includes the path segments.";
- throw new ArgumentException (msg, "url");
+ sendAsync(Opcode.Binary, new MemoryStream(bytes), completed);
}
- }
- if (!username.IsNullOrEmpty ()) {
- if (username.Contains (':') || !username.IsText ()) {
- msg = "It contains an invalid character.";
- throw new ArgumentException (msg, "username");
- }
- }
+ ///
+ /// Sets an HTTP cookie to send with the handshake request.
+ ///
+ ///
+ /// This method does nothing if the connection has already been
+ /// established or it is closing.
+ ///
+ ///
+ /// A that represents the cookie to send.
+ ///
+ ///
+ /// This instance is not a client.
+ ///
+ ///
+ /// is .
+ ///
+ public void SetCookie(Cookie cookie)
+ {
+ string msg = null;
+
+ if (!_client)
+ {
+ msg = "This instance is not a client.";
+ throw new InvalidOperationException(msg);
+ }
- if (!password.IsNullOrEmpty ()) {
- if (!password.IsText ()) {
- msg = "It contains an invalid character.";
- throw new ArgumentException (msg, "password");
- }
- }
+ if (cookie == null)
+ throw new ArgumentNullException("cookie");
+
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
- }
+ lock (_forState)
+ {
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
- lock (_forState) {
- if (!canSet (out msg)) {
- _logger.Warn (msg);
- return;
+ lock (_cookies.SyncRoot)
+ _cookies.SetOrRemove(cookie);
+ }
}
- if (url.IsNullOrEmpty ()) {
- _proxyUri = null;
- _proxyCredentials = null;
+ ///
+ /// Sets the credentials for the HTTP authentication (Basic/Digest).
+ ///
+ ///
+ /// This method does nothing if the connection has already been
+ /// established or it is closing.
+ ///
+ ///
+ ///
+ /// A that represents the username associated with
+ /// the credentials.
+ ///
+ ///
+ /// or an empty string if initializes
+ /// the credentials.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the password for the username
+ /// associated with the credentials.
+ ///
+ ///
+ /// or an empty string if not necessary.
+ ///
+ ///
+ ///
+ /// true if sends the credentials for the Basic authentication in
+ /// advance with the first handshake request; otherwise, false.
+ ///
+ ///
+ /// This instance is not a client.
+ ///
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ public void SetCredentials(string username, string password, bool preAuth)
+ {
+ string msg = null;
+
+ if (!_client)
+ {
+ msg = "This instance is not a client.";
+ throw new InvalidOperationException(msg);
+ }
+
+ if (!username.IsNullOrEmpty())
+ {
+ if (username.Contains(':') || !username.IsText())
+ {
+ msg = "It contains an invalid character.";
+ throw new ArgumentException(msg, "username");
+ }
+ }
+
+ if (!password.IsNullOrEmpty())
+ {
+ if (!password.IsText())
+ {
+ msg = "It contains an invalid character.";
+ throw new ArgumentException(msg, "password");
+ }
+ }
+
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
- return;
+ lock (_forState)
+ {
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
+
+ if (username.IsNullOrEmpty())
+ {
+ _credentials = null;
+ _preAuth = false;
+
+ return;
+ }
+
+ _credentials = new NetworkCredential(
+ username, password, _uri.PathAndQuery
+ );
+
+ _preAuth = preAuth;
+ }
}
- _proxyUri = uri;
- _proxyCredentials = !username.IsNullOrEmpty ()
- ? new NetworkCredential (
- username,
- password,
- String.Format (
- "{0}:{1}", _uri.DnsSafeHost, _uri.Port
- )
- )
- : null;
- }
- }
+ ///
+ /// Sets the URL of the HTTP proxy server through which to connect and
+ /// the credentials for the HTTP proxy authentication (Basic/Digest).
+ ///
+ ///
+ /// This method does nothing if the connection has already been
+ /// established or it is closing.
+ ///
+ ///
+ ///
+ /// A that represents the URL of the proxy server
+ /// through which to connect.
+ ///
+ ///
+ /// The syntax is http://<host>[:<port>].
+ ///
+ ///
+ /// or an empty string if initializes the URL and
+ /// the credentials.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the username associated with
+ /// the credentials.
+ ///
+ ///
+ /// or an empty string if the credentials are not
+ /// necessary.
+ ///
+ ///
+ ///
+ ///
+ /// A that represents the password for the username
+ /// associated with the credentials.
+ ///
+ ///
+ /// or an empty string if not necessary.
+ ///
+ ///
+ ///
+ /// This instance is not a client.
+ ///
+ ///
+ ///
+ /// is not an absolute URI string.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// The scheme of is not http.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// includes the path segments.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ /// -or-
+ ///
+ ///
+ /// contains an invalid character.
+ ///
+ ///
+ public void SetProxy(string url, string username, string password)
+ {
+ string msg = null;
+
+ if (!_client)
+ {
+ msg = "This instance is not a client.";
+ throw new InvalidOperationException(msg);
+ }
- #endregion
+ Uri uri = null;
+
+ if (!url.IsNullOrEmpty())
+ {
+ if (!Uri.TryCreate(url, UriKind.Absolute, out uri))
+ {
+ msg = "Not an absolute URI string.";
+ throw new ArgumentException(msg, "url");
+ }
+
+ if (uri.Scheme != "http")
+ {
+ msg = "The scheme part is not http.";
+ throw new ArgumentException(msg, "url");
+ }
+
+ if (uri.Segments.Length > 1)
+ {
+ msg = "It includes the path segments.";
+ throw new ArgumentException(msg, "url");
+ }
+ }
- #region Explicit Interface Implementations
+ if (!username.IsNullOrEmpty())
+ {
+ if (username.Contains(':') || !username.IsText())
+ {
+ msg = "It contains an invalid character.";
+ throw new ArgumentException(msg, "username");
+ }
+ }
- ///
- /// Closes the connection and releases all associated resources.
- ///
- ///
- ///
- /// This method closes the connection with close status 1001 (going away).
- ///
- ///
- /// And this method does nothing if the current state of the connection is
- /// Closing or Closed.
- ///
- ///
- void IDisposable.Dispose ()
- {
- close (1001, String.Empty);
- }
+ if (!password.IsNullOrEmpty())
+ {
+ if (!password.IsText())
+ {
+ msg = "It contains an invalid character.";
+ throw new ArgumentException(msg, "password");
+ }
+ }
- #endregion
- }
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
+
+ lock (_forState)
+ {
+ if (!canSet(out msg))
+ {
+ _logger.Warn(msg);
+ return;
+ }
+
+ if (url.IsNullOrEmpty())
+ {
+ _proxyUri = null;
+ _proxyCredentials = null;
+
+ return;
+ }
+
+ _proxyUri = uri;
+ _proxyCredentials = !username.IsNullOrEmpty()
+ ? new NetworkCredential(
+ username,
+ password,
+ String.Format(
+ "{0}:{1}", _uri.DnsSafeHost, _uri.Port
+ )
+ )
+ : null;
+ }
+ }
+
+ #endregion
+
+ #region Explicit Interface Implementations
+
+ ///
+ /// Closes the connection and releases all associated resources.
+ ///
+ ///
+ ///
+ /// This method closes the connection with close status 1001 (going away).
+ ///
+ ///
+ /// And this method does nothing if the current state of the connection is
+ /// Closing or Closed.
+ ///
+ ///
+ void IDisposable.Dispose()
+ {
+ close(1001, String.Empty);
+ }
+
+ #endregion
+ }
}