HttpRequestSocketHandler.cs

/*
 * All intellectual rights of this framework, including this source file belong to Appicacy, René Vaessen.
 * Customers of Appicacy, may copy and change it, as long as this header remains.
 *
 */
using System.Text;
using Ninject;
using GenXdev.MemoryManagement;
using GenXdev.AsyncSockets.Containers;
using GenXdev.AsyncSockets.Configuration;
using GenXdev.Configuration;
using GenXdev.AsyncSockets.Arguments;
using System.Security.Cryptography.X509Certificates;
using System.Net;
using System.Net.Security;
using Org.BouncyCastle.Asn1.X509;
 
namespace GenXdev.AsyncSockets.Handlers
{
    public enum HttpRequestState { DeterminingIfClientRequestsTLS, NewRequest, ReadingHeaders, Sending100Continue, GotRequest, ReadingBody, SendingResponseStatusHeader, SendingResponseHeaders, SendingResponseBody, Websocket, ServerSendEventDispatcher }
 
    public enum SuitableContentEncoding { none, gzip, deflate };
 
    public class HttpRequestSocketHandler : SocketHandlerBase, IHttpRequestSocketHandler
    {
        #region Initialization
 
        [Inject]
        public HttpRequestSocketHandler(
            IKernel Kernel,
            IMemoryManagerConfiguration MemoryConfiguration,
            IServiceMemoryManager MemoryManager,
            String ServiceName
 
#if (Logging)
, IServiceLogger Logger
#endif
, object Pool = null
, ISocketHandlerTLSConfiguration TLSConfiguration = null
)
            : base(Kernel, MemoryConfiguration, MemoryManager, ServiceName
#if (Logging)
, Logger
#endif
 
, Pool
            )
        {
            this.TLSConfiguration = TLSConfiguration != null ? TLSConfiguration
                : Kernel.Get<ISocketHandlerTLSConfiguration>();
 
            if (String.IsNullOrWhiteSpace(this.TLSConfiguration.UniqueConfigurationName))
            {
 
                this.TLSConfiguration.UniqueConfigurationName = ServiceName;
            }
 
 
            this.HttpRequestHeaders = new HttpRequestHeaders(Kernel);
            IsFirstRequest = true;
        }
 
        protected override void SetupHandlers()
        {
            this.OnHandleInitialize += HttpRequestSocketHandler_OnHandleInitialize;
 
            this.OnHandleReceive += HttpRequestSocketHandler_OnHandleReceive;
 
            this.OnHandleSend += HttpRequestSocketHandler_OnHandleSend;
 
            this.OnHandleReset += HttpRequestSocketHandler_OnHandleReset;
        }
 
        #endregion
 
        #region Fields
 
        // misc
        int lastEOHsearchIndex;
 
        // state of handler
        protected HttpRequestState HttpHandlerState;
 
        // frequently used sequence
        static byte[] EndOfHeadersSequence = new byte[4] { (byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' };
        static byte[] Continue100Header = (new UTF8Encoding(false, true)).GetBytes("HTTP/1.1 100 Continue\r\n\r\n");
 
        // flags
        public bool IsFirstRequest { get; private set; }
        bool TlsHandlerAssigned;
 
        public ISocketHandlerTLSConfiguration TLSConfiguration { get; private set; }
 
        #endregion
 
        #region Properties
 
        // headers
        public IHttpRequestHeaders HttpRequestHeaders { get; private set; }
 
        // encoding
        public UTF8Encoding UTF8Encoding { get; private set; }
 
        // timer
        public double RequestStartTimestamp { get; private set; }
 
        // configuration
        [Inject]
        public IHttpSocketHandlerConfiguration HttpConfiguration { get; set; }
 
        [Inject]
        protected IWebResourceAssignmentHandler WebResourceAssignmentHandler { get; set; }
 
        #endregion
 
        #region Handling
 
        void HttpRequestSocketHandler_OnHandleInitialize(object sender, HandleSocketBEventArgs e)
        {
            if (!TlsHandlerAssigned)
            {
                this.OnHandleAsyncAction += HttpRequestSocketHandler_OnHandleAsyncAction;
                TlsHandlerAssigned = true;
            }
 
            // create new statefull utf8 encoder
            this.UTF8Encoding = new UTF8Encoding(false, true);
 
            if (IsFirstRequest)
            {
                #region ---------------------------------------------------------------------------------------------[LOG]-
#if (Logging)
                Logger.LogHandlerFlowMessage(saeaHandler, () => { return "Http handler's 1st request"; });
#endif
                #endregion ------------------------------------------------------------------------------------------------
 
                switch (TLSConfiguration.TLS_ActivationOptions)
                {
                    case SocketHandlerTLSActivationOptions.TLSAutoDetect:
                    case SocketHandlerTLSActivationOptions.TLSRequired:
                        HttpHandlerState = HttpRequestState.DeterminingIfClientRequestsTLS; break;
 
                    default:
                        HttpHandlerState = HttpRequestState.NewRequest; break;
                }
 
                // Set request start timestamp
                RequestStartTimestamp = Timer.Duration;
 
                // TCP keep-alives enabled?
                if (HttpConfiguration.EnableSocketKeepAlives)
                {
                    // set TCP timeout values
                    SetTcpKeepAlive(HttpConfiguration.SocketKeepAliveTimeoutMiliSeconds, HttpConfiguration.SocketKeepAliveProbeIntervalMiliSeconds);
                }
                else
                {
                    // todo
                    // SetTcpKeepAlive(0, HttpConfiguration.SocketKeepAliveTimeoutMiliSeconds);
                }
 
                // create statefull new encoding
                this.UTF8Encoding = new UTF8Encoding(false, true);
 
                // set socket properties
                SetReceiveBufferSize(MemoryConfiguration.ReceiveBufferSize);
                SetSendBufferSize(MemoryConfiguration.SendBufferSize);
                SetNoDelay(true);
                SetBlocking(false);
 
                // set timeout
                SetCurrentStageTimeoutSeconds(HttpConfiguration.HttpReadingHeadersTimeoutSeconds, false);
            }
            else
            {
                #region ---------------------------------------------------------------------------------------------[LOG]-
#if (Logging)
                Logger.LogHandlerFlowMessage(saeaHandler, () => { return "Http handler continuing listening for next request"; });
#endif
                #endregion ------------------------------------------------------------------------------------------------
 
                // set socket properties
                SetReceiveBufferSize(MemoryConfiguration.ReceiveBufferSize);
                SetSendBufferSize(MemoryConfiguration.SendBufferSize);
 
                // return request headers memory
                ReturnRequestHeadersMemory();
 
                // set state
                HttpHandlerState = HttpRequestState.NewRequest;
 
                // Set keep alife timeout if timeout is now set to readingheaders timeout
                SetCurrentStageTimeoutSeconds(HttpConfiguration.HttpKeepAliveTimeoutSeconds, true);
                SetActivityTimestamp(false);
            }
 
            // Start receiving
            if (e.RxBuffer.Length > 0)
            {
                InitNewRequestState();
 
                HandleReceive_ReadingHeaders(sender, e);
                return;
            }
 
            e.Count = Math.Max(Math.Max(1, NrOfBytesSocketHasAvailable), 5);
            e.NextAction = NextRequestedHandlerAction.Receive;
        }
 
        void HttpRequestSocketHandler_OnHandleReceive(object sender, HandleSocketBEventArgs e)
        {
            switch (HttpHandlerState)
            {
                case HttpRequestState.DeterminingIfClientRequestsTLS:
                    HandleReceive_DeterminingIfClientRequestsTLS(sender, e);
                    return;
 
                case HttpRequestState.NewRequest:
                    HandleReceive_NewRequest(sender, e);
                    return;
 
                case HttpRequestState.ReadingHeaders:
                    HandleReceive_ReadingHeaders(sender, e);
                    return;
            }
        }
 
        void HttpRequestSocketHandler_OnHandleSend(object sender, HandleSocketBEventArgs e)
        {
            if (e.TxBuffer.Count > 0)
            {
                e.NextAction = NextRequestedHandlerAction.Send;
                return;
            }
 
            // update state
            HttpHandlerState = HttpRequestState.GotRequest;
 
            // prevent timeout
            SetActivityTimestamp();
 
            try
            {
                DelegateControl(sender, e);
                return;
            }
            finally
            {
                IsFirstRequest = false;
            }
        }
 
        private void DelegateControl(object sender, HandleSocketBEventArgs e)
        {
            WebResourceAssignmentHandler.AssignWebResourceHandler(this, e, saeaHandler);
        }
 
        void HttpRequestSocketHandler_OnHandleReset(object sender, EventArgs e)
        {
            // return request headers memory
            ReturnRequestHeadersMemory();
 
            // reset flags
            IsFirstRequest = true;
 
            // set state
            HttpHandlerState = HttpRequestState.NewRequest;
        }
 
        async Task HttpRequestSocketHandler_OnHandleAsyncAction(object sender, HandleSocketBEventArgs e)
        {
            await _OnHandleStartTransportSecurity(e);
        }
 
        async Task _OnHandleStartTransportSecurity(HandleSocketBEventArgs e)
        {
            if (!SslStarted)
            {
                await StartSslAsServer(
                     Dns.GetHostName().ToLower(),
                     TLSConfiguration.TLS_EnabledProtocols,
                     System.Net.Security.EncryptionPolicy.RequireEncryption,
                     false,
                     new System.Net.Security.LocalCertificateSelectionCallback(OnLocalCertificateSelection),
                     false,
                     new System.Net.Security.RemoteCertificateValidationCallback(OnRemoteCertificateValidation),
                     GetServerCertificate(),
                     null
               );
            }
 
            e.Count = Math.Max(1, NrOfBytesSocketHasAvailable);
            e.NextAction = NextRequestedHandlerAction.Receive;
        }
 
        public event EventHandler<ProvideCertificateEventArgs> OnProvideCertificate;
        ProvideCertificateEventArgs _OnProvideCertificate_args;
 
        public X509Certificate2 GetServerCertificate()
        {
            switch (TLSConfiguration.TLS_CertificateUsage)
            {
                case SocketHandlerTLSCertificateUsage.AutoGenerate:
                    return LoadOrCreateServerCertificate(
                                 TLSConfiguration.UniqueConfigurationName,
                                 Dns.GetHostName().ToLower(),
                                 TLSConfiguration.TLS_AdditionalHostNames,
                                 new[] { KeyPurposeID.id_kp_serverAuth, KeyPurposeID.id_kp_clientAuth },
                                 TLSConfiguration.TLS_Default_CACertificate_AuthorityName,
                                 TLSConfiguration.TLS_ServerCertificate_PfxFilename,
                                 TLSConfiguration.TLS_CACertificate_PfxFilename,
                                 TLSConfiguration.TLS_ServerCertificate_Password,
                                 TLSConfiguration.TLS_CACertificate_Password,
                                 true
                             );
 
                case SocketHandlerTLSCertificateUsage.FromFileOnly:
                    return LoadOrCreateServerCertificate(
                                 TLSConfiguration.UniqueConfigurationName,
                                 Dns.GetHostName().ToLower(),
                                 TLSConfiguration.TLS_AdditionalHostNames,
                                 new[] { KeyPurposeID.id_kp_serverAuth, KeyPurposeID.id_kp_clientAuth },
                                 TLSConfiguration.TLS_Default_CACertificate_AuthorityName,
                                 TLSConfiguration.TLS_ServerCertificate_PfxFilename,
                                 TLSConfiguration.TLS_CACertificate_PfxFilename,
                                 TLSConfiguration.TLS_ServerCertificate_Password,
                                 TLSConfiguration.TLS_CACertificate_Password,
                                 false
                             );
 
                case SocketHandlerTLSCertificateUsage.ManualAssignmentByEvent:
 
                    var handler = OnProvideCertificate;
 
                    if (handler != null)
                    {
                        if (_OnProvideCertificate_args == null)
                        {
                            _OnProvideCertificate_args = new ProvideCertificateEventArgs();
                        }
                        _OnProvideCertificate_args.Result = null;
 
                        handler(this, _OnProvideCertificate_args);
 
                        return _OnProvideCertificate_args.Result;
                    }
 
                    throw new InvalidOperationException("TLS configuration is set to manual assignment for server certificate, but no OnProvideCertificate handler is assigned");
            }
 
            return null;
        }
 
        X509Certificate OnLocalCertificateSelection(object sender, string targetHost, X509CertificateCollection localCertificates, X509Certificate remoteCertificate, string[] acceptableIssuers)
        {
            return GetServerCertificate();
        }
 
 
        bool OnRemoteCertificateValidation(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            return true;
        }
 
        #endregion
 
        #region Public
 
        public SuitableContentEncoding GetSuitableContentEncoding()
        {
            if (SslStarted)
                return SuitableContentEncoding.none;
 
            // find accepted encodings in http headers send by client
            String acceptEncoding = (String)HttpRequestHeaders["accept-encoding"];
 
            // found it?
            if (acceptEncoding != null && acceptEncoding.Length != 0)
            {
                // make lowercase
                acceptEncoding = acceptEncoding.ToLower();
 
                if (HttpConfiguration.EnableHttpDeflateCompressionSupport && (acceptEncoding.Contains("deflate") || acceptEncoding == "*"))
                {
                    return SuitableContentEncoding.deflate;
                }
                else
                    if (HttpConfiguration.EnableHttpGzipCompressionSupport && acceptEncoding.Contains("gzip"))
                {
                    return SuitableContentEncoding.gzip;
                }
            }
 
            return SuitableContentEncoding.none;
        }
 
        public Stream GetCompressionStream(SuitableContentEncoding encoding, Stream ResponseStream)
        {
            switch (encoding)
            {
                case SuitableContentEncoding.gzip:
                    return new Ionic.Zlib.GZipStream(ResponseStream, Ionic.Zlib.CompressionMode.Compress, HttpConfiguration.GzipCompressionLevel, true);
 
                case SuitableContentEncoding.deflate:
                    return new Ionic.Zlib.DeflateStream(ResponseStream, Ionic.Zlib.CompressionMode.Compress, HttpConfiguration.DeflateCompressionLevel, true);
            }
 
            return null;
        }
 
        #endregion
 
        #region Private
 
        #region Allocations
 
        void ReturnRequestHeadersMemory()
        {
            // de-reference
            ((HttpRequestHeaders)this.HttpRequestHeaders).Reset();
        }
 
        #endregion
 
        #region I/O State Handling
 
        #region Receiving
 
        #region State NewQuest
 
        void HandleReceive_DeterminingIfClientRequestsTLS(object sender, HandleSocketBEventArgs e)
        {
            if (e.RxBuffer.Count == 0)
            {
                e.Count = MemoryConfiguration.DynamicBufferFragmentSize;
                e.NextAction = NextRequestedHandlerAction.Receive;
                return;
            }
 
            if (e.RxBuffer[0] == 22)
            {
                e.NextAction = NextRequestedHandlerAction.AsyncAction;
                return;
            }
 
            HttpHandlerState = HttpRequestState.NewRequest;
 
            HttpRequestSocketHandler_OnHandleReceive(sender, e);
        }
 
        void HandleReceive_NewRequest(object sender, HandleSocketBEventArgs e)
        {
            InitNewRequestState();
 
            HttpRequestSocketHandler_OnHandleReceive(sender, e);
        }
 
        void InitNewRequestState()
        {
            // prevent timeout
            SetActivityTimestamp();
 
            // set Timestamp
            RequestStartTimestamp = Timer.Duration;
 
            // set readingheaders timeout if now set to keep-alife timeout
            Interlocked.CompareExchange(
                ref CurrentStageTimeout,
                BitConverter.DoubleToInt64Bits(HttpConfiguration.HttpReadingHeadersTimeoutSeconds),
                BitConverter.DoubleToInt64Bits(HttpConfiguration.HttpKeepAliveTimeoutSeconds)
            );
 
            // update state and recurse once
            HttpHandlerState = HttpRequestState.ReadingHeaders;
        }
 
        #endregion
 
        #region State ReadingHeaders
 
        void HandleReceive_ReadingHeaders(object sender, HandleSocketBEventArgs e)
        {
            // find end of headers
            int? count = null;
            int HeadersEndOffset = e.RxBuffer.IndexOf(ref lastEOHsearchIndex, ref count, SslStarted, EndOfHeadersSequence);
 
            // got the end of the headers?
            if (HeadersEndOffset >= 0)
            {
                // reset
                lastEOHsearchIndex = 0;
 
                // create headers
                ((HttpRequestHeaders)HttpRequestHeaders).Init(
                    SslStarted,
                    PortNumber, PortNumber,
                    e.RxBuffer,
                    HeadersEndOffset + EndOfHeadersSequence.Length
                 );
 
                // prevent timeout
                SetActivityTimestamp();
 
                // check for 100-continue
                string expect = HttpRequestHeaders["expect"];
 
                // expects 100-continue?
                if ((!String.IsNullOrEmpty(expect)) && (expect.ToLower() == "100-continue"))
                {
                    // write continue
                    Buffer.BlockCopy(Continue100Header, 0, saeaHandler.Buffer, 0, Continue100Header.Length);
 
                    // update state
                    HttpHandlerState = HttpRequestState.Sending100Continue;
 
                    // send
                    e.NextAction = NextRequestedHandlerAction.Send;
                    return;
                }
 
                // update state
                HttpHandlerState = HttpRequestState.GotRequest;
 
                try
                {
                    // assign new handler to handle this request
 
                    DelegateControl(sender, e);
                    return;
                }
                finally
                {
                    IsFirstRequest = false;
                }
            }
            else
            {
                // Too large request header?
                if (e.RxBuffer.Count >= HttpConfiguration.MaxIncomingRequestHeaderSize)
                {
                    SendHttpErrorMessage(e, 413, "Request Entity Too Large", true);
                    return;
                }
            }
 
            e.Count = count.HasValue ? Math.Max(Math.Max(1, NrOfBytesSocketHasAvailable), count.Value) : MemoryConfiguration.ReceiveBufferSize;
            e.NextAction = NextRequestedHandlerAction.Receive;
            return;
        }
 
        #endregion
 
        #endregion
 
        #endregion
 
        #region Misc
 
        void SendHttpErrorMessage(HandleSocketBEventArgs e, int HttpResultCode, string HttpStatusMessage, bool ForceDisconnect = true)
        {
            // create new socket handler
            var newHandler = Kernel.Get<EmptyHttpResponseHandler>();
 
            // set handler properties
            newHandler.HttpRequestSocketHandler = this;
            newHandler.HttpResultCode = HttpResultCode;
            newHandler.HttpStatusMessage = HttpStatusMessage;
            newHandler.ForceDisconnect = ForceDisconnect;
 
            // delegate
            newHandler.TakeOverConrolOfSocket(saeaHandler);
            e.NextAction = NextRequestedHandlerAction.Handled;
        }
 
        #endregion
 
        #endregion
    }
}