#region License /* * HttpServer.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.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.IO; using System.Threading; using WebSocketSharp.Net; namespace WebSocketSharp.Server { /// /// Provides a simple HTTP server that allows to accept the WebSocket connection requests. /// /// /// /// The HttpServer class provides the multi WebSocket service. /// /// /// /// The HttpServer class needs the application configuration file to configure the server root path. /// /// /// <?xml version="1.0" encoding="utf-8"?> /// <configuration> /// <appSettings> /// <add key="RootPath" value="./Public" /> /// </appSettings> /// </configuration> /// /// /// public class HttpServer { #region Private Fields private bool _isWindows; private HttpListener _listener; private int _port; private Thread _receiveRequestThread; private string _rootPath; private ServiceHostManager _svcHosts; #endregion #region Public Constructors /// /// Initializes a new instance of the class that listens for incoming requests /// on port 80. /// public HttpServer() : this(80) { } /// /// Initializes a new instance of the class that listens for incoming requests /// on the specified . /// /// /// An that contains a port number. /// public HttpServer(int port) { _port = port; init(); } #endregion #region Public Properties /// /// Gets the port on which to listen for incoming requests. /// /// /// An that contains a port number. /// public int Port { get { return _port; } } /// /// Gets the collection of paths associated with the every WebSocket services that the server provides. /// /// /// An IEnumerable<string> that contains the collection of paths. /// public IEnumerable ServicePaths { get { return _svcHosts.Paths; } } /// /// Gets or sets a value indicating whether the server cleans up the inactive WebSocket service /// instances periodically. /// /// /// true if the server cleans up the inactive WebSocket service instances every 60 seconds; /// otherwise, false. The default value is true. /// public bool Sweeping { get { return _svcHosts.Sweeping; } set { _svcHosts.Sweeping = value; } } #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 gets an error. /// public event EventHandler OnError; /// /// 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 PATCH request. /// public event EventHandler OnPatch; /// /// 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 configureFromConfigFile() { _rootPath = ConfigurationManager.AppSettings["RootPath"]; } private void init() { _isWindows = false; _listener = new HttpListener(); _svcHosts = new ServiceHostManager(); var os = Environment.OSVersion; if (os.Platform != PlatformID.Unix && os.Platform != PlatformID.MacOSX) _isWindows = true; var prefix = String.Format( "http{0}://*:{1}/", _port == 443 ? "s" : String.Empty, _port); _listener.Prefixes.Add(prefix); configureFromConfigFile(); } private void onError(string message) { #if DEBUG var callerFrame = new StackFrame(1); var caller = callerFrame.GetMethod(); Console.WriteLine("HTTPSV: Error@{0}: {1}", caller.Name, message); #endif OnError.Emit(this, new ErrorEventArgs(message)); } private void onRequest(HttpListenerContext context) { var req = context.Request; var res = context.Response; var eventArgs = new HttpRequestEventArgs(context); if (req.HttpMethod == "GET" && !OnGet.IsNull()) { OnGet(this, eventArgs); return; } if (req.HttpMethod == "HEAD" && !OnHead.IsNull()) { OnHead(this, eventArgs); return; } if (req.HttpMethod == "POST" && !OnPost.IsNull()) { OnPost(this, eventArgs); return; } if (req.HttpMethod == "PUT" && !OnPut.IsNull()) { OnPut(this, eventArgs); return; } if (req.HttpMethod == "DELETE" && !OnDelete.IsNull()) { OnDelete(this, eventArgs); return; } if (req.HttpMethod == "OPTIONS" && !OnOptions.IsNull()) { OnOptions(this, eventArgs); return; } if (req.HttpMethod == "TRACE" && !OnTrace.IsNull()) { OnTrace(this, eventArgs); return; } if (req.HttpMethod == "CONNECT" && !OnConnect.IsNull()) { OnConnect(this, eventArgs); return; } if (req.HttpMethod == "PATCH" && !OnPatch.IsNull()) { OnPatch(this, eventArgs); return; } res.StatusCode = (int)HttpStatusCode.NotImplemented; } private void processRequestAsync(HttpListenerContext context) { WaitCallback callback = (state) => { var req = context.Request; var res = context.Response; try { if (req.IsUpgradeTo("websocket")) { if (upgradeToWebSocket(context)) return; } else { onRequest(context); } res.Close(); } catch (Exception ex) { onError(ex.Message); } }; ThreadPool.QueueUserWorkItem(callback); } private void receiveRequest() { while (true) { try { var context = _listener.GetContext(); processRequestAsync(context); } catch (HttpListenerException) { // HttpListener has been closed. break; } catch (Exception ex) { onError(ex.Message); break; } } } private void startReceiveRequestThread() { _receiveRequestThread = new Thread(new ThreadStart(receiveRequest)); _receiveRequestThread.IsBackground = true; _receiveRequestThread.Start(); } private bool upgradeToWebSocket(HttpListenerContext context) { var res = context.Response; var wsContext = context.AcceptWebSocket(); var path = wsContext.Path.UrlDecode(); IServiceHost svcHost; if (!_svcHosts.TryGetServiceHost(path, out svcHost)) { res.StatusCode = (int)HttpStatusCode.NotImplemented; return false; } svcHost.BindWebSocket(wsContext); return true; } #endregion #region Public Methods /// /// Adds the specified type WebSocket service. /// /// /// A that contains an absolute path associated with the WebSocket service. /// /// /// The type of the WebSocket service. The T must inherit the class. /// public void AddWebSocketService(string absPath) where T : WebSocketService, new() { string msg; if (!absPath.IsValidAbsolutePath(out msg)) { onError(msg); return; } var svcHost = new WebSocketServiceHost(); svcHost.Uri = absPath.ToUri(); if (!Sweeping) svcHost.Sweeping = false; _svcHosts.Add(absPath, svcHost); } /// /// Gets the contents of the specified file. /// /// /// An array of that contains the contents of the file. /// /// /// A that contains a virtual path to the file to get. /// public byte[] GetFile(string path) { var filePath = _rootPath + path; if (_isWindows) filePath = filePath.Replace("/", "\\"); return File.Exists(filePath) ? File.ReadAllBytes(filePath) : null; } /// /// Starts the . /// public void Start() { _listener.Start(); startReceiveRequestThread(); } /// /// Shuts down the . /// public void Stop() { _listener.Close(); _receiveRequestThread.Join(5 * 1000); _svcHosts.Stop(); } #endregion } }