#region License /* * HttpServer.cs * * A simple HTTP server that allows to accept WebSocket handshake requests. * * The MIT License * * Copyright (c) 2012-2016 sta.blockhead * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #endregion #region Contributors /* * Contributors: * - Juan Manuel Lallana * - Liryna * - Rohan Singh */ #endregion using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using System.Security.Principal; using System.Text; using System.Threading; using WebSocketSharp.Net; using WebSocketSharp.Net.WebSockets; namespace WebSocketSharp.Server { /// /// Provides a simple HTTP server that allows to accept /// WebSocket handshake requests. /// /// /// This class can provide multiple WebSocket services. /// public class HttpServer { #region Private Fields private System.Net.IPAddress _address; private string _docRootPath; private string _hostname; private HttpListener _listener; private Logger _log; private int _port; private Thread _receiveThread; private bool _secure; private WebSocketServiceManager _services; private volatile ServerState _state; private object _sync; #endregion #region Public Constructors /// /// Initializes a new instance of the class. /// /// /// The new instance listens for incoming requests on /// and port 80. /// public HttpServer () { init ("*", System.Net.IPAddress.Any, 80, false); } /// /// Initializes a new instance of the class with /// the specified . /// /// /// /// The new instance listens for incoming requests on /// and . /// /// /// It provides secure connections if is 443. /// /// /// /// An that represents the number of the port /// on which to listen. /// /// /// is less than 1 or greater than 65535. /// public HttpServer (int port) : this (port, port == 443) { } /// /// Initializes a new instance of the class with /// the specified . /// /// /// /// The new instance listens for incoming requests on the IP address of the /// host of and the port of . /// /// /// Either port 80 or 443 is used if includes /// no port. Port 443 is used if the scheme of /// is https; otherwise, port 80 is used. /// /// /// The new instance provides secure connections if the scheme of /// is https. /// /// /// /// A that represents the HTTP URL of the server. /// /// /// is . /// /// /// /// is empty. /// /// /// -or- /// /// /// is invalid. /// /// public HttpServer (string url) { if (url == null) throw new ArgumentNullException ("url"); if (url.Length == 0) throw new ArgumentException ("An empty string.", "url"); Uri uri; string msg; if (!tryCreateUri (url, out uri, out msg)) throw new ArgumentException (msg, "url"); var host = uri.GetDnsSafeHost (true); var addr = host.ToIPAddress (); if (addr == null) { msg = "The host part could not be converted to an IP address."; throw new ArgumentException (msg, "url"); } if (!addr.IsLocal ()) { msg = "The IP address of the host is not a local IP address."; throw new ArgumentException (msg, "url"); } init (host, addr, uri.Port, uri.Scheme == "https"); } /// /// Initializes a new instance of the class with /// the specified and . /// /// /// The new instance listens for incoming requests on /// and . /// /// /// An that represents the number of the port /// on which to listen. /// /// /// A : true if the new instance provides /// secure connections; otherwise, false. /// /// /// is less than 1 or greater than 65535. /// public HttpServer (int port, bool secure) { if (!port.IsPortNumber ()) { var msg = "Less than 1 or greater than 65535."; throw new ArgumentOutOfRangeException ("port", msg); } init ("*", System.Net.IPAddress.Any, port, secure); } /// /// Initializes a new instance of the class with /// the specified and . /// /// /// /// The new instance listens for incoming requests on /// and . /// /// /// It provides secure connections if is 443. /// /// /// /// A that represents /// the local IP address on which to listen. /// /// /// An that represents the number of the port /// on which to listen. /// /// /// is . /// /// /// is not a local IP address. /// /// /// is less than 1 or greater than 65535. /// public HttpServer (System.Net.IPAddress address, int port) : this (address, port, port == 443) { } /// /// Initializes a new instance of the class with /// the specified , , /// and . /// /// /// The new instance listens for incoming requests on /// and . /// /// /// A that represents /// the local IP address on which to listen. /// /// /// An that represents the number of the port /// on which to listen. /// /// /// A : true if the new instance provides /// secure connections; otherwise, false. /// /// /// is . /// /// /// is not a local IP address. /// /// /// is less than 1 or greater than 65535. /// public HttpServer (System.Net.IPAddress address, int port, bool secure) { if (address == null) throw new ArgumentNullException ("address"); if (!address.IsLocal ()) throw new ArgumentException ("Not a local IP address.", "address"); if (!port.IsPortNumber ()) { var msg = "Less than 1 or greater than 65535."; throw new ArgumentOutOfRangeException ("port", msg); } init (address.ToString (true), address, port, secure); } #endregion #region Public Properties /// /// Gets the IP address of the server. /// /// /// A that represents the local /// IP address on which to listen for incoming requests. /// public System.Net.IPAddress Address { get { return _address; } } /// /// Gets or sets the scheme used to authenticate the clients. /// /// /// The set operation does nothing if the server has already /// started or it is shutting down. /// /// /// /// One of the /// enum values. /// /// /// It represents the scheme used to authenticate the clients. /// /// /// The default value is /// . /// /// public AuthenticationSchemes AuthenticationSchemes { get { return _listener.AuthenticationSchemes; } set { string msg; if (!canSet (out msg)) { _log.Warn (msg); return; } lock (_sync) { if (!canSet (out msg)) { _log.Warn (msg); return; } _listener.AuthenticationSchemes = value; } } } /// /// Gets or sets the path to the document folder of the server. /// /// /// /// '/' or '\' is trimmed from the end of the value if any. /// /// /// The set operation does nothing if the server has already /// started or it is shutting down. /// /// /// /// /// A that represents a path to the folder /// from which to find the requested file. /// /// /// The default value is "./Public". /// /// /// /// The value specified for a set operation is . /// /// /// /// The value specified for a set operation is an empty string. /// /// /// -or- /// /// /// The value specified for a set operation is an invalid path string. /// /// /// -or- /// /// /// The value specified for a set operation is an absolute root. /// /// public string DocumentRootPath { get { return _docRootPath; } set { if (value == null) throw new ArgumentNullException ("value"); if (value.Length == 0) throw new ArgumentException ("An empty string.", "value"); value = value.TrimSlashOrBackslashFromEnd (); string full = null; try { full = Path.GetFullPath (value); } catch (Exception ex) { throw new ArgumentException ("An invalid path string.", "value", ex); } if (value == "/") throw new ArgumentException ("An absolute root.", "value"); if (value == "\\") throw new ArgumentException ("An absolute root.", "value"); if (value.Length == 2 && value[1] == ':') throw new ArgumentException ("An absolute root.", "value"); if (full == "/") throw new ArgumentException ("An absolute root.", "value"); full = full.TrimSlashOrBackslashFromEnd (); if (full.Length == 2 && full[1] == ':') throw new ArgumentException ("An absolute root.", "value"); string msg; if (!canSet (out msg)) { _log.Warn (msg); return; } lock (_sync) { if (!canSet (out msg)) { _log.Warn (msg); return; } _docRootPath = value; } } } /// /// Gets a value indicating whether the server has started. /// /// /// true if the server has started; otherwise, false. /// public bool IsListening { get { return _state == ServerState.Start; } } /// /// Gets a value indicating whether secure connections are provided. /// /// /// true if this instance provides secure connections; otherwise, /// false. /// public bool IsSecure { get { return _secure; } } /// /// Gets or sets a value indicating whether the server cleans up /// the inactive sessions periodically. /// /// /// The set operation does nothing if the server has already /// started or it is shutting down. /// /// /// /// true if the server cleans up the inactive sessions /// every 60 seconds; otherwise, false. /// /// /// The default value is true. /// /// public bool KeepClean { get { return _services.KeepClean; } set { _services.KeepClean = value; } } /// /// Gets the logging function for the server. /// /// /// The default logging level is . /// /// /// A that provides the logging function. /// public Logger Log { get { return _log; } } /// /// Gets the port of the server. /// /// /// An that represents the number of the port /// on which to listen for incoming requests. /// public int Port { get { return _port; } } /// /// Gets or sets the realm used for authentication. /// /// /// /// "SECRET AREA" is used as the realm if the value is /// or an empty string. /// /// /// The set operation does nothing if the server has /// already started or it is shutting down. /// /// /// /// /// A or by default. /// /// /// That string represents the name of the realm. /// /// public string Realm { get { return _listener.Realm; } set { string msg; if (!canSet (out msg)) { _log.Warn (msg); return; } lock (_sync) { if (!canSet (out msg)) { _log.Warn (msg); return; } _listener.Realm = value; } } } /// /// Gets or sets a value indicating whether the server is allowed to /// be bound to an address that is already in use. /// /// /// /// You should set this property to true if you would /// like to resolve to wait for socket in TIME_WAIT state. /// /// /// The set operation does nothing if the server has already /// started or it is shutting down. /// /// /// /// /// true if the server is allowed to be bound to an address /// that is already in use; otherwise, false. /// /// /// The default value is false. /// /// public bool ReuseAddress { get { return _listener.ReuseAddress; } set { string msg; if (!canSet (out msg)) { _log.Warn (msg); return; } lock (_sync) { if (!canSet (out msg)) { _log.Warn (msg); return; } _listener.ReuseAddress = value; } } } /// /// Gets the configuration for secure connection. /// /// /// This configuration will be referenced when attempts to start, /// so it must be configured before the start method is called. /// /// /// A that represents /// the configuration used to provide secure connections. /// /// /// This instance does not provide secure connections. /// public ServerSslConfiguration SslConfiguration { get { if (!_secure) { var msg = "This instance does not provide secure connections."; throw new InvalidOperationException (msg); } return _listener.SslConfiguration; } } /// /// Gets or sets the delegate used to find the credentials /// for an identity. /// /// /// /// No credentials are found if the method invoked by /// the delegate returns or /// the value is . /// /// /// The set operation does nothing if the server has /// already started or it is shutting down. /// /// /// /// /// A Func<, /// > delegate or /// if not needed. /// /// /// That delegate invokes the method called for finding /// the credentials used to authenticate a client. /// /// /// The default value is . /// /// public Func UserCredentialsFinder { get { return _listener.UserCredentialsFinder; } set { string msg; if (!canSet (out msg)) { _log.Warn (msg); return; } lock (_sync) { if (!canSet (out msg)) { _log.Warn (msg); return; } _listener.UserCredentialsFinder = value; } } } /// /// Gets or sets the time to wait for the response to the WebSocket Ping or /// Close. /// /// /// The set operation does nothing if the server has already started or /// it is shutting down. /// /// /// /// A to wait for the response. /// /// /// The default value is the same as 1 second. /// /// /// /// The value specified for a set operation is zero or less. /// public TimeSpan WaitTime { get { return _services.WaitTime; } set { _services.WaitTime = value; } } /// /// Gets the management function for the WebSocket services /// provided by the server. /// /// /// A that manages /// the WebSocket services provided by the server. /// public WebSocketServiceManager WebSocketServices { get { return _services; } } #endregion #region Public Events /// /// Occurs when the server receives an HTTP CONNECT request. /// public event EventHandler OnConnect; /// /// Occurs when the server receives an HTTP DELETE request. /// public event EventHandler OnDelete; /// /// Occurs when the server receives an HTTP GET request. /// public event EventHandler OnGet; /// /// Occurs when the server receives an HTTP HEAD request. /// public event EventHandler OnHead; /// /// Occurs when the server receives an HTTP OPTIONS request. /// public event EventHandler OnOptions; /// /// Occurs when the server receives an HTTP POST request. /// public event EventHandler OnPost; /// /// Occurs when the server receives an HTTP PUT request. /// public event EventHandler OnPut; /// /// Occurs when the server receives an HTTP TRACE request. /// public event EventHandler OnTrace; #endregion #region Private Methods private void abort () { lock (_sync) { if (_state != ServerState.Start) return; _state = ServerState.ShuttingDown; } try { try { _services.Stop (1006, String.Empty); } finally { _listener.Abort (); } } catch { } _state = ServerState.Stop; } private bool canSet (out string message) { message = null; if (_state == ServerState.Start) { message = "The server has already started."; return false; } if (_state == ServerState.ShuttingDown) { message = "The server is shutting down."; return false; } return true; } private bool checkCertificate (out string message) { message = null; var byUser = _listener.SslConfiguration.ServerCertificate != null; var path = _listener.CertificateFolderPath; var withPort = EndPointListener.CertificateExists (_port, path); if (!(byUser || withPort)) { message = "There is no server certificate for secure connection."; return false; } if (byUser && withPort) _log.Warn ("The server certificate associated with the port is used."); return true; } private string createFilePath (string childPath) { childPath = childPath.TrimStart ('/', '\\'); return new StringBuilder (_docRootPath, 32) .AppendFormat ("/{0}", childPath) .ToString () .Replace ('\\', '/'); } private static HttpListener createListener ( string hostname, int port, bool secure ) { var lsnr = new HttpListener (); var schm = secure ? "https" : "http"; var pref = String.Format ("{0}://{1}:{2}/", schm, hostname, port); lsnr.Prefixes.Add (pref); return lsnr; } private void init ( string hostname, System.Net.IPAddress address, int port, bool secure ) { _hostname = hostname; _address = address; _port = port; _secure = secure; _docRootPath = "./Public"; _listener = createListener (_hostname, _port, _secure); _log = _listener.Log; _services = new WebSocketServiceManager (_log); _sync = new object (); } private void processRequest (HttpListenerContext context) { var method = context.Request.HttpMethod; var evt = method == "GET" ? OnGet : method == "HEAD" ? OnHead : method == "POST" ? OnPost : method == "PUT" ? OnPut : method == "DELETE" ? OnDelete : method == "CONNECT" ? OnConnect : method == "OPTIONS" ? OnOptions : method == "TRACE" ? OnTrace : null; if (evt != null) evt (this, new HttpRequestEventArgs (context, _docRootPath)); else context.Response.StatusCode = 501; // Not Implemented context.Response.Close (); } private void processRequest (HttpListenerWebSocketContext context) { var uri = context.RequestUri; if (uri == null) { context.Close (HttpStatusCode.BadRequest); return; } var path = uri.AbsolutePath; if (path.IndexOfAny (new[] { '%', '+' }) > -1) path = HttpUtility.UrlDecode (path, Encoding.UTF8); WebSocketServiceHost host; if (!_services.InternalTryGetServiceHost (path, out host)) { context.Close (HttpStatusCode.NotImplemented); return; } host.StartSession (context); } private void receiveRequest () { while (true) { HttpListenerContext ctx = null; try { ctx = _listener.GetContext (); ThreadPool.QueueUserWorkItem ( state => { try { if (ctx.Request.IsUpgradeRequest ("websocket")) { processRequest (ctx.AcceptWebSocket (null)); return; } processRequest (ctx); } catch (Exception ex) { _log.Fatal (ex.Message); _log.Debug (ex.ToString ()); ctx.Connection.Close (true); } } ); } catch (HttpListenerException) { _log.Info ("The underlying listener is stopped."); break; } catch (InvalidOperationException) { _log.Info ("The underlying listener is stopped."); break; } catch (Exception ex) { _log.Fatal (ex.Message); _log.Debug (ex.ToString ()); if (ctx != null) ctx.Connection.Close (true); break; } } if (_state != ServerState.ShuttingDown) abort (); } private void start () { if (_state == ServerState.Start) { _log.Info ("The server has already started."); return; } if (_state == ServerState.ShuttingDown) { _log.Warn ("The server is shutting down."); return; } lock (_sync) { if (_state == ServerState.Start) { _log.Info ("The server has already started."); return; } if (_state == ServerState.ShuttingDown) { _log.Warn ("The server is shutting down."); return; } _services.Start (); try { startReceiving (); } catch { _services.Stop (1011, String.Empty); throw; } _state = ServerState.Start; } } private void startReceiving () { try { _listener.Start (); } catch (Exception ex) { var msg = "The underlying listener has failed to start."; throw new InvalidOperationException (msg, ex); } _receiveThread = new Thread (new ThreadStart (receiveRequest)); _receiveThread.IsBackground = true; _receiveThread.Start (); } private void stop (ushort code, string reason) { if (_state == ServerState.Ready) { _log.Info ("The server is not started."); return; } if (_state == ServerState.ShuttingDown) { _log.Info ("The server is shutting down."); return; } if (_state == ServerState.Stop) { _log.Info ("The server has already stopped."); return; } lock (_sync) { if (_state == ServerState.ShuttingDown) { _log.Info ("The server is shutting down."); return; } if (_state == ServerState.Stop) { _log.Info ("The server has already stopped."); return; } _state = ServerState.ShuttingDown; } try { var threw = false; try { _services.Stop (code, reason); } catch { threw = true; throw; } finally { try { stopReceiving (5000); } catch { if (!threw) throw; } } } finally { _state = ServerState.Stop; } } private void stopReceiving (int millisecondsTimeout) { _listener.Stop (); _receiveThread.Join (millisecondsTimeout); } private static bool tryCreateUri ( string uriString, out Uri result, out string message ) { result = null; message = null; var uri = uriString.ToUri (); if (uri == null) { message = "An invalid URI string."; return false; } if (!uri.IsAbsoluteUri) { message = "A relative URI."; return false; } var schm = uri.Scheme; if (!(schm == "http" || schm == "https")) { message = "The scheme part is not 'http' or 'https'."; return false; } if (uri.PathAndQuery != "/") { message = "It includes either or both path and query components."; return false; } if (uri.Fragment.Length > 0) { message = "It includes the fragment component."; return false; } if (uri.Port == 0) { message = "The port part is zero."; return false; } result = uri; return true; } #endregion #region Public Methods /// /// Adds a WebSocket service with the specified behavior, path, /// and delegate. /// /// /// /// A that represents an absolute path to /// the service to add. /// /// /// / is trimmed from the end of the string if present. /// /// /// /// /// A Func<TBehavior> delegate. /// /// /// It invokes the method called when creating a new session /// instance for the service. /// /// /// The method must create a new instance of the specified /// behavior class and return it. /// /// /// /// /// The type of the behavior for the service. /// /// /// It must inherit the class. /// /// /// /// /// is . /// /// /// -or- /// /// /// is . /// /// /// /// /// is an empty string. /// /// /// -or- /// /// /// is not an absolute path. /// /// /// -or- /// /// /// includes either or both /// query and fragment components. /// /// /// -or- /// /// /// is already in use. /// /// [Obsolete ("This method will be removed. Use added one instead.")] public void AddWebSocketService ( string path, Func creator ) where TBehavior : WebSocketBehavior { if (path == null) throw new ArgumentNullException ("path"); if (creator == null) throw new ArgumentNullException ("creator"); if (path.Length == 0) throw new ArgumentException ("An empty string.", "path"); if (path[0] != '/') throw new ArgumentException ("Not an absolute path.", "path"); if (path.IndexOfAny (new[] { '?', '#' }) > -1) { var msg = "It includes either or both query and fragment components."; throw new ArgumentException (msg, "path"); } _services.Add (path, creator); } /// /// Adds a WebSocket service with the specified behavior and path. /// /// /// /// A that represents an absolute path to /// the service to add. /// /// /// / is trimmed from the end of the string if present. /// /// /// /// /// The type of the behavior for the service. /// /// /// It must inherit the class. /// /// /// And also, it must have a public parameterless constructor. /// /// /// /// is . /// /// /// /// is an empty string. /// /// /// -or- /// /// /// is not an absolute path. /// /// /// -or- /// /// /// includes either or both /// query and fragment components. /// /// /// -or- /// /// /// is already in use. /// /// public void AddWebSocketService (string path) where TBehaviorWithNew : WebSocketBehavior, new () { _services.AddService (path, null); } /// /// Adds a WebSocket service with the specified behavior, path, /// and delegate. /// /// /// /// A that represents an absolute path to /// the service to add. /// /// /// / is trimmed from the end of the string if present. /// /// /// /// /// An Action<TBehaviorWithNew> delegate or /// if not needed. /// /// /// The delegate invokes the method called when initializing /// a new session instance for the service. /// /// /// /// /// The type of the behavior for the service. /// /// /// It must inherit the class. /// /// /// And also, it must have a public parameterless constructor. /// /// /// /// is . /// /// /// /// is an empty string. /// /// /// -or- /// /// /// is not an absolute path. /// /// /// -or- /// /// /// includes either or both /// query and fragment components. /// /// /// -or- /// /// /// is already in use. /// /// public void AddWebSocketService ( string path, Action initializer ) where TBehaviorWithNew : WebSocketBehavior, new () { _services.AddService (path, initializer); } /// /// Gets the contents of the specified file from the document /// folder of the server. /// /// /// /// An array of or /// if it fails. /// /// /// That array represents the contents of the file. /// /// /// /// A that represents a virtual path to /// find the file from the document folder. /// /// /// is . /// /// /// /// is an empty string. /// /// /// -or- /// /// /// contains "..". /// /// [Obsolete ("This method will be removed.")] public byte[] GetFile (string path) { if (path == null) throw new ArgumentNullException ("path"); if (path.Length == 0) throw new ArgumentException ("An empty string.", "path"); if (path.IndexOf ("..") > -1) throw new ArgumentException ("It contains '..'.", "path"); path = createFilePath (path); return File.Exists (path) ? File.ReadAllBytes (path) : null; } /// /// Removes a WebSocket service with the specified path. /// /// /// The service is stopped with close status 1001 (going away) /// if it has already started. /// /// /// true if the service is successfully found and removed; /// otherwise, false. /// /// /// /// A that represents an absolute path to /// the service to remove. /// /// /// / is trimmed from the end of the string if present. /// /// /// /// is . /// /// /// /// is an empty string. /// /// /// -or- /// /// /// is not an absolute path. /// /// /// -or- /// /// /// includes either or both /// query and fragment components. /// /// public bool RemoveWebSocketService (string path) { return _services.RemoveService (path); } /// /// Starts receiving incoming requests. /// /// /// This method does nothing if the server has already started or /// it is shutting down. /// /// /// /// There is no server certificate for secure connection. /// /// /// -or- /// /// /// The underlying has failed to start. /// /// public void Start () { if (_secure) { string msg; if (!checkCertificate (out msg)) throw new InvalidOperationException (msg); } start (); } /// /// Stops receiving incoming requests. /// public void Stop () { stop (1001, String.Empty); } /// /// Stops receiving incoming requests and closes each connection. /// /// /// /// A that represents the status code indicating /// the reason for the WebSocket connection close. /// /// /// The status codes are defined in /// /// Section 7.4 of RFC 6455. /// /// /// /// /// A that represents the reason for the WebSocket /// connection 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 1010 (mandatory extension). /// /// /// -or- /// /// /// is 1005 (no status) and there is reason. /// /// /// -or- /// /// /// could not be UTF-8-encoded. /// /// [Obsolete ("This method will be removed.")] public void Stop (ushort code, string reason) { if (!code.IsCloseStatusCode ()) { var msg = "Less than 1000 or greater than 4999."; throw new ArgumentOutOfRangeException ("code", msg); } if (code == 1010) { var msg = "1010 cannot be used."; throw new ArgumentException (msg, "code"); } if (!reason.IsNullOrEmpty ()) { 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); } } stop (code, reason); } /// /// Stops receiving incoming requests and closes each connection. /// /// /// /// One of the enum values. /// /// /// It represents the status code indicating the reason for the WebSocket /// connection close. /// /// /// /// /// A that represents the reason for the WebSocket /// connection close. /// /// /// The size must be 123 bytes or less in UTF-8. /// /// /// /// The size of is greater than 123 bytes. /// /// /// /// is /// . /// /// /// -or- /// /// /// is /// and there is reason. /// /// /// -or- /// /// /// could not be UTF-8-encoded. /// /// [Obsolete ("This method will be removed.")] public void Stop (CloseStatusCode code, string reason) { if (code == CloseStatusCode.MandatoryExtension) { var msg = "MandatoryExtension cannot be used."; throw new ArgumentException (msg, "code"); } if (!reason.IsNullOrEmpty ()) { 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); } } stop ((ushort) code, reason); } #endregion } }