/// <summary>
 /// Set the tcp connection to server used by this webclient
 /// </summary>
 /// <param name="Connection"></param>
 internal void SetConnection(TcpConnectionCache Connection)
 {
     Connection.LastAccess = DateTime.Now;
     ServerConnection      = Connection;
 }
        /// <summary>
        /// This is the core request handler method for a particular connection from client
        /// </summary>
        /// <param name="client"></param>
        /// <param name="httpCmd"></param>
        /// <param name="clientStream"></param>
        /// <param name="clientStreamReader"></param>
        /// <param name="clientStreamWriter"></param>
        /// <param name="isHttps"></param>
        /// <returns></returns>
        private async Task HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream,
                                                    CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, bool isHttps)
        {
            TcpConnectionCache connection = null;

            //Loop through each subsequest request on this particular client connection
            //(assuming HTTP connection is kept alive by client)
            while (true)
            {
                if (string.IsNullOrEmpty(httpCmd))
                {
                    Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
                    break;
                }

                var args = new SessionEventArgs(BUFFER_SIZE, HandleHttpSessionResponse);
                args.ProxyClient.TcpClient = client;

                try
                {
                    //break up the line into three components (method, remote URL & Http Version)
                    var httpCmdSplit = httpCmd.Split(ProxyConstants.SpaceSplit, 3);

                    var httpMethod = httpCmdSplit[0];

                    //find the request HTTP version
                    Version version = new Version(1, 1);
                    if (httpCmdSplit.Length == 3)
                    {
                        var httpVersion = httpCmdSplit[2].ToLower().Trim();

                        if (httpVersion == "http/1.0")
                        {
                            version = new Version(1, 0);
                        }
                    }

                    args.WebSession.Request.RequestHeaders = new List <HttpHeader>();

                    //Read the request headers
                    string tmpLine;
                    while (!string.IsNullOrEmpty(tmpLine = await clientStreamReader.ReadLineAsync()))
                    {
                        var header = tmpLine.Split(ProxyConstants.ColonSplit, 2);
                        args.WebSession.Request.RequestHeaders.Add(new HttpHeader(header[0], header[1]));
                    }

                    var httpRemoteUri = new Uri(!isHttps ? httpCmdSplit[1] : (string.Concat("https://", args.WebSession.Request.Host, httpCmdSplit[1])));
#if DEBUG
                    //Just ignore local requests while Debugging
                    //Its annoying
                    if (httpRemoteUri.Host.Contains("localhost"))
                    {
                        Dispose(client, clientStream, clientStreamReader, clientStreamWriter, null);
                        break;
                    }
#endif
                    args.WebSession.Request.RequestUri = httpRemoteUri;

                    args.WebSession.Request.Method      = httpMethod;
                    args.WebSession.Request.HttpVersion = version;
                    args.ProxyClient.ClientStream       = clientStream;
                    args.ProxyClient.ClientStreamReader = clientStreamReader;
                    args.ProxyClient.ClientStreamWriter = clientStreamWriter;

                    PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession);
                    args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority;

                    //If user requested interception do it
                    if (BeforeRequest != null)
                    {
                        Delegate[] invocationList = BeforeRequest.GetInvocationList();
                        Task[]     handlerTasks   = new Task[invocationList.Length];

                        for (int i = 0; i < invocationList.Length; i++)
                        {
                            handlerTasks[i] = ((Func <object, SessionEventArgs, Task>)invocationList[i])(null, args);
                        }

                        await Task.WhenAll(handlerTasks);
                    }

                    //if upgrading to websocket then relay the requet without reading the contents
                    if (args.WebSession.Request.UpgradeToWebSocket)
                    {
                        await TcpHelper.SendRaw(clientStream, httpCmd, args.WebSession.Request.RequestHeaders,
                                                httpRemoteUri.Host, httpRemoteUri.Port, args.IsHttps, SupportedSslProtocols);

                        Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
                        return;
                    }

                    //construct the web request that we are going to issue on behalf of the client.
                    connection = await tcpConnectionCacheManager.GetClient(args.WebSession.Request.RequestUri.Host, args.WebSession.Request.RequestUri.Port, args.IsHttps, version,
                                                                           UpStreamHttpProxy, UpStreamHttpsProxy, BUFFER_SIZE, SupportedSslProtocols, new RemoteCertificateValidationCallback(ValidateServerCertificate),
                                                                           new LocalCertificateSelectionCallback(SelectClientCertificate));

                    args.WebSession.Request.RequestLocked = true;

                    //If request was cancelled by user then dispose the client
                    if (args.WebSession.Request.CancelRequest)
                    {
                        Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
                        break;
                    }

                    //if expect continue is enabled then send the headers first
                    //and see if server would return 100 conitinue
                    if (args.WebSession.Request.ExpectContinue)
                    {
                        args.WebSession.SetConnection(connection);
                        await args.WebSession.SendRequest(Enable100ContinueBehaviour);
                    }

                    //If 100 continue was the response inform that to the client
                    if (Enable100ContinueBehaviour)
                    {
                        if (args.WebSession.Request.Is100Continue)
                        {
                            await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100",
                                                      "Continue", args.ProxyClient.ClientStreamWriter);

                            await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
                        }
                        else if (args.WebSession.Request.ExpectationFailed)
                        {
                            await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417",
                                                      "Expectation Failed", args.ProxyClient.ClientStreamWriter);

                            await args.ProxyClient.ClientStreamWriter.WriteLineAsync();
                        }
                    }

                    //If expect continue is not enabled then set the connectio and send request headers
                    if (!args.WebSession.Request.ExpectContinue)
                    {
                        args.WebSession.SetConnection(connection);
                        await args.WebSession.SendRequest(Enable100ContinueBehaviour);
                    }

                    //If request was modified by user
                    if (args.WebSession.Request.RequestBodyRead)
                    {
                        if (args.WebSession.Request.ContentEncoding != null)
                        {
                            args.WebSession.Request.RequestBody = await GetCompressedResponseBody(args.WebSession.Request.ContentEncoding, args.WebSession.Request.RequestBody);
                        }
                        //chunked send is not supported as of now
                        args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length;

                        var newStream = args.WebSession.ServerConnection.Stream;
                        await newStream.WriteAsync(args.WebSession.Request.RequestBody, 0, args.WebSession.Request.RequestBody.Length);
                    }
                    else
                    {
                        if (!args.WebSession.Request.ExpectationFailed)
                        {
                            //If its a post/put request, then read the client html body and send it to server
                            if (httpMethod.ToUpper() == "POST" || httpMethod.ToUpper() == "PUT")
                            {
                                await SendClientRequestBody(args);
                            }
                        }
                    }

                    //If not expectation failed response was returned by server then parse response
                    if (!args.WebSession.Request.ExpectationFailed)
                    {
                        await HandleHttpSessionResponse(args);
                    }

                    //if connection is closing exit
                    if (args.WebSession.Response.ResponseKeepAlive == false)
                    {
                        Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
                        return;
                    }

                    //send the tcp connection to server back to connection cache for reuse
                    await tcpConnectionCacheManager.ReleaseClient(connection);

                    // read the next request
                    httpCmd = await clientStreamReader.ReadLineAsync();
                }
                catch
                {
                    Dispose(client, clientStream, clientStreamReader, clientStreamWriter, args);
                    break;
                }
            }
        }