/// <summary>
        ///
        /// </summary>
        /// <param name="requestObj"></param>
        public void ReceiveClientRequestHeaders(RequestObj requestObj)
        {
            // Read the client request headers
            this.ParseClientRequestHeaders(requestObj);

            // If Host header does not exist throw exception
            if (!requestObj.ClientRequestObj.ClientRequestHeaders.ContainsKey("Host"))
            {
                ClientNotificationException exception = new ClientNotificationException();
                exception.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.NotFound);
                throw exception;
            }

            // if Host header contains illegal characters throw exception
            if (Regex.Match(requestObj.ClientRequestObj.ClientRequestHeaders["Host"][0], @"[^\w\d\-_\.]+").Success == true)
            {
                ClientNotificationException exception = new ClientNotificationException("Invalid characters in host name");
                exception.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.BadRequest);
                throw exception;
            }

            requestObj.ClientRequestObj.Host = requestObj.ClientRequestObj.ClientRequestHeaders["Host"][0];

            // Parse Client request content type
            requestObj.ClientRequestObj.ContentTypeEncoding = this.DetermineClientRequestContentTypeEncoding(requestObj);

            // Parse Client request content length
            this.DetermineClientRequestContentLength(requestObj);

            requestObj.ProxyDataTransmissionModeC2S = this.DetermineDataTransmissionModeC2S(requestObj);
            Logging.Instance.LogMessage(requestObj.Id, requestObj.ProxyProtocol, Loglevel.Debug, "ReceiveClientRequestHeaders(): ProxyDataTransmissionModeC2S:{0}", requestObj.ProxyDataTransmissionModeC2S.ToString());
        }
        /// <summary>
        /// Send custom HttpReverseProxyServer error message to the client system.
        /// </summary>
        /// <param name="requestObj"></param>
        /// <param name="cnex"></param>
        public void SendErrorMessage2Client(RequestObj requestObj, ClientNotificationException cnex)
        {
            var tmpStatusCode            = this.statusDescription[HttpStatusCode.InternalServerError].Code;
            var tmpStatusTitle           = this.statusDescription[HttpStatusCode.InternalServerError].Title;
            var httpServerResponseStatus = $"HTTP/1.1 {tmpStatusCode} {tmpStatusTitle}";
            var message = this.statusDescription[HttpStatusCode.InternalServerError].Description;

            if (cnex.Data.Contains(StatusCodeLabel.StatusCode))
            {
                HttpStatusCode code            = (HttpStatusCode)cnex.Data[StatusCodeLabel.StatusCode];
                var            tmpStatusCode2  = this.statusDescription[code].Code;
                var            tmpStatusTitle2 = this.statusDescription[code].Title;
                httpServerResponseStatus = $"HTTP/1.1 {tmpStatusCode2} {tmpStatusTitle2}";
                message = this.statusDescription[code].Description;
            }

            var serverHeaders = new string[] { httpServerResponseStatus, "Content-Type: text/html", "Connection: close", $"Content-Length: {message.Length}" };

            // Send headers to client
            foreach (var tmpHeader in serverHeaders)
            {
                this.SendStringToClient(requestObj.ClientRequestObj.ClientBinaryWriter, tmpHeader, true);
            }

            // Send ...
            this.SendStringToClient(requestObj.ClientRequestObj.ClientBinaryWriter, "\n", false);

            // Send message to client
            this.SendStringToClient(requestObj.ClientRequestObj.ClientBinaryWriter, message, false);
        }
        /// <summary>
        ///
        /// </summary>
        public void ProcessClientRequest()
        {
            PluginInstruction pluginInstr;

            this.requestObj.ClientRequestObj.ClientWebRequestHandler = new IncomingClientRequest();

            while (true)
            {
                // (Re) Initialize request object values like client request and server response headers
                pluginInstr = null;
                //this.requestObj.InitRequestValues();
                Logging.Instance.LogMessage(this.requestObj.Id, this.requestObj.ProxyProtocol, Loglevel.Debug, "HttpReverseProxy.ProcessClientRequest(): New {0} request to {1}{2}", this.requestObj.ProxyProtocol.ToString(), this.requestObj.ClientRequestObj.Host, this.requestObj.ClientRequestObj.RequestLine.Path);

                try
                {
                    // Receive client data
                    this.ReadClientRequestHeaders();

                    // Call post tcp-client request methodString of each loaded plugin
                    bool mustBreakLoop = this.PostClientHeadersRequest();
                    if (mustBreakLoop == true)
                    {
                        break;
                    }

                    // Re(re)quest server
                    pluginInstr = this.SendClientRequestToServer();

                    // Send server response to client
                    this.SendServerResponseToClient(pluginInstr);
                }
                catch (ClientNotificationException cnex)
                {
                    this.clientErrorHandler.SendErrorMessage2Client(this.requestObj, cnex);

                    var innerException = cnex.InnerException?.Message ?? "No inner exception found";
                    Logging.Instance.LogMessage(this.requestObj.Id, this.requestObj.ProxyProtocol, Loglevel.Warning, "HttpReverseProxy.ProcessClientRequest(ClientNotificationException): Inner exception:{0}\r\nRegular exception: {1}\r\n{2}", innerException, cnex.Message, cnex.StackTrace);
                    break;
                }
                catch (ProxyErrorException peex)
                {
                    ClientNotificationException cnex = new ClientNotificationException();
                    cnex.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.BadRequest);
                    this.clientErrorHandler.SendErrorMessage2Client(this.requestObj, cnex);

                    var innerException = peex.InnerException?.Message ?? "No inner exception found";
                    Logging.Instance.LogMessage(this.requestObj.Id, this.requestObj.ProxyProtocol, Loglevel.Error, "HttpReverseProxy.ProcessClientRequest(ProxyErrorException): Inner exception:{0}\r\nRegular exception: {1}\r\n{2}", innerException, peex.Message, peex.StackTrace);
                    break;
                }
                catch (WebException wex)
                {
                    var innerException = wex.InnerException?.Message ?? "No inner exception found";
                    Logging.Instance.LogMessage(this.requestObj.Id, this.requestObj.ProxyProtocol, Loglevel.Warning, "HttpReverseProxy.ProcessClientRequest(WebException): Inner exception:{0}\r\nRegular exception: {1}\r\n{2}", innerException, wex.Message, wex.StackTrace);
                    this.clientErrorHandler.ProcessWebException(this.requestObj, wex);
                }
                catch (System.IO.IOException ioex)
                {
                    var innerException = ioex.InnerException?.Message ?? "No inner exception found";
                    Logging.Instance.LogMessage(this.requestObj.Id, this.requestObj.ProxyProtocol, Loglevel.Debug, "HttpReverseProxy.ProcessClientRequest(IOException): Client system closed the connection");
                    break;
                }
                catch (ObjectDisposedException odex)
                {
                    var innerException = odex.InnerException?.Message ?? "No inner exception found";
                    Logging.Instance.LogMessage(this.requestObj.Id, this.requestObj.ProxyProtocol, Loglevel.Debug, "HttpReverseProxy.ProcessClientRequest(ObjectDisposedException): Inner exception:{0}\r\nRegular exception: {1}\r\n{2}", innerException, odex.Message, odex.StackTrace);
                    break;
                }
                catch (SocketException sex) when(sex.SocketErrorCode == SocketError.HostNotFound)
                {
                    ;
                    ClientNotificationException cnex = new ClientNotificationException();

                    cnex.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.BadRequest);
                    this.clientErrorHandler.SendErrorMessage2Client(this.requestObj, cnex);

                    var innerException = sex.InnerException?.Message ?? "No inner exception found";

                    Logging.Instance.LogMessage(this.requestObj.Id, this.requestObj.ProxyProtocol, Loglevel.Warning, "HttpReverseProxy.ProcessClientRequest(SocketException): Inner exception:{0}\r\nRegular exception: {1}\r\nHost \"{2}\" not found", innerException, sex.Message, requestObj.ClientRequestObj.Host);
                    break;
                }
                catch (SocketException sex)
                {
                    ClientNotificationException cnex = new ClientNotificationException();
                    cnex.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.BadRequest);
                    this.clientErrorHandler.SendErrorMessage2Client(this.requestObj, cnex);

                    var innerException = sex.InnerException?.Message ?? "No inner exception found";
                    Logging.Instance.LogMessage(this.requestObj.Id, this.requestObj.ProxyProtocol, Loglevel.Warning, "HttpReverseProxy.ProcessClientRequest(SocketException): Inner exception:{0}\r\nRegular exception: {1}\r\n{2}", innerException, sex.Message, sex.StackTrace);
                    break;
                }
                catch (EmptyRequestException erex)
                {
                    Logging.Instance.LogMessage(this.requestObj.Id, this.requestObj.ProxyProtocol, Loglevel.Warning, "HttpReverseProxy.ProcessClientRequest(EmptyRequestException): Regular exception: {1}", erex.Message);
                    this.requestObj.CurrentException = erex;
                }
                catch (Exception ex)
                {
                    var innerException   = ex.InnerException?.Message ?? "No inner exception found";
                    var src              = ex.Source;
                    var targetSiteName   = ex.TargetSite.Name;
                    var targetSiteModule = ex.TargetSite.Module;
                    Logging.Instance.LogMessage(this.requestObj.Id, this.requestObj.ProxyProtocol, Loglevel.Error, "HttpReverseProxy.ProcessClientRequest(Exception): Inner exception:{0}\r\nRegular exception: {1}\r\n{2}", innerException, ex.Message, ex.StackTrace);
                    Logging.Instance.LogMessage(this.requestObj.Id, this.requestObj.ProxyProtocol, Loglevel.Error, $"HONK: {src}, SiteName:{targetSiteName}, SiteModule:{targetSiteModule}");

                    break;
                }

                // If remote socket was closed or the client sent a "Conection: close" headerByteArray
                // break out of the loop
                if (this.CloseClientServerConnection())
                {
                    break;

                    // Set keep-alive value
                }
                else
                {
                    this.requestObj.ClientRequestObj.ClientBinaryReader.BaseStream.ReadTimeout = Config.ClientReadTimetout;
                }

                // Reinitialize request object.
                this.requestObj.InitRequestValues();
            }
        }
        /*
         * Http2http2XX                 -> Relay response                                   SendServerResponseData2Client()
         * Http2Http3XX                 -> Relay response                                   SendServerResponseData2Client()
         * Http2Https3XXSameUrl         -> Remember redirect, strip SSL, request new Url    SSLCacheAndRedirectClient2RedirectLocation()
         * Http2Https3XXDifferentUrl    -> Remember redirect, strip SSL, relay response
         *
         * Process HTTP request.
         * 1. Detect HTTP Redirect requests
         * 2. Cache new HTTP Redirect locations
         * 3. Recognize incoming lClient requests that need to be SSL-Stripped
         * 4. Process reqular HTTP requests
         *
         */

        #endregion


        #region PRIVATE METHODS

        /// <summary>
        ///
        /// </summary>
        /// <param name="requestString"></param>
        private void ParseRequestString(RequestObj requestObj)
        {
            if (requestObj == null)
            {
                throw new Exception("Request object is invalid");
            }

            if (string.IsNullOrEmpty(requestObj?.ClientRequestObj?.RequestLine?.RequestLine))
            {
                ClientNotificationException exception = new ClientNotificationException("The RequestLine is undefined");
                exception.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.BadRequest);
                throw exception;
            }

            if (!requestObj.ClientRequestObj.RequestLine.RequestLine.Contains(' '))
            {
                ClientNotificationException exception = new ClientNotificationException();
                exception.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.BadRequest);
                throw exception;
            }

            string[] requestSplitBuffer = requestObj.ClientRequestObj.RequestLine.RequestLine.Split(new char[] { ' ' }, 3);
            if (requestSplitBuffer.Count() != 3)
            {
                ClientNotificationException exception = new ClientNotificationException();
                exception.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.BadRequest);
                throw exception;
            }

            if (!Regex.Match(requestSplitBuffer[0].ToLower(), @"^\s*(get|put|post|head|trace|delete|options|connect)\s*$").Success)
            {
                ClientNotificationException exception = new ClientNotificationException();
                exception.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.MethodNotAllowed);
                throw exception;
            }

            if (!requestSplitBuffer[1].StartsWith("/"))
            {
                ClientNotificationException exception = new ClientNotificationException();
                exception.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.BadRequest);
                throw exception;
            }

            if (!requestSplitBuffer[2].StartsWith("HTTP/1."))
            {
                ClientNotificationException exception = new ClientNotificationException();
                exception.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.HttpVersionNotSupported);
                throw exception;
            }

            // Evaluate request method
            requestObj.ClientRequestObj.RequestLine.MethodString = requestSplitBuffer[0];
            requestObj.ClientRequestObj.RequestLine.Path         = requestSplitBuffer[1];
            requestObj.ClientRequestObj.RequestLine.HttpVersion  = requestSplitBuffer[2];

            if (requestObj.ClientRequestObj.RequestLine.MethodString == "GET")
            {
                requestObj.ClientRequestObj.RequestLine.RequestMethod = RequestMethod.GET;
            }
            else if (requestObj.ClientRequestObj.RequestLine.MethodString == "POST")
            {
                requestObj.ClientRequestObj.RequestLine.RequestMethod = RequestMethod.POST;
            }
            else if (requestObj.ClientRequestObj.RequestLine.MethodString == "HEAD")
            {
                requestObj.ClientRequestObj.RequestLine.RequestMethod = RequestMethod.HEAD;
            }
            else if (requestObj.ClientRequestObj.RequestLine.MethodString == "PUT")
            {
                requestObj.ClientRequestObj.RequestLine.RequestMethod = RequestMethod.PUT;
                ClientNotificationException exception = new ClientNotificationException();
                exception.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.MethodNotAllowed);
                throw exception;
            }
            else if (requestObj.ClientRequestObj.RequestLine.MethodString == "DELETE")
            {
                requestObj.ClientRequestObj.RequestLine.RequestMethod = RequestMethod.DELETE;
                ClientNotificationException exception = new ClientNotificationException();
                exception.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.MethodNotAllowed);
                throw exception;
            }
            else if (requestObj.ClientRequestObj.RequestLine.MethodString == "OPTIONS")
            {
                requestObj.ClientRequestObj.RequestLine.RequestMethod = RequestMethod.OPTIONS;
                ClientNotificationException exception = new ClientNotificationException();
                exception.Data.Add(StatusCodeLabel.StatusCode, HttpStatusCode.MethodNotAllowed);
                throw exception;
            }
            else
            {
                requestObj.ClientRequestObj.RequestLine.RequestMethod = RequestMethod.Undefined;
            }

            if (!requestObj.ClientRequestObj.RequestLine.Path.StartsWith("/"))
            {
                requestObj.ClientRequestObj.RequestLine.Path = $"/{requestObj.ClientRequestObj.RequestLine.Path}";
            }

            requestObj.HttpLogData = requestObj.ClientRequestObj.RequestLine.RequestLine.Trim();
        }