/////////////////////////////////////////////////////////////////////////// // Copyright (C) Wizardry and Steamworks 2016 - License: GNU GPLv3 // // Please see: http://www.gnu.org/licenses/gpl.html for legal details, // // rights of fair usage, the disclaimer and warranty conditions. // /////////////////////////////////////////////////////////////////////////// using System; using System.Collections.Generic; using System.Net; using System.Threading; namespace wasSharpNET.Network.HTTP { public abstract class HTTPServer : IDisposable { private readonly ManualResetEventSlim listenStop = new ManualResetEventSlim(false); private readonly ManualResetEventSlim stopListen = new ManualResetEventSlim(false); private int activeRequests; private HttpListener HTTPListener; private int processedRequests; public AuthenticationSchemes AuthenticationSchemes { get; set; } = AuthenticationSchemes.None; public bool IsRunning => HTTPListener != null && HTTPListener.IsListening; public void Dispose() { stopListen.Set(); } public bool Start(List prefixes) { HTTPListener = new HttpListener { AuthenticationSchemes = AuthenticationSchemes }; // Add all prefixes. HTTPListener.Prefixes.Clear(); foreach (var prefix in prefixes) HTTPListener.Prefixes.Add(prefix); // Do not bomb if the client disconnects. HTTPListener.IgnoreWriteExceptions = true; HTTPListener.Start(); return ThreadPool.QueueUserWorkItem(Listen, new HTTPServerCallbackState(HTTPListener)); } public bool Stop(int timeout) { stopListen.Set(); return listenStop.Wait(timeout); } private void Listen(object state) { var callbackState = (HTTPServerCallbackState) state; while (callbackState.Listener.IsListening) { callbackState.Listener?.BeginGetContext(ContextCallback, callbackState); if (WaitHandle.WaitAny(new[] {callbackState.ContextRetrieved, stopListen.WaitHandle}) != 1) continue; try { callbackState.Listener.Stop(); callbackState.Listener.Close(); } finally { listenStop.Set(); } break; } } public abstract void ProcessHTTPContext(HttpListenerContext context); private void ContextCallback(IAsyncResult ar) { var callbackState = (HTTPServerCallbackState) ar.AsyncState; HttpListenerContext httpContext; Interlocked.Increment(ref processedRequests); Interlocked.Increment(ref activeRequests); try { httpContext = callbackState.Listener.EndGetContext(ar); } catch (Exception) { Interlocked.Decrement(ref activeRequests); return; } finally { callbackState.ContextRetrieved.Set(); } try { ProcessHTTPContext(httpContext); } finally { Interlocked.Decrement(ref activeRequests); } } private class HTTPServerCallbackState { public HTTPServerCallbackState(HttpListener listener) { if (listener == null) throw new ArgumentNullException(nameof(listener)); Listener = listener; ContextRetrieved = new AutoResetEvent(false); } public HttpListener Listener { get; } public AutoResetEvent ContextRetrieved { get; } } } }