#region License /* * WebSocketServerBase.cs * * The MIT License * * Copyright (c) 2012-2013 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 using System; using System.Diagnostics; using System.Net; using System.Net.Sockets; using System.Security.Cryptography.X509Certificates; using System.Threading; using WebSocketSharp.Net.WebSockets; namespace WebSocketSharp.Server { /// /// Provides the basic functions of the server that receives the WebSocket connection requests. /// /// /// The WebSocketServerBase class is an abstract class. /// public abstract class WebSocketServerBase { #region Private Fields private IPAddress _address; private X509Certificate2 _cert; private bool _listening; private Logger _logger; private int _port; private Thread _receiveRequestThread; private bool _secure; private bool _selfHost; private TcpListener _listener; private Uri _uri; #endregion #region Protected Constructors /// /// Initializes a new instance of the class. /// /// /// This constructor initializes a new instance of this class as non self hosted server. /// protected WebSocketServerBase () : this (new Logger ()) { } /// /// Initializes a new instance of the class /// with the specified . /// /// /// This constructor initializes a new instance of this class as non self hosted server. /// /// /// A that provides the logging functions. /// protected WebSocketServerBase (Logger logger) { _logger = logger; _selfHost = false; } /// /// Initializes a new instance of the class /// that listens for incoming connection attempts on the specified WebSocket URL. /// /// /// A that contains a WebSocket URL. /// /// /// is . /// /// /// is invalid. /// protected WebSocketServerBase (string url) { if (url == null) throw new ArgumentNullException ("url"); Uri uri; string msg; if (!tryCreateUri (url, out uri, out msg)) throw new ArgumentException (msg, "url"); init (uri); } /// /// Initializes a new instance of the class /// that listens for incoming connection attempts on the specified , /// , and . /// /// /// A that contains a local IP address. /// /// /// An that contains a port number. /// /// /// A that contains an absolute path. /// /// /// A that indicates providing a secure connection or not. /// (true indicates providing a secure connection.) /// /// /// Either or is . /// /// /// /// is invalid. /// /// /// -or- /// /// /// Pair of and is invalid. /// /// protected WebSocketServerBase (IPAddress address, int port, string absPath, bool secure) { if (address == null) throw new ArgumentNullException ("address"); if (absPath == null) throw new ArgumentNullException ("absPath"); string msg; if (!absPath.IsValidAbsolutePath (out msg)) throw new ArgumentException (msg, "absPath"); if ((port == 80 && secure) || (port == 443 && !secure)) { msg = String.Format ( "Invalid pair of 'port' and 'secure': {0}, {1}", port, secure); throw new ArgumentException (msg); } _address = address; _port = port > 0 ? port : secure ? 443 : 80; _uri = absPath.ToUri (); _secure = secure; init (); } #endregion #region Protected Properties /// /// Gets or sets the WebSocket URL on which to listen for incoming connection attempts. /// /// /// A that contains a WebSocket URL. /// protected Uri BaseUri { get { return _uri; } set { _uri = value; } } #endregion #region Public Properties /// /// Gets the local IP address on which to listen for incoming connection attempts. /// /// /// A that contains a local IP address. /// public IPAddress Address { get { return _address; } } /// /// Gets or sets the certificate used to authenticate the server on the secure connection. /// /// /// A used to authenticate the server. /// public X509Certificate2 Certificate { get { return _cert; } set { if (_listening) return; _cert = value; } } /// /// Gets a value indicating whether the server has been started. /// /// /// true if the server has been started; otherwise, false. /// public bool IsListening { get { return _listening; } } /// /// Gets a value indicating whether the server provides secure connection. /// /// /// true if the server provides secure connection; otherwise, false. /// public bool IsSecure { get { return _secure; } } /// /// Gets a value indicating whether the server is self host. /// /// /// true if the server is self host; otherwise, false. /// public bool IsSelfHost { get { return _selfHost; } } /// /// Gets the logging functions. /// /// /// The default logging level is the . /// If you want to change the current logging level, you set the Log.Level property /// to one of the values which you want. /// /// /// A that provides the logging functions. /// public Logger Log { get { return _logger; } internal set { if (value == null) return; _logger = value; } } /// /// Gets the port on which to listen for incoming connection attempts. /// /// /// An that contains a port number. /// public int Port { get { return _port; } } #endregion #region Private Methods private void init () { _listening = false; _logger = new Logger (); _selfHost = true; _listener = new TcpListener (_address, _port); } private void init (Uri uri) { var scheme = uri.Scheme; var host = uri.DnsSafeHost; var port = uri.Port; var addrs = Dns.GetHostAddresses (host); _uri = uri; _address = addrs [0]; _secure = scheme == "wss" ? true : false; _port = port > 0 ? port : _secure ? 443 : 80; init (); } private void processRequestAsync (TcpClient client) { WaitCallback callback = state => { try { AcceptWebSocket (client.GetWebSocketContext (_secure, _cert)); } catch (Exception ex) { client.Close (); _logger.Fatal (ex.Message); } }; ThreadPool.QueueUserWorkItem (callback); } private void receiveRequest () { while (true) { try { processRequestAsync (_listener.AcceptTcpClient ()); } catch (SocketException) { _logger.Info ("TcpListener has been stopped."); break; } catch (Exception ex) { _logger.Fatal (ex.Message); break; } } } private void startReceiveRequestThread () { _receiveRequestThread = new Thread (new ThreadStart (receiveRequest)); _receiveRequestThread.IsBackground = true; _receiveRequestThread.Start (); } private static bool tryCreateUri (string uriString, out Uri result, out string message) { if (!uriString.TryCreateWebSocketUri (out result, out message)) return false; if (!result.Query.IsNullOrEmpty ()) { result = null; message = "Must not contain the query component: " + uriString; return false; } return true; } #endregion #region Protected Methods /// /// Accepts a WebSocket connection request. /// /// /// A that contains the WebSocket connection request objects. /// protected abstract void AcceptWebSocket (TcpListenerWebSocketContext context); #endregion #region Public Methods /// /// Starts to receive the WebSocket connection requests. /// public virtual void Start () { if (!_selfHost || _listening) return; if (_secure && _cert == null) { _logger.Error ("Secure connection requires a server certificate."); return; } _listener.Start (); startReceiveRequestThread (); _listening = true; } /// /// Stops receiving the WebSocket connection requests. /// public virtual void Stop () { if (!_selfHost || !_listening) return; _listener.Stop (); _receiveRequestThread.Join (5 * 1000); _listening = false; } #endregion } }