12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172 |
- #region License
- /*
- * WebSocketServer.cs
- *
- * The MIT License
- *
- * Copyright (c) 2012-2023 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 <juan.manuel.lallana@gmail.com>
- * - Jonas Hovgaard <j@jhovgaard.dk>
- * - Liryna <liryna.stark@gmail.com>
- * - Rohan Singh <rohan-singh@hotmail.com>
- */
- #endregion
- using System;
- using System.Collections.Generic;
- 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
- {
- /// <summary>
- /// Provides a WebSocket protocol server.
- /// </summary>
- /// <remarks>
- /// This class can provide multiple WebSocket services.
- /// </remarks>
- public class WebSocketServer
- {
- #region Private Fields
- private System.Net.IPAddress _address;
- private AuthenticationSchemes _authSchemes;
- private static readonly string _defaultRealm;
- private bool _dnsStyle;
- private string _hostname;
- private TcpListener _listener;
- private Logger _log;
- private int _port;
- private string _realm;
- private string _realmInUse;
- private Thread _receiveThread;
- private bool _reuseAddress;
- private bool _secure;
- private WebSocketServiceManager _services;
- private ServerSslConfiguration _sslConfig;
- private ServerSslConfiguration _sslConfigInUse;
- private volatile ServerState _state;
- private object _sync;
- private Func<IIdentity, NetworkCredential> _userCredFinder;
- #endregion
- #region Static Constructor
- static WebSocketServer ()
- {
- _defaultRealm = "SECRET AREA";
- }
- #endregion
- #region Public Constructors
- /// <summary>
- /// Initializes a new instance of the <see cref="WebSocketServer"/> class.
- /// </summary>
- /// <remarks>
- /// The new instance listens for incoming handshake requests on
- /// <see cref="System.Net.IPAddress.Any"/> and port 80.
- /// </remarks>
- public WebSocketServer ()
- {
- var addr = System.Net.IPAddress.Any;
- init (addr.ToString (), addr, 80, false);
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="WebSocketServer"/> class
- /// with the specified port.
- /// </summary>
- /// <remarks>
- /// <para>
- /// The new instance listens for incoming handshake requests on
- /// <see cref="System.Net.IPAddress.Any"/> and <paramref name="port"/>.
- /// </para>
- /// <para>
- /// It provides secure connections if <paramref name="port"/> is 443.
- /// </para>
- /// </remarks>
- /// <param name="port">
- /// An <see cref="int"/> that specifies the number of the port on which
- /// to listen.
- /// </param>
- /// <exception cref="ArgumentOutOfRangeException">
- /// <paramref name="port"/> is less than 1 or greater than 65535.
- /// </exception>
- public WebSocketServer (int port)
- : this (port, port == 443)
- {
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="WebSocketServer"/> class
- /// with the specified URL.
- /// </summary>
- /// <remarks>
- /// <para>
- /// The new instance listens for incoming handshake requests on
- /// the IP address and port of <paramref name="url"/>.
- /// </para>
- /// <para>
- /// Either port 80 or 443 is used if <paramref name="url"/> includes
- /// no port. Port 443 is used if the scheme of <paramref name="url"/>
- /// is wss; otherwise, port 80 is used.
- /// </para>
- /// <para>
- /// The new instance provides secure connections if the scheme of
- /// <paramref name="url"/> is wss.
- /// </para>
- /// </remarks>
- /// <param name="url">
- /// A <see cref="string"/> that specifies the WebSocket URL of the server.
- /// </param>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="url"/> is <see langword="null"/>.
- /// </exception>
- /// <exception cref="ArgumentException">
- /// <para>
- /// <paramref name="url"/> is an empty string.
- /// </para>
- /// <para>
- /// -or-
- /// </para>
- /// <para>
- /// <paramref name="url"/> is invalid.
- /// </para>
- /// </exception>
- public WebSocketServer (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.DnsSafeHost;
- 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 == "wss");
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="WebSocketServer"/> class
- /// with the specified port and boolean if secure or not.
- /// </summary>
- /// <remarks>
- /// The new instance listens for incoming handshake requests on
- /// <see cref="System.Net.IPAddress.Any"/> and <paramref name="port"/>.
- /// </remarks>
- /// <param name="port">
- /// An <see cref="int"/> that specifies the number of the port on which
- /// to listen.
- /// </param>
- /// <param name="secure">
- /// A <see cref="bool"/>: <c>true</c> if the new instance provides
- /// secure connections; otherwise, <c>false</c>.
- /// </param>
- /// <exception cref="ArgumentOutOfRangeException">
- /// <paramref name="port"/> is less than 1 or greater than 65535.
- /// </exception>
- public WebSocketServer (int port, bool secure)
- {
- if (!port.IsPortNumber ()) {
- var msg = "Less than 1 or greater than 65535.";
- throw new ArgumentOutOfRangeException ("port", msg);
- }
- var addr = System.Net.IPAddress.Any;
- init (addr.ToString (), addr, port, secure);
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="WebSocketServer"/> class
- /// with the specified IP address and port.
- /// </summary>
- /// <remarks>
- /// <para>
- /// The new instance listens for incoming handshake requests on
- /// <paramref name="address"/> and <paramref name="port"/>.
- /// </para>
- /// <para>
- /// It provides secure connections if <paramref name="port"/> is 443.
- /// </para>
- /// </remarks>
- /// <param name="address">
- /// A <see cref="System.Net.IPAddress"/> that specifies the local IP
- /// address on which to listen.
- /// </param>
- /// <param name="port">
- /// An <see cref="int"/> that specifies the number of the port on which
- /// to listen.
- /// </param>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="address"/> is <see langword="null"/>.
- /// </exception>
- /// <exception cref="ArgumentException">
- /// <paramref name="address"/> is not a local IP address.
- /// </exception>
- /// <exception cref="ArgumentOutOfRangeException">
- /// <paramref name="port"/> is less than 1 or greater than 65535.
- /// </exception>
- public WebSocketServer (System.Net.IPAddress address, int port)
- : this (address, port, port == 443)
- {
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="WebSocketServer"/> class
- /// with the specified IP address, port, and boolean if secure or not.
- /// </summary>
- /// <remarks>
- /// The new instance listens for incoming handshake requests on
- /// <paramref name="address"/> and <paramref name="port"/>.
- /// </remarks>
- /// <param name="address">
- /// A <see cref="System.Net.IPAddress"/> that specifies the local IP
- /// address on which to listen.
- /// </param>
- /// <param name="port">
- /// An <see cref="int"/> that specifies the number of the port on which
- /// to listen.
- /// </param>
- /// <param name="secure">
- /// A <see cref="bool"/>: <c>true</c> if the new instance provides
- /// secure connections; otherwise, <c>false</c>.
- /// </param>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="address"/> is <see langword="null"/>.
- /// </exception>
- /// <exception cref="ArgumentException">
- /// <paramref name="address"/> is not a local IP address.
- /// </exception>
- /// <exception cref="ArgumentOutOfRangeException">
- /// <paramref name="port"/> is less than 1 or greater than 65535.
- /// </exception>
- public WebSocketServer (System.Net.IPAddress address, int port, bool secure)
- {
- if (address == null)
- throw new ArgumentNullException ("address");
- if (!address.IsLocal ()) {
- var msg = "Not a local IP address.";
- throw new ArgumentException (msg, "address");
- }
- if (!port.IsPortNumber ()) {
- var msg = "Less than 1 or greater than 65535.";
- throw new ArgumentOutOfRangeException ("port", msg);
- }
- init (address.ToString (), address, port, secure);
- }
- #endregion
- #region Public Properties
- /// <summary>
- /// Gets the IP address of the server.
- /// </summary>
- /// <value>
- /// A <see cref="System.Net.IPAddress"/> that represents the local IP
- /// address on which to listen for incoming handshake requests.
- /// </value>
- public System.Net.IPAddress Address {
- get {
- return _address;
- }
- }
- /// <summary>
- /// Gets or sets the scheme used to authenticate the clients.
- /// </summary>
- /// <remarks>
- /// The set operation works if the current state of the server is
- /// Ready or Stop.
- /// </remarks>
- /// <value>
- /// <para>
- /// One of the <see cref="WebSocketSharp.Net.AuthenticationSchemes"/>
- /// enum values.
- /// </para>
- /// <para>
- /// It represents the scheme used to authenticate the clients.
- /// </para>
- /// <para>
- /// The default value is
- /// <see cref="WebSocketSharp.Net.AuthenticationSchemes.Anonymous"/>.
- /// </para>
- /// </value>
- public AuthenticationSchemes AuthenticationSchemes {
- get {
- return _authSchemes;
- }
- set {
- lock (_sync) {
- if (!canSet ())
- return;
- _authSchemes = value;
- }
- }
- }
- /// <summary>
- /// Gets a value indicating whether the server has started.
- /// </summary>
- /// <value>
- /// <c>true</c> if the server has started; otherwise, <c>false</c>.
- /// </value>
- public bool IsListening {
- get {
- return _state == ServerState.Start;
- }
- }
- /// <summary>
- /// Gets a value indicating whether the server provides secure connections.
- /// </summary>
- /// <value>
- /// <c>true</c> if the server provides secure connections; otherwise,
- /// <c>false</c>.
- /// </value>
- public bool IsSecure {
- get {
- return _secure;
- }
- }
- /// <summary>
- /// Gets or sets a value indicating whether the server cleans up
- /// the inactive sessions periodically.
- /// </summary>
- /// <remarks>
- /// The set operation works if the current state of the server is
- /// Ready or Stop.
- /// </remarks>
- /// <value>
- /// <para>
- /// <c>true</c> if the server cleans up the inactive sessions
- /// every 60 seconds; otherwise, <c>false</c>.
- /// </para>
- /// <para>
- /// The default value is <c>true</c>.
- /// </para>
- /// </value>
- public bool KeepClean {
- get {
- return _services.KeepClean;
- }
- set {
- _services.KeepClean = value;
- }
- }
- /// <summary>
- /// Gets the logging function for the server.
- /// </summary>
- /// <remarks>
- /// The default logging level is <see cref="LogLevel.Error"/>.
- /// </remarks>
- /// <value>
- /// A <see cref="Logger"/> that provides the logging function.
- /// </value>
- public Logger Log {
- get {
- return _log;
- }
- }
- /// <summary>
- /// Gets the port of the server.
- /// </summary>
- /// <value>
- /// An <see cref="int"/> that represents the number of the port on which
- /// to listen for incoming handshake requests.
- /// </value>
- public int Port {
- get {
- return _port;
- }
- }
- /// <summary>
- /// Gets or sets the name of the realm associated with the server.
- /// </summary>
- /// <remarks>
- /// The set operation works if the current state of the server is
- /// Ready or Stop.
- /// </remarks>
- /// <value>
- /// <para>
- /// A <see cref="string"/> that represents the name of the realm.
- /// </para>
- /// <para>
- /// "SECRET AREA" is used as the name of the realm if the value is
- /// <see langword="null"/> or an empty string.
- /// </para>
- /// <para>
- /// The default value is <see langword="null"/>.
- /// </para>
- /// </value>
- public string Realm {
- get {
- return _realm;
- }
- set {
- lock (_sync) {
- if (!canSet ())
- return;
- _realm = value;
- }
- }
- }
- /// <summary>
- /// Gets or sets a value indicating whether the server is allowed to
- /// be bound to an address that is already in use.
- /// </summary>
- /// <remarks>
- /// <para>
- /// You should set this property to <c>true</c> if you would like to
- /// resolve to wait for socket in TIME_WAIT state.
- /// </para>
- /// <para>
- /// The set operation works if the current state of the server is
- /// Ready or Stop.
- /// </para>
- /// </remarks>
- /// <value>
- /// <para>
- /// <c>true</c> if the server is allowed to be bound to an address
- /// that is already in use; otherwise, <c>false</c>.
- /// </para>
- /// <para>
- /// The default value is <c>false</c>.
- /// </para>
- /// </value>
- public bool ReuseAddress {
- get {
- return _reuseAddress;
- }
- set {
- lock (_sync) {
- if (!canSet ())
- return;
- _reuseAddress = value;
- }
- }
- }
- /// <summary>
- /// Gets the configuration for secure connection.
- /// </summary>
- /// <remarks>
- /// The configuration is used when the server attempts to start,
- /// so it must be configured before the start method is called.
- /// </remarks>
- /// <value>
- /// A <see cref="ServerSslConfiguration"/> that represents the
- /// configuration used to provide secure connections.
- /// </value>
- /// <exception cref="InvalidOperationException">
- /// The server does not provide secure connections.
- /// </exception>
- public ServerSslConfiguration SslConfiguration {
- get {
- if (!_secure) {
- var msg = "The server does not provide secure connections.";
- throw new InvalidOperationException (msg);
- }
- return getSslConfiguration ();
- }
- }
- /// <summary>
- /// Gets or sets the delegate used to find the credentials for an identity.
- /// </summary>
- /// <remarks>
- /// The set operation works if the current state of the server is
- /// Ready or Stop.
- /// </remarks>
- /// <value>
- /// <para>
- /// A <see cref="T:System.Func{IIdentity, NetworkCredential}"/>
- /// delegate.
- /// </para>
- /// <para>
- /// The delegate invokes the method called when the server finds
- /// the credentials used to authenticate a client.
- /// </para>
- /// <para>
- /// The method must return <see langword="null"/> if the credentials
- /// are not found.
- /// </para>
- /// <para>
- /// <see langword="null"/> if not necessary.
- /// </para>
- /// <para>
- /// The default value is <see langword="null"/>.
- /// </para>
- /// </value>
- public Func<IIdentity, NetworkCredential> UserCredentialsFinder {
- get {
- return _userCredFinder;
- }
- set {
- lock (_sync) {
- if (!canSet ())
- return;
- _userCredFinder = value;
- }
- }
- }
- /// <summary>
- /// Gets or sets the time to wait for the response to the WebSocket
- /// Ping or Close.
- /// </summary>
- /// <remarks>
- /// The set operation works if the current state of the server is
- /// Ready or Stop.
- /// </remarks>
- /// <value>
- /// <para>
- /// A <see cref="TimeSpan"/> that represents the time to wait for
- /// the response.
- /// </para>
- /// <para>
- /// The default value is the same as 1 second.
- /// </para>
- /// </value>
- /// <exception cref="ArgumentOutOfRangeException">
- /// The value specified for a set operation is zero or less.
- /// </exception>
- public TimeSpan WaitTime {
- get {
- return _services.WaitTime;
- }
- set {
- _services.WaitTime = value;
- }
- }
- /// <summary>
- /// Gets the management function for the WebSocket services provided by
- /// the server.
- /// </summary>
- /// <value>
- /// A <see cref="WebSocketServiceManager"/> that manages the WebSocket
- /// services provided by the server.
- /// </value>
- public WebSocketServiceManager WebSocketServices {
- get {
- return _services;
- }
- }
- #endregion
- #region Private Methods
- private void abort ()
- {
- lock (_sync) {
- if (_state != ServerState.Start)
- return;
- _state = ServerState.ShuttingDown;
- }
- try {
- _listener.Stop ();
- }
- catch (Exception ex) {
- _log.Fatal (ex.Message);
- _log.Debug (ex.ToString ());
- }
- try {
- _services.Stop (1006, String.Empty);
- }
- catch (Exception ex) {
- _log.Fatal (ex.Message);
- _log.Debug (ex.ToString ());
- }
- _state = ServerState.Stop;
- }
- private bool authenticateClient (TcpListenerWebSocketContext context)
- {
- if (_authSchemes == AuthenticationSchemes.Anonymous)
- return true;
- if (_authSchemes == AuthenticationSchemes.None)
- return false;
- var chal = new AuthenticationChallenge (_authSchemes, _realmInUse)
- .ToString ();
- var retry = -1;
- Func<bool> auth = null;
- auth =
- () => {
- retry++;
- if (retry > 99)
- return false;
- if (context.SetUser (_authSchemes, _realmInUse, _userCredFinder))
- return true;
- context.SendAuthenticationChallenge (chal);
- return auth ();
- };
- return auth ();
- }
- private bool canSet ()
- {
- return _state == ServerState.Ready || _state == ServerState.Stop;
- }
- private bool checkHostNameForRequest (string name)
- {
- return !_dnsStyle
- || Uri.CheckHostName (name) != UriHostNameType.Dns
- || name == _hostname;
- }
- private string getRealm ()
- {
- var realm = _realm;
- return realm != null && realm.Length > 0 ? realm : _defaultRealm;
- }
- private ServerSslConfiguration getSslConfiguration ()
- {
- if (_sslConfig == null)
- _sslConfig = new ServerSslConfiguration ();
- return _sslConfig;
- }
- private void init (
- string hostname, System.Net.IPAddress address, int port, bool secure
- )
- {
- _hostname = hostname;
- _address = address;
- _port = port;
- _secure = secure;
- _authSchemes = AuthenticationSchemes.Anonymous;
- _dnsStyle = Uri.CheckHostName (hostname) == UriHostNameType.Dns;
- _listener = new TcpListener (address, port);
- _log = new Logger ();
- _services = new WebSocketServiceManager (_log);
- _sync = new object ();
- }
- private void processRequest (TcpListenerWebSocketContext context)
- {
- if (!authenticateClient (context)) {
- context.Close (HttpStatusCode.Forbidden);
- return;
- }
- var uri = context.RequestUri;
- if (uri == null) {
- context.Close (HttpStatusCode.BadRequest);
- return;
- }
- var name = uri.DnsSafeHost;
- if (!checkHostNameForRequest (name)) {
- context.Close (HttpStatusCode.NotFound);
- 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) {
- TcpClient cl = null;
- try {
- cl = _listener.AcceptTcpClient ();
- ThreadPool.QueueUserWorkItem (
- state => {
- try {
- var ctx = new TcpListenerWebSocketContext (
- cl, null, _secure, _sslConfigInUse, _log
- );
- processRequest (ctx);
- }
- catch (Exception ex) {
- _log.Error (ex.Message);
- _log.Debug (ex.ToString ());
- cl.Close ();
- }
- }
- );
- }
- catch (SocketException ex) {
- if (_state == ServerState.ShuttingDown)
- return;
- _log.Fatal (ex.Message);
- _log.Debug (ex.ToString ());
- break;
- }
- catch (InvalidOperationException ex) {
- if (_state == ServerState.ShuttingDown)
- return;
- _log.Fatal (ex.Message);
- _log.Debug (ex.ToString ());
- break;
- }
- catch (Exception ex) {
- _log.Fatal (ex.Message);
- _log.Debug (ex.ToString ());
- if (cl != null)
- cl.Close ();
- if (_state == ServerState.ShuttingDown)
- return;
- break;
- }
- }
- abort ();
- }
- private void start ()
- {
- lock (_sync) {
- if (_state == ServerState.Start || _state == ServerState.ShuttingDown)
- return;
- if (_secure) {
- var src = getSslConfiguration ();
- var conf = new ServerSslConfiguration (src);
- if (conf.ServerCertificate == null) {
- var msg = "There is no server certificate for secure connection.";
- throw new InvalidOperationException (msg);
- }
- _sslConfigInUse = conf;
- }
- _realmInUse = getRealm ();
- _services.Start ();
- try {
- startReceiving ();
- }
- catch {
- _services.Stop (1011, String.Empty);
- throw;
- }
- _state = ServerState.Start;
- }
- }
- private void startReceiving ()
- {
- if (_reuseAddress) {
- _listener.Server.SetSocketOption (
- SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true
- );
- }
- try {
- _listener.Start ();
- }
- catch (Exception ex) {
- var msg = "The underlying listener has failed to start.";
- throw new InvalidOperationException (msg, ex);
- }
- var receiver = new ThreadStart (receiveRequest);
- _receiveThread = new Thread (receiver);
- _receiveThread.IsBackground = true;
- _receiveThread.Start ();
- }
- private void stop (ushort code, string reason)
- {
- lock (_sync) {
- if (_state != ServerState.Start)
- return;
- _state = ServerState.ShuttingDown;
- }
- try {
- var timeout = 5000;
- stopReceiving (timeout);
- }
- catch (Exception ex) {
- _log.Fatal (ex.Message);
- _log.Debug (ex.ToString ());
- }
- try {
- _services.Stop (code, reason);
- }
- catch (Exception ex) {
- _log.Fatal (ex.Message);
- _log.Debug (ex.ToString ());
- }
- _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
- )
- {
- if (!uriString.TryCreateWebSocketUri (out result, out message))
- return false;
- if (result.PathAndQuery != "/") {
- result = null;
- message = "It includes either or both path and query components.";
- return false;
- }
- return true;
- }
- #endregion
- #region Public Methods
- /// <summary>
- /// Adds a WebSocket service with the specified behavior and path.
- /// </summary>
- /// <param name="path">
- /// <para>
- /// A <see cref="string"/> that specifies an absolute path to
- /// the service to add.
- /// </para>
- /// <para>
- /// / is trimmed from the end of the string if present.
- /// </para>
- /// </param>
- /// <typeparam name="TBehavior">
- /// <para>
- /// The type of the behavior for the service.
- /// </para>
- /// <para>
- /// It must inherit the <see cref="WebSocketBehavior"/> class.
- /// </para>
- /// <para>
- /// Also it must have a public parameterless constructor.
- /// </para>
- /// </typeparam>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="path"/> is <see langword="null"/>.
- /// </exception>
- /// <exception cref="ArgumentException">
- /// <para>
- /// <paramref name="path"/> is an empty string.
- /// </para>
- /// <para>
- /// -or-
- /// </para>
- /// <para>
- /// <paramref name="path"/> is not an absolute path.
- /// </para>
- /// <para>
- /// -or-
- /// </para>
- /// <para>
- /// <paramref name="path"/> includes either or both
- /// query and fragment components.
- /// </para>
- /// <para>
- /// -or-
- /// </para>
- /// <para>
- /// <paramref name="path"/> is already in use.
- /// </para>
- /// </exception>
- public void AddWebSocketService<TBehavior> (string path)
- where TBehavior : WebSocketBehavior, new ()
- {
- _services.AddService<TBehavior> (path, null);
- }
- /// <summary>
- /// Adds a WebSocket service with the specified behavior, path,
- /// and initializer.
- /// </summary>
- /// <param name="path">
- /// <para>
- /// A <see cref="string"/> that specifies an absolute path to
- /// the service to add.
- /// </para>
- /// <para>
- /// / is trimmed from the end of the string if present.
- /// </para>
- /// </param>
- /// <param name="initializer">
- /// <para>
- /// An <see cref="T:System.Action{TBehavior}"/> delegate.
- /// </para>
- /// <para>
- /// The delegate invokes the method called when the service
- /// initializes a new session instance.
- /// </para>
- /// <para>
- /// <see langword="null"/> if not necessary.
- /// </para>
- /// </param>
- /// <typeparam name="TBehavior">
- /// <para>
- /// The type of the behavior for the service.
- /// </para>
- /// <para>
- /// It must inherit the <see cref="WebSocketBehavior"/> class.
- /// </para>
- /// <para>
- /// Also it must have a public parameterless constructor.
- /// </para>
- /// </typeparam>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="path"/> is <see langword="null"/>.
- /// </exception>
- /// <exception cref="ArgumentException">
- /// <para>
- /// <paramref name="path"/> is an empty string.
- /// </para>
- /// <para>
- /// -or-
- /// </para>
- /// <para>
- /// <paramref name="path"/> is not an absolute path.
- /// </para>
- /// <para>
- /// -or-
- /// </para>
- /// <para>
- /// <paramref name="path"/> includes either or both
- /// query and fragment components.
- /// </para>
- /// <para>
- /// -or-
- /// </para>
- /// <para>
- /// <paramref name="path"/> is already in use.
- /// </para>
- /// </exception>
- public void AddWebSocketService<TBehavior> (
- string path, Action<TBehavior> initializer
- )
- where TBehavior : WebSocketBehavior, new ()
- {
- _services.AddService<TBehavior> (path, initializer);
- }
- /// <summary>
- /// Removes a WebSocket service with the specified path.
- /// </summary>
- /// <remarks>
- /// The service is stopped with close status 1001 (going away)
- /// if the current state of the service is Start.
- /// </remarks>
- /// <returns>
- /// <c>true</c> if the service is successfully found and removed;
- /// otherwise, <c>false</c>.
- /// </returns>
- /// <param name="path">
- /// <para>
- /// A <see cref="string"/> that specifies an absolute path to
- /// the service to remove.
- /// </para>
- /// <para>
- /// / is trimmed from the end of the string if present.
- /// </para>
- /// </param>
- /// <exception cref="ArgumentNullException">
- /// <paramref name="path"/> is <see langword="null"/>.
- /// </exception>
- /// <exception cref="ArgumentException">
- /// <para>
- /// <paramref name="path"/> is an empty string.
- /// </para>
- /// <para>
- /// -or-
- /// </para>
- /// <para>
- /// <paramref name="path"/> is not an absolute path.
- /// </para>
- /// <para>
- /// -or-
- /// </para>
- /// <para>
- /// <paramref name="path"/> includes either or both
- /// query and fragment components.
- /// </para>
- /// </exception>
- public bool RemoveWebSocketService (string path)
- {
- return _services.RemoveService (path);
- }
- /// <summary>
- /// Starts receiving incoming handshake requests.
- /// </summary>
- /// <remarks>
- /// This method works if the current state of the server is Ready or Stop.
- /// </remarks>
- /// <exception cref="InvalidOperationException">
- /// <para>
- /// There is no server certificate for secure connection.
- /// </para>
- /// <para>
- /// -or-
- /// </para>
- /// <para>
- /// The underlying <see cref="TcpListener"/> has failed to start.
- /// </para>
- /// </exception>
- public void Start ()
- {
- if (_state == ServerState.Start || _state == ServerState.ShuttingDown)
- return;
- start ();
- }
- /// <summary>
- /// Stops receiving incoming handshake requests.
- /// </summary>
- /// <remarks>
- /// This method works if the current state of the server is Start.
- /// </remarks>
- public void Stop ()
- {
- if (_state != ServerState.Start)
- return;
- stop (1001, String.Empty);
- }
- #endregion
- }
- }
|