| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776 | using System;using System.Collections;using System.Collections.Generic;using System.IO;using System.Net;using System.Net.Sockets;using System.Text;using System.Threading;using System.Threading.Tasks;using SocketHttpListener.Net.WebSockets;using HttpStatusCode = SocketHttpListener.Net.HttpStatusCode;using WebSocketState = System.Net.WebSockets.WebSocketState;namespace SocketHttpListener{    /// <summary>    /// Implements the WebSocket interface.    /// </summary>    /// <remarks>    /// The WebSocket class provides a set of methods and properties for two-way communication using    /// the WebSocket protocol (<see href="http://tools.ietf.org/html/rfc6455">RFC 6455</see>).    /// </remarks>    public class WebSocket : IDisposable    {        #region Private Fields        private Action _closeContext;        private CompressionMethod _compression;        private WebSocketContext _context;        private CookieCollection _cookies;        private AutoResetEvent _exitReceiving;        private object _forConn;        private object _forEvent;        private object _forMessageEventQueue;        private object _forSend;        private const string _guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";        private Queue<MessageEventArgs> _messageEventQueue;        private string _protocol;        private volatile WebSocketState _readyState;        private AutoResetEvent _receivePong;        private bool _secure;        private Stream _stream;        private const string _version = "13";        #endregion        #region Internal Fields        internal const int FragmentLength = 1016; // Max value is int.MaxValue - 14.        #endregion        #region Internal Constructors        // As server        internal WebSocket(string protocol)        {            _protocol = protocol;        }        public void SetContext(HttpListenerWebSocketContext context, Action closeContextFn, Stream stream)        {            _context = context;            _closeContext = closeContextFn;            _secure = context.IsSecureConnection;            _stream = stream;            init();        }        // In the .NET Framework, this pulls the value from a P/Invoke.  Here we just hardcode it to a reasonable default.        public static TimeSpan DefaultKeepAliveInterval => TimeSpan.FromSeconds(30);        #endregion        /// <summary>        /// Gets the state of the WebSocket connection.        /// </summary>        /// <value>        /// One of the <see cref="WebSocketState"/> enum values, indicates the state of the WebSocket        /// connection. The default value is <see cref="WebSocketState.Connecting"/>.        /// </value>        public WebSocketState ReadyState => _readyState;        #region Public Events        /// <summary>        /// Occurs when the WebSocket connection has been closed.        /// </summary>        public event EventHandler<CloseEventArgs> OnClose;        /// <summary>        /// Occurs when the <see cref="WebSocket"/> gets an error.        /// </summary>        public event EventHandler<ErrorEventArgs> OnError;        /// <summary>        /// Occurs when the <see cref="WebSocket"/> receives a message.        /// </summary>        public event EventHandler<MessageEventArgs> OnMessage;        /// <summary>        /// Occurs when the WebSocket connection has been established.        /// </summary>        public event EventHandler OnOpen;        #endregion        #region Private Methods        private void close(CloseStatusCode code, string reason, bool wait)        {            close(new PayloadData(((ushort)code).Append(reason)), !code.IsReserved(), wait);        }        private void close(PayloadData payload, bool send, bool wait)        {            lock (_forConn)            {                if (_readyState == WebSocketState.CloseSent || _readyState == WebSocketState.Closed)                {                    return;                }                _readyState = WebSocketState.CloseSent;            }            var e = new CloseEventArgs(payload);            e.WasClean =              closeHandshake(                  send ? WebSocketFrame.CreateCloseFrame(Mask.Unmask, payload).ToByteArray() : null,                  wait ? 1000 : 0);            _readyState = WebSocketState.Closed;            try            {                OnClose.Emit(this, e);            }            catch (Exception ex)            {                error("An exception has occurred while OnClose.", ex);            }        }        private bool closeHandshake(byte[] frameAsBytes, int millisecondsTimeout)        {            var sent = frameAsBytes != null && writeBytes(frameAsBytes);            var received =              millisecondsTimeout == 0 ||              (sent && _exitReceiving != null && _exitReceiving.WaitOne(millisecondsTimeout));            closeServerResources();            if (_receivePong != null)            {                _receivePong.Dispose();                _receivePong = null;            }            if (_exitReceiving != null)            {                _exitReceiving.Dispose();                _exitReceiving = null;            }            var result = sent && received;            return result;        }        // As server        private void closeServerResources()        {            if (_closeContext == null)                return;            try            {                _closeContext();            }            catch (SocketException)            {                // it could be unable to send the handshake response            }            _closeContext = null;            _stream = null;            _context = null;        }        private bool concatenateFragmentsInto(Stream dest)        {            while (true)            {                var frame = WebSocketFrame.Read(_stream, true);                if (frame.IsFinal)                {                    /* FINAL */                    // CONT                    if (frame.IsContinuation)                    {                        dest.WriteBytes(frame.PayloadData.ApplicationData);                        break;                    }                    // PING                    if (frame.IsPing)                    {                        processPingFrame(frame);                        continue;                    }                    // PONG                    if (frame.IsPong)                    {                        processPongFrame(frame);                        continue;                    }                    // CLOSE                    if (frame.IsClose)                        return processCloseFrame(frame);                }                else                {                    /* MORE */                    // CONT                    if (frame.IsContinuation)                    {                        dest.WriteBytes(frame.PayloadData.ApplicationData);                        continue;                    }                }                // ?                return processUnsupportedFrame(                  frame,                  CloseStatusCode.IncorrectData,                  "An incorrect data has been received while receiving fragmented data.");            }            return true;        }        // As server        private HttpResponse createHandshakeCloseResponse(HttpStatusCode code)        {            var res = HttpResponse.CreateCloseResponse(code);            res.Headers["Sec-WebSocket-Version"] = _version;            return res;        }        private MessageEventArgs dequeueFromMessageEventQueue()        {            lock (_forMessageEventQueue)                return _messageEventQueue.Count > 0                       ? _messageEventQueue.Dequeue()                       : null;        }        private void enqueueToMessageEventQueue(MessageEventArgs e)        {            lock (_forMessageEventQueue)                _messageEventQueue.Enqueue(e);        }        private void error(string message, Exception exception)        {            try            {                if (exception != null)                {                    message += ". Exception.Message: " + exception.Message;                }                OnError.Emit(this, new ErrorEventArgs(message));            }            catch (Exception)            {            }        }        private void error(string message)        {            try            {                OnError.Emit(this, new ErrorEventArgs(message));            }            catch (Exception)            {            }        }        private void init()        {            _compression = CompressionMethod.None;            _cookies = new CookieCollection();            _forConn = new object();            _forEvent = new object();            _forSend = new object();            _messageEventQueue = new Queue<MessageEventArgs>();            _forMessageEventQueue = ((ICollection)_messageEventQueue).SyncRoot;            _readyState = WebSocketState.Connecting;        }        private void open()        {            try            {                startReceiving();                lock (_forEvent)                {                    try                    {                        if (OnOpen != null)                        {                            OnOpen(this, EventArgs.Empty);                        }                    }                    catch (Exception ex)                    {                        processException(ex, "An exception has occurred while OnOpen.");                    }                }            }            catch (Exception ex)            {                processException(ex, "An exception has occurred while opening.");            }        }        private bool processCloseFrame(WebSocketFrame frame)        {            var payload = frame.PayloadData;            close(payload, !payload.ContainsReservedCloseStatusCode, false);            return false;        }        private bool processDataFrame(WebSocketFrame frame)        {            var e = frame.IsCompressed                    ? new MessageEventArgs(                        frame.Opcode, frame.PayloadData.ApplicationData.Decompress(_compression))                    : new MessageEventArgs(frame.Opcode, frame.PayloadData);            enqueueToMessageEventQueue(e);            return true;        }        private void processException(Exception exception, string message)        {            var code = CloseStatusCode.Abnormal;            var reason = message;            if (exception is WebSocketException)            {                var wsex = (WebSocketException)exception;                code = wsex.Code;                reason = wsex.Message;            }            error(message ?? code.GetMessage(), exception);            if (_readyState == WebSocketState.Connecting)                Close(HttpStatusCode.BadRequest);            else                close(code, reason ?? code.GetMessage(), false);        }        private bool processFragmentedFrame(WebSocketFrame frame)        {            return frame.IsContinuation // Not first fragment                   ? true                   : processFragments(frame);        }        private bool processFragments(WebSocketFrame first)        {            using (var buff = new MemoryStream())            {                buff.WriteBytes(first.PayloadData.ApplicationData);                if (!concatenateFragmentsInto(buff))                    return false;                byte[] data;                if (_compression != CompressionMethod.None)                {                    data = buff.DecompressToArray(_compression);                }                else                {                    data = buff.ToArray();                }                enqueueToMessageEventQueue(new MessageEventArgs(first.Opcode, data));                return true;            }        }        private bool processPingFrame(WebSocketFrame frame)        {            return true;        }        private bool processPongFrame(WebSocketFrame frame)        {            _receivePong.Set();            return true;        }        private bool processUnsupportedFrame(WebSocketFrame frame, CloseStatusCode code, string reason)        {            processException(new WebSocketException(code, reason), null);            return false;        }        private bool processWebSocketFrame(WebSocketFrame frame)        {            return frame.IsCompressed && _compression == CompressionMethod.None                   ? processUnsupportedFrame(                       frame,                       CloseStatusCode.IncorrectData,                       "A compressed data has been received without available decompression method.")                   : frame.IsFragmented                     ? processFragmentedFrame(frame)                     : frame.IsData                       ? processDataFrame(frame)                       : frame.IsPing                         ? processPingFrame(frame)                         : frame.IsPong                           ? processPongFrame(frame)                           : frame.IsClose                             ? processCloseFrame(frame)                             : processUnsupportedFrame(frame, CloseStatusCode.PolicyViolation, null);        }        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, Mask.Unmask, stream, compressed);                    if (!sent)                        error("Sending a data has been interrupted.");                }                catch (Exception ex)                {                    error("An exception has occurred while sending a data.", ex);                }                finally                {                    if (compressed)                        stream.Dispose();                    src.Dispose();                }                return sent;            }        }        private bool send(Opcode opcode, Mask mask, Stream stream, bool compressed)        {            var len = stream.Length;            /* Not fragmented */            if (len == 0)                return send(Fin.Final, opcode, mask, new byte[0], compressed);            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, mask, buff, compressed);            }            buff = new byte[FragmentLength];            if (quo == 1 && rem == 0)                return stream.Read(buff, 0, FragmentLength) == FragmentLength &&                       send(Fin.Final, opcode, mask, buff, compressed);            /* Send fragmented */            // Begin            if (stream.Read(buff, 0, FragmentLength) != FragmentLength ||                !send(Fin.More, opcode, mask, buff, compressed))                return false;            var n = rem == 0 ? quo - 2 : quo - 1;            for (long i = 0; i < n; i++)                if (stream.Read(buff, 0, FragmentLength) != FragmentLength ||                    !send(Fin.More, Opcode.Cont, mask, buff, compressed))                    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, mask, buff, compressed);        }        private bool send(Fin fin, Opcode opcode, Mask mask, byte[] data, bool compressed)        {            lock (_forConn)            {                if (_readyState != WebSocketState.Open)                {                    return false;                }                return writeBytes(                  WebSocketFrame.CreateWebSocketFrame(fin, opcode, mask, data, compressed).ToByteArray());            }        }        private Task sendAsync(Opcode opcode, Stream stream)        {            var completionSource = new TaskCompletionSource<bool>();            Task.Run(() =>           {               try               {                   send(opcode, stream);                   completionSource.TrySetResult(true);               }               catch (Exception ex)               {                   completionSource.TrySetException(ex);               }           });            return completionSource.Task;        }        // As server        private bool sendHttpResponse(HttpResponse response)        {            return writeBytes(response.ToByteArray());        }        private void startReceiving()        {            if (_messageEventQueue.Count > 0)                _messageEventQueue.Clear();            _exitReceiving = new AutoResetEvent(false);            _receivePong = new AutoResetEvent(false);            Action receive = null;            receive = () => WebSocketFrame.ReadAsync(              _stream,              true,              frame =>              {                  if (processWebSocketFrame(frame) && _readyState != WebSocketState.Closed)                  {                      receive();                      if (!frame.IsData)                          return;                      lock (_forEvent)                      {                          try                          {                              var e = dequeueFromMessageEventQueue();                              if (e != null && _readyState == WebSocketState.Open)                                  OnMessage.Emit(this, e);                          }                          catch (Exception ex)                          {                              processException(ex, "An exception has occurred while OnMessage.");                          }                      }                  }                  else if (_exitReceiving != null)                  {                      _exitReceiving.Set();                  }              },              ex => processException(ex, "An exception has occurred while receiving a message."));            receive();        }        private bool writeBytes(byte[] data)        {            try            {                _stream.Write(data, 0, data.Length);                return true;            }            catch (Exception)            {                return false;            }        }        #endregion        #region Internal Methods        // As server        internal void Close(HttpResponse response)        {            _readyState = WebSocketState.CloseSent;            sendHttpResponse(response);            closeServerResources();            _readyState = WebSocketState.Closed;        }        // As server        internal void Close(HttpStatusCode code)        {            Close(createHandshakeCloseResponse(code));        }        // As server        public void ConnectAsServer()        {            try            {                _readyState = WebSocketState.Open;                open();            }            catch (Exception ex)            {                processException(ex, "An exception has occurred while connecting.");            }        }        #endregion        #region Public Methods        /// <summary>        /// Closes the WebSocket connection, and releases all associated resources.        /// </summary>        public void Close()        {            var msg = _readyState.CheckIfClosable();            if (msg != null)            {                error(msg);                return;            }            var send = _readyState == WebSocketState.Open;            close(new PayloadData(), send, send);        }        /// <summary>        /// Closes the WebSocket connection with the specified <see cref="CloseStatusCode"/>        /// and <see cref="string"/>, and releases all associated resources.        /// </summary>        /// <remarks>        /// This method emits a <see cref="OnError"/> event if the size        /// of <paramref name="reason"/> is greater than 123 bytes.        /// </remarks>        /// <param name="code">        /// One of the <see cref="CloseStatusCode"/> enum values, represents the status code        /// indicating the reason for the close.        /// </param>        /// <param name="reason">        /// A <see cref="string"/> that represents the reason for the close.        /// </param>        public void Close(CloseStatusCode code, string reason)        {            byte[] data = null;            var msg = _readyState.CheckIfClosable() ??                      (data = ((ushort)code).Append(reason)).CheckIfValidControlData("reason");            if (msg != null)            {                error(msg);                return;            }            var send = _readyState == WebSocketState.Open && !code.IsReserved();            close(new PayloadData(data), send, send);        }        /// <summary>        /// Sends a binary <paramref name="data"/> asynchronously using the WebSocket connection.        /// </summary>        /// <remarks>        /// This method doesn't wait for the send to be complete.        /// </remarks>        /// <param name="data">        /// An array of <see cref="byte"/> that represents the binary data to send.        /// </param>        public Task SendAsync(byte[] data)        {            if (data == null)            {                throw new ArgumentNullException(nameof(data));            }            var msg = _readyState.CheckIfOpen();            if (msg != null)            {                throw new Exception(msg);            }            return sendAsync(Opcode.Binary, new MemoryStream(data));        }        /// <summary>        /// Sends a text <paramref name="data"/> asynchronously using the WebSocket connection.        /// </summary>        /// <remarks>        /// This method doesn't wait for the send to be complete.        /// </remarks>        /// <param name="data">        /// A <see cref="string"/> that represents the text data to send.        /// </param>        public Task SendAsync(string data)        {            if (data == null)            {                throw new ArgumentNullException(nameof(data));            }            var msg = _readyState.CheckIfOpen();            if (msg != null)            {                throw new Exception(msg);            }            return sendAsync(Opcode.Text, new MemoryStream(Encoding.UTF8.GetBytes(data)));        }        #endregion        #region Explicit Interface Implementation        /// <summary>        /// Closes the WebSocket connection, and releases all associated resources.        /// </summary>        /// <remarks>        /// This method closes the WebSocket connection with <see cref="CloseStatusCode.Away"/>.        /// </remarks>        void IDisposable.Dispose()        {            Close(CloseStatusCode.Away, null);        }        #endregion    }}
 |