#region MIT License /** * HttpServer.cs * * The MIT License * * Copyright (c) 2012 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 { public class HttpServer { #region Fields private Thread _acceptRequestThread; private bool _isWindows; private HttpListener _listener; private int _port; private string _rootPath; private ServiceManager _services; #endregion #region Constructors public HttpServer() : this(80) { } public HttpServer(int port) { _port = port; init(); } #endregion #region Properties public int Port { get { return _port; } } public IEnumerable ServicePath { get { return _services.Path; } } public bool Sweeped { get { return _services.Sweeped; } set { _services.Sweeped = value; } } #endregion #region Events public event EventHandler OnConnect; public event EventHandler OnDelete; public event EventHandler OnError; public event EventHandler OnGet; public event EventHandler OnHead; public event EventHandler OnOptions; public event EventHandler OnPatch; public event EventHandler OnPost; public event EventHandler OnPut; public event EventHandler OnTrace; #endregion #region Private Methods private void acceptRequest() { while (true) { try { var context = _listener.GetContext(); respondAsync(context); } catch (HttpListenerException) { // HttpListener has been closed. break; } catch (Exception ex) { onError(ex.Message); break; } } } private void configureFromConfigFile() { _rootPath = ConfigurationManager.AppSettings["RootPath"]; } private void init() { _isWindows = false; _listener = new HttpListener(); _services = new ServiceManager(); 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 bool isUpgrade(HttpListenerRequest request, string value) { if (!Ext.Exists(request.Headers, "Upgrade", value)) return false; if (!Ext.Exists(request.Headers, "Connection", "Upgrade")) return false; return true; } 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 Ext.Emit(OnError, this, new ErrorEventArgs(message)); } private void respond(HttpListenerContext context) { var req = context.Request; var res = context.Response; var eventArgs = new ResponseEventArgs(context); if (req.HttpMethod == "GET" && OnGet != null) { OnGet(this, eventArgs); return; } if (req.HttpMethod == "HEAD" && OnHead != null) { OnHead(this, eventArgs); return; } if (req.HttpMethod == "POST" && OnPost != null) { OnPost(this, eventArgs); return; } if (req.HttpMethod == "PUT" && OnPut != null) { OnPut(this, eventArgs); return; } if (req.HttpMethod == "DELETE" && OnDelete != null) { OnDelete(this, eventArgs); return; } if (req.HttpMethod == "OPTIONS" && OnOptions != null) { OnOptions(this, eventArgs); return; } if (req.HttpMethod == "TRACE" && OnTrace != null) { OnTrace(this, eventArgs); return; } if (req.HttpMethod == "CONNECT" && OnConnect != null) { OnConnect(this, eventArgs); return; } if (req.HttpMethod == "PATCH" && OnPatch != null) { OnPatch(this, eventArgs); return; } res.StatusCode = (int)HttpStatusCode.NotImplemented; } private void respondAsync(HttpListenerContext context) { WaitCallback respondCb = (state) => { var req = context.Request; var res = context.Response; try { if (isUpgrade(req, "websocket")) { if (upgradeToWebSocket(context)) return; } else { respond(context); } res.Close(); } catch (Exception ex) { onError(ex.Message); } }; ThreadPool.QueueUserWorkItem(respondCb); } private void startAcceptRequestThread() { _acceptRequestThread = new Thread(new ThreadStart(acceptRequest)); _acceptRequestThread.IsBackground = true; _acceptRequestThread.Start(); } private bool upgradeToWebSocket(HttpListenerContext context) { var res = context.Response; var wsContext = context.AcceptWebSocket(); var socket = wsContext.WebSocket; var path = Ext.UrlDecode(wsContext.Path); IServiceHost svcHost; if (!_services.TryGetServiceHost(path, out svcHost)) { res.StatusCode = (int)HttpStatusCode.NotImplemented; return false; } svcHost.BindWebSocket(socket); return true; } #endregion #region Public Methods public void AddService(string absPath) where T : WebSocketService, new() { string msg; if (!Ext.IsValidAbsolutePath(absPath, out msg)) { onError(msg); return; } var svcHost = new WebSocketServiceHost(); svcHost.Uri = Ext.ToUri(absPath); if (!Sweeped) svcHost.Sweeped = Sweeped; _services.Add(absPath, svcHost); } public byte[] GetFile(string path) { var filePath = _rootPath + path; if (_isWindows) filePath = filePath.Replace("/", "\\"); if (File.Exists(filePath)) return File.ReadAllBytes(filePath); return null; } public void Start() { _listener.Start(); startAcceptRequestThread(); } public void Stop() { _listener.Close(); _acceptRequestThread.Join(5 * 1000); _services.Stop(); } #endregion } }