wasSharpNET – Rev 11

Subversion Repositories:
Rev:
///////////////////////////////////////////////////////////////////////////
//  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
    {
        private int activeRequests;

        private readonly HttpListener HTTPListener = new HttpListener()
        {
            AuthenticationSchemes = AuthenticationSchemes.None
        };

        private int processedRequests;

        private readonly AutoResetEvent StopServerEvent = new AutoResetEvent(false);
        private readonly AutoResetEvent ServerStoppedEvent = new AutoResetEvent(false);

        public AuthenticationSchemes AuthenticationSchemes
        {
            get
            {
                return HTTPListener.AuthenticationSchemes;
            }
            set
            {
                HTTPListener.AuthenticationSchemes = value;
            }
        }

        public bool IsRunning => HTTPListener.IsListening;

        public bool Start(IEnumerable<string> prefixes)
        {
            // Do not start the HTTP server if it is already running.
            if (HTTPListener.IsListening)
                return false;

            // Add all prefixes.
            foreach (var prefix in prefixes)
            {
                HTTPListener.Prefixes.Add(prefix);
            }

            // Do not bomb if the client disconnects.
            HTTPListener.IgnoreWriteExceptions = true;
            HTTPListener.Start();
            ThreadPool.QueueUserWorkItem(Listen, new HTTPServerCallbackState(HTTPListener));
            return true;
        }

        public bool Stop()
        {
            StopServerEvent.Set();
            ServerStoppedEvent.WaitOne();
            HTTPListener.Prefixes.Clear();
            return true;
        }

        private void Listen(object state)
        {
            var callbackState = (HTTPServerCallbackState)state;

            while (callbackState.Listener.IsListening)
            {
                callbackState.Listener.BeginGetContext(ContextCallback, callbackState);
                var n = WaitHandle.WaitAny(new WaitHandle[] { callbackState.ContextRetrieved, StopServerEvent });

                if (n.Equals(1))
                {
                    callbackState.Listener.Stop();
                    break;
                }
            }

            ServerStoppedEvent.Set();
        }

        public abstract void ProcessHTTPContext(HttpListenerContext context);

        private void ContextCallback(IAsyncResult ar)
        {
            var callbackState = (HTTPServerCallbackState)ar.AsyncState;
            HttpListenerContext httpContext = null;

            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();
            }

            if (httpContext == null)
                return;

            try
            {
                ProcessHTTPContext(httpContext);
            }
            finally
            {
                Interlocked.Decrement(ref activeRequests);
            }
        }

        private class HTTPServerCallbackState
        {
            public HTTPServerCallbackState(HttpListener listener)
            {
                if (listener == null)
                    throw new ArgumentNullException("listener");
                Listener = listener;
                ContextRetrieved = new AutoResetEvent(false);
            }

            public HttpListener Listener { get; }

            public AutoResetEvent ContextRetrieved { get; }
        }
    }
}