private static void Dispose(TcpClient client, Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, SessionEventArgs args) { if (args != null) args.Dispose(); if (clientStreamReader != null) clientStreamReader.Dispose(); if (clientStreamWriter != null) clientStreamWriter.Dispose(); if (clientStream != null) clientStream.Dispose(); if (client != null) client.Close(); }
//Called asynchronously when a request was successfully and we received the response public async Task HandleHttpSessionResponse(SessionEventArgs args) { //read response & headers from server await args.WebSession.ReceiveResponse(); try { if (!args.WebSession.Response.ResponseBodyRead) { args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream; } args.ReRequest = false; //If user requested call back then do it if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) { Delegate[] invocationList = BeforeResponse.GetInvocationList(); Task[] handlerTasks = new Task[invocationList.Length]; for (int i = 0; i < invocationList.Length; i++) { handlerTasks[i] = ((Func <object, SessionEventArgs, Task>)invocationList[i])(this, args); } await Task.WhenAll(handlerTasks); } if (args.ReRequest) { await HandleHttpSessionRequestInternal(null, args, null, null, true).ConfigureAwait(false); return; } args.WebSession.Response.ResponseLocked = true; //Write back to client 100-conitinue response if that's what server returned if (args.WebSession.Response.Is100Continue) { await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100", "Continue", args.ProxyClient.ClientStreamWriter); await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); } else if (args.WebSession.Response.ExpectationFailed) { await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417", "Expectation Failed", args.ProxyClient.ClientStreamWriter); await args.ProxyClient.ClientStreamWriter.WriteLineAsync(); } //Write back response status to client await WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode, args.WebSession.Response.ResponseStatusDescription, args.ProxyClient.ClientStreamWriter); if (args.WebSession.Response.ResponseBodyRead) { var isChunked = args.WebSession.Response.IsChunked; var contentEncoding = args.WebSession.Response.ContentEncoding; if (contentEncoding != null) { args.WebSession.Response.ResponseBody = await GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody); if (isChunked == false) { args.WebSession.Response.ContentLength = args.WebSession.Response.ResponseBody.Length; } else { args.WebSession.Response.ContentLength = -1; } } await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response); await args.ProxyClient.ClientStream.WriteResponseBody(args.WebSession.Response.ResponseBody, isChunked); } else { await WriteResponseHeaders(args.ProxyClient.ClientStreamWriter, args.WebSession.Response); //Write body only if response is chunked or content length >0 //Is none are true then check if connection:close header exist, if so write response until server or client terminates the connection if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0 || !args.WebSession.Response.ResponseKeepAlive) { await args.WebSession.ServerConnection.StreamReader .WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength); } //write response if connection:keep-alive header exist and when version is http/1.0 //Because in Http 1.0 server can return a response without content-length (expectation being client would read until end of stream) else if (args.WebSession.Response.ResponseKeepAlive && args.WebSession.Response.HttpVersion.Minor == 0) { await args.WebSession.ServerConnection.StreamReader .WriteResponseBody(BUFFER_SIZE, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength); } } await args.ProxyClient.ClientStream.FlushAsync(); } catch { Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args); } finally { args.Dispose(); } }
/// <summary> /// This is the core request handler method for a particular connection from client. /// Will create new session (request/response) sequence until /// client/server abruptly terminates connection or by normal HTTP termination. /// </summary> /// <param name="endPoint">The proxy endpoint.</param> /// <param name="clientConnection">The client connection.</param> /// <param name="clientStream">The client stream.</param> /// <param name="clientStreamWriter">The client stream writer.</param> /// <param name="cancellationTokenSource">The cancellation token source for this async task.</param> /// <param name="httpsConnectHostname"> /// The https hostname as appeared in CONNECT request if this is a HTTPS request from /// explicit endpoint. /// </param> /// <param name="connectRequest">The Connect request if this is a HTTPS request from explicit endpoint.</param> private async Task HandleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection, CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter, CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest) { var cancellationToken = cancellationTokenSource.Token; TcpServerConnection serverConnection = null; bool serverConnectionClose = false; try { // Loop through each subsequest request on this particular client connection // (assuming HTTP connection is kept alive by client) while (true) { // read the request line string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (string.IsNullOrEmpty(httpCmd)) { return; } var args = new SessionEventArgs(BufferSize, endPoint, cancellationTokenSource, ExceptionFunc) { ProxyClient = { ClientConnection = clientConnection }, WebSession = { ConnectRequest = connectRequest } }; try { try { Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version); // Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStream, args.WebSession.Request.Headers, cancellationToken); Uri httpRemoteUri; if (uriSchemeRegex.IsMatch(httpUrl)) { try { httpRemoteUri = new Uri(httpUrl); } catch (Exception ex) { throw new Exception($"Invalid URI: '{httpUrl}'", ex); } } else { string host = args.WebSession.Request.Host ?? httpsConnectHostname; string hostAndPath = host; if (httpUrl.StartsWith("/")) { hostAndPath += httpUrl; } string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath); try { httpRemoteUri = new Uri(url); } catch (Exception ex) { throw new Exception($"Invalid URI: '{url}'", ex); } } var request = args.WebSession.Request; request.RequestUri = httpRemoteUri; request.OriginalUrl = httpUrl; request.Method = httpMethod; request.HttpVersion = version; args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStreamWriter = clientStreamWriter; if (!args.IsTransparent) { // proxy authorization check if (httpsConnectHostname == null && await CheckAuthorization(args) == false) { await InvokeBeforeResponse(args); // send the response await clientStreamWriter.WriteResponseAsync(args.WebSession.Response, cancellationToken : cancellationToken); return; } PrepareRequestHeaders(request.Headers); request.Host = request.RequestUri.Authority; } // if win auth is enabled // we need a cache of request body // so that we can send it after authentication in WinAuthHandler.cs if (isWindowsAuthenticationEnabledAndSupported && request.HasBody) { await args.GetRequestBody(cancellationToken); } request.OriginalHasBody = request.HasBody; // If user requested interception do it await InvokeBeforeRequest(args); var response = args.WebSession.Response; if (request.CancelRequest) { // syphon out the request body from client before setting the new body await args.SyphonOutBodyAsync(true, cancellationToken); await HandleHttpSessionResponse(args); if (!response.KeepAlive) { return; } continue; } // create a new connection if hostname/upstream end point changes if (serverConnection != null && (!serverConnection.HostName.EqualsIgnoreCase(request.RequestUri.Host) || args.WebSession.UpStreamEndPoint?.Equals(serverConnection.UpStreamEndPoint) == false)) { tcpConnectionFactory.Release(serverConnection, true); serverConnection = null; } if (serverConnection == null) { serverConnection = await GetServerConnection(args, false, clientConnection.NegotiatedApplicationProtocol, cancellationToken); } // if upgrading to websocket then relay the requet without reading the contents if (request.UpgradeToWebSocket) { // prepare the prefix content await serverConnection.StreamWriter.WriteLineAsync(httpCmd, cancellationToken); await serverConnection.StreamWriter.WriteHeadersAsync(request.Headers, cancellationToken : cancellationToken); string httpStatus = await serverConnection.Stream.ReadLineAsync(cancellationToken); Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, out string responseStatusDescription); response.HttpVersion = responseVersion; response.StatusCode = responseStatusCode; response.StatusDescription = responseStatusDescription; await HeaderParser.ReadHeaders(serverConnection.Stream, response.Headers, cancellationToken); if (!args.IsTransparent) { await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); } // If user requested call back then do it if (!args.WebSession.Response.Locked) { await InvokeBeforeResponse(args); } await TcpHelper.SendRaw(clientStream, serverConnection.Stream, BufferSize, (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); }, cancellationTokenSource, ExceptionFunc); return; } // construct the web request that we are going to issue on behalf of the client. await HandleHttpSessionRequestInternal(serverConnection, args); if (args.WebSession.ServerConnection == null) { return; } // if connection is closing exit if (!response.KeepAlive) { serverConnectionClose = true; return; } if (cancellationTokenSource.IsCancellationRequested) { throw new Exception("Session was terminated by user."); } } catch (Exception e) when(!(e is ProxyHttpException)) { throw new ProxyHttpException("Error occured whilst handling session request", e, args); } } catch (Exception e) { args.Exception = e; serverConnectionClose = true; throw; } finally { await InvokeAfterResponse(args); args.Dispose(); } } } finally { tcpConnectionFactory.Release(serverConnection, serverConnectionClose || !EnableConnectionPool); } }
/// <summary> /// This is the core request handler method for a particular connection from client /// Will create new session (request/response) sequence until /// client/server abruptly terminates connection or by normal HTTP termination /// </summary> /// <param name="client"></param> /// <param name="httpCmd"></param> /// <param name="clientStream"></param> /// <param name="clientStreamReader"></param> /// <param name="clientStreamWriter"></param> /// <param name="httpsConnectHostname"></param> /// <param name="endPoint"></param> /// <param name="connectRequest"></param> /// <param name="isTransparentEndPoint"></param> /// <returns></returns> private async Task <bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, string httpsConnectHostname, ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false) { bool disposed = false; TcpConnection 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)) { break; } var args = new SessionEventArgs(BufferSize, endPoint, HandleHttpSessionResponse) { ProxyClient = { TcpClient = client }, WebSession = { ConnectRequest = connectRequest } }; try { string httpMethod; string httpUrl; Version version; Request.ParseRequestLine(httpCmd, out httpMethod, out httpUrl, out version); //Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.RequestHeaders); var httpRemoteUri = new Uri(httpsConnectHostname == null ? isTransparentEndPoint ? string.Concat("http://", args.WebSession.Request.Host, httpUrl) : httpUrl : string.Concat("https://", args.WebSession.Request.Host ?? httpsConnectHostname, httpUrl)); args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.OriginalRequestUrl = httpUrl; args.WebSession.Request.Method = httpMethod; args.WebSession.Request.HttpVersion = version; args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamWriter = clientStreamWriter; //proxy authorization check if (httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false) { args.Dispose(); break; } PrepareRequestHeaders(args.WebSession.Request.RequestHeaders); args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority; #if NET45 //if win auth is enabled //we need a cache of request body //so that we can send it after authentication in WinAuthHandler.cs if (EnableWinAuth && !RunTime.IsRunningOnMono && args.WebSession.Request.HasBody) { await args.GetRequestBody(); } #endif //If user requested interception do it if (BeforeRequest != null) { await BeforeRequest.InvokeParallelAsync(this, args, ExceptionFunc); } if (args.WebSession.Request.CancelRequest) { args.Dispose(); break; } //create a new connection if hostname changes if (connection != null && !connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase)) { connection.Dispose(); UpdateServerConnectionCount(false); connection = null; } if (connection == null) { connection = await GetServerConnection(args, false); } //if upgrading to websocket then relay the requet without reading the contents if (args.WebSession.Request.UpgradeToWebSocket) { //prepare the prefix content var requestHeaders = args.WebSession.Request.RequestHeaders; byte[] requestBytes; using (var ms = new MemoryStream()) using (var writer = new HttpRequestWriter(ms)) { writer.WriteLine(httpCmd); writer.WriteHeaders(requestHeaders); requestBytes = ms.ToArray(); } await connection.Stream.WriteAsync(requestBytes, 0, requestBytes.Length); string httpStatus = await connection.StreamReader.ReadLineAsync(); Version responseVersion; int responseStatusCode; string responseStatusDescription; Response.ParseResponseLine(httpStatus, out responseVersion, out responseStatusCode, out responseStatusDescription); args.WebSession.Response.HttpVersion = responseVersion; args.WebSession.Response.ResponseStatusCode = responseStatusCode; args.WebSession.Response.ResponseStatusDescription = responseStatusDescription; await HeaderParser.ReadHeaders(connection.StreamReader, args.WebSession.Response.ResponseHeaders); await clientStreamWriter.WriteResponseAsync(args.WebSession.Response); //If user requested call back then do it if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) { await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc); } await TcpHelper.SendRaw(clientStream, connection.Stream, (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); }); args.Dispose(); break; } //construct the web request that we are going to issue on behalf of the client. disposed = await HandleHttpSessionRequestInternal(connection, args, false); if (disposed) { //already disposed inside above method args.Dispose(); break; } //if connection is closing exit if (args.WebSession.Response.ResponseKeepAlive == false) { args.Dispose(); break; } args.Dispose(); // read the next request httpCmd = await clientStreamReader.ReadLineAsync(); } catch (Exception e) { ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args)); break; } } if (!disposed) { Dispose(clientStream, clientStreamReader, clientStreamWriter, connection); } return(true); }
/// <summary> /// This is the core request handler method for a particular connection from client /// Will create new session (request/response) sequence until /// client/server abruptly terminates connection or by normal HTTP termination /// </summary> /// <param name="client"></param> /// <param name="httpCmd"></param> /// <param name="clientStream"></param> /// <param name="clientStreamReader"></param> /// <param name="clientStreamWriter"></param> /// <param name="httpsHostName"></param> /// <param name="endPoint"></param> /// <param name="connectHeaders"></param> /// <returns></returns> private async Task <bool> HandleHttpSessionRequest(TcpClient client, string httpCmd, Stream clientStream, CustomBinaryReader clientStreamReader, StreamWriter clientStreamWriter, string httpsHostName, ProxyEndPoint endPoint, List <HttpHeader> connectHeaders) { bool disposed = false; TcpConnection 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)) { break; } var args = new SessionEventArgs(BufferSize, HandleHttpSessionResponse) { ProxyClient = { TcpClient = client }, WebSession = { ConnectHeaders = connectHeaders } }; args.WebSession.ProcessId = new Lazy <int>(() => { var remoteEndPoint = (IPEndPoint)args.ProxyClient.TcpClient.Client.RemoteEndPoint; //If client is localhost get the process id if (NetworkHelper.IsLocalIpAddress(remoteEndPoint.Address)) { return(NetworkHelper.GetProcessIdFromPort(remoteEndPoint.Port, endPoint.IpV6Enabled)); } //can't access process Id of remote request from remote machine return(-1); }); 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 var httpVersion = HttpHeader.Version11; if (httpCmdSplit.Length == 3) { var httpVersionString = httpCmdSplit[2].Trim(); if (string.Equals(httpVersionString, "HTTP/1.0", StringComparison.OrdinalIgnoreCase)) { httpVersion = HttpHeader.Version10; } } //Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.NonUniqueRequestHeaders, args.WebSession.Request.RequestHeaders); var httpRemoteUri = new Uri(httpsHostName == null ? httpCmdSplit[1] : string.Concat("https://", args.WebSession.Request.Host ?? httpsHostName, httpCmdSplit[1])); args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.Method = httpMethod.Trim().ToUpper(); args.WebSession.Request.HttpVersion = httpVersion; args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamWriter = clientStreamWriter; if (httpsHostName == null && await CheckAuthorization(clientStreamWriter, args.WebSession.Request.RequestHeaders.Values) == false) { args.Dispose(); break; } PrepareRequestHeaders(args.WebSession.Request.RequestHeaders, args.WebSession); args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority; //if win auth is enabled //we need a cache of request body //so that we can send it after authentication in WinAuthHandler.cs if (EnableWinAuth && !RunTime.IsRunningOnMono && args.WebSession.Request.HasBody) { await args.GetRequestBody(); } //If user requested interception do it if (BeforeRequest != null) { await BeforeRequest.InvokeParallelAsync(this, args); } //if upgrading to websocket then relay the requet without reading the contents if (args.WebSession.Request.UpgradeToWebSocket) { await TcpHelper.SendRaw(this, httpRemoteUri.Host, httpRemoteUri.Port, httpCmd, httpVersion, args.WebSession.Request.RequestHeaders, args.IsHttps, clientStream, tcpConnectionFactory, connection); args.Dispose(); break; } if (connection == null) { connection = await GetServerConnection(args); } //construct the web request that we are going to issue on behalf of the client. disposed = await HandleHttpSessionRequestInternal(connection, args, false); if (disposed) { //already disposed inside above method args.Dispose(); break; } if (args.WebSession.Request.CancelRequest) { args.Dispose(); break; } //if connection is closing exit if (args.WebSession.Response.ResponseKeepAlive == false) { args.Dispose(); break; } args.Dispose(); // read the next request httpCmd = await clientStreamReader.ReadLineAsync(); } catch (Exception e) { ExceptionFunc(new ProxyHttpException("Error occured whilst handling session request", e, args)); break; } } if (!disposed) { Dispose(clientStream, clientStreamReader, clientStreamWriter, connection); } return(true); }
/// <summary> /// This is the core request handler method for a particular connection from client. /// Will create new session (request/response) sequence until /// client/server abruptly terminates connection or by normal HTTP termination. /// </summary> /// <param name="endPoint">The proxy endpoint.</param> /// <param name="clientConnection">The client connection.</param> /// <param name="clientStream">The client stream.</param> /// <param name="clientStreamWriter">The client stream writer.</param> /// <param name="cancellationTokenSource">The cancellation token source for this async task.</param> /// <param name="httpsConnectHostname"> /// The https hostname as appeared in CONNECT request if this is a HTTPS request from /// explicit endpoint. /// </param> /// <param name="connectRequest">The Connect request if this is a HTTPS request from explicit endpoint.</param> /// <param name="prefetchConnectionTask">Prefetched server connection for current client using Connect/SNI headers.</param> private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection, CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter, CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, TunnelConnectSessionEventArgs connectArgs, Task <TcpServerConnection> prefetchConnectionTask = null) { var connectRequest = connectArgs?.HttpClient.ConnectRequest; var prefetchTask = prefetchConnectionTask; TcpServerConnection connection = null; bool closeServerConnection = false; try { var cancellationToken = cancellationTokenSource.Token; // Loop through each subsequest request on this particular client connection // (assuming HTTP connection is kept alive by client) while (true) { // read the request line string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (string.IsNullOrEmpty(httpCmd)) { return; } var args = new SessionEventArgs(this, endPoint, cancellationTokenSource) { ProxyClient = { Connection = clientConnection }, HttpClient = { ConnectRequest = connectRequest }, UserData = connectArgs?.UserData }; try { try { Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version); // Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStream, args.HttpClient.Request.Headers, cancellationToken); Uri httpRemoteUri; if (ProxyConstants.UriSchemeRegex.IsMatch(httpUrl)) { try { httpRemoteUri = new Uri(httpUrl); } catch (Exception ex) { throw new Exception($"Invalid URI: '{httpUrl}'", ex); } } else { string host = args.HttpClient.Request.Host ?? httpsConnectHostname; string hostAndPath = host; if (httpUrl.StartsWith("/")) { hostAndPath += httpUrl; } string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath); try { httpRemoteUri = new Uri(url); } catch (Exception ex) { throw new Exception($"Invalid URI: '{url}'", ex); } } var request = args.HttpClient.Request; request.RequestUri = httpRemoteUri; request.OriginalUrl = httpUrl; request.Method = httpMethod; request.HttpVersion = version; args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStreamWriter = clientStreamWriter; if (!args.IsTransparent) { // proxy authorization check if (httpsConnectHostname == null && await checkAuthorization(args) == false) { await invokeBeforeResponse(args); // send the response await clientStreamWriter.WriteResponseAsync(args.HttpClient.Response, cancellationToken : cancellationToken); return; } prepareRequestHeaders(request.Headers); request.Host = request.RequestUri.Authority; } // if win auth is enabled // we need a cache of request body // so that we can send it after authentication in WinAuthHandler.cs if (isWindowsAuthenticationEnabledAndSupported && request.HasBody) { await args.GetRequestBody(cancellationToken); } //we need this to syphon out data from connection if API user changes them. request.SetOriginalHeaders(); args.TimeLine["Request Received"] = DateTime.Now; // If user requested interception do it await invokeBeforeRequest(args); var response = args.HttpClient.Response; if (request.CancelRequest) { if (!(Enable100ContinueBehaviour && request.ExpectContinue)) { // syphon out the request body from client before setting the new body await args.SyphonOutBodyAsync(true, cancellationToken); } await handleHttpSessionResponse(args); if (!response.KeepAlive) { return; } continue; } //If prefetch task is available. if (connection == null && prefetchTask != null) { connection = await prefetchTask; prefetchTask = null; } // create a new connection if cache key changes. // only gets hit when connection pool is disabled. // or when prefetch task has a unexpectedly different connection. if (connection != null && (await tcpConnectionFactory.GetConnectionCacheKey(this, args, clientConnection.NegotiatedApplicationProtocol) != connection.CacheKey)) { await tcpConnectionFactory.Release(connection); connection = null; } var result = await handleHttpSessionRequest(httpCmd, args, connection, clientConnection.NegotiatedApplicationProtocol, cancellationToken, cancellationTokenSource); //update connection to latest used connection = result.LatestConnection; closeServerConnection = !result.Continue; //throw if exception happened if (!result.IsSuccess) { throw result.Exception; } if (!result.Continue) { return; } //user requested if (args.HttpClient.CloseServerConnection) { closeServerConnection = true; return; } // if connection is closing exit if (!response.KeepAlive) { closeServerConnection = true; return; } if (cancellationTokenSource.IsCancellationRequested) { throw new Exception("Session was terminated by user."); } //Release server connection for each HTTP session instead of per client connection. //This will be more efficient especially when client is idly holding server connection //between sessions without using it. //Do not release authenticated connections for performance reasons. //Otherwise it will keep authenticating per session. if (EnableConnectionPool && connection != null && !connection.IsWinAuthenticated) { await tcpConnectionFactory.Release(connection); connection = null; } } catch (Exception e) when(!(e is ProxyHttpException)) { throw new ProxyHttpException("Error occured whilst handling session request", e, args); } } catch (Exception e) { args.Exception = e; closeServerConnection = true; throw; } finally { await invokeAfterResponse(args); args.Dispose(); } } } finally { await tcpConnectionFactory.Release(connection, closeServerConnection); await tcpConnectionFactory.Release(prefetchTask, closeServerConnection); } }
//Called asynchronously when a request was successfully and we received the response private static void HandleHttpSessionResponse(SessionEventArgs args) { args.ProxySession.ReceiveResponse(); try { args.ProxySession.Response.ResponseHeaders = ReadResponseHeaders(args.ProxySession); args.ProxySession.Response.ResponseStream = args.ProxySession.ProxyClient.ServerStreamReader.BaseStream; if (BeforeResponse != null) { args.ProxySession.Response.Encoding = args.ProxySession.GetResponseEncoding(); BeforeResponse(null, args); } args.ProxySession.Response.ResponseLocked = true; if (args.ProxySession.Response.ResponseBodyRead) { var isChunked = args.ProxySession.Response.IsChunked; var contentEncoding = args.ProxySession.Response.ContentEncoding; if(contentEncoding!=null) switch (contentEncoding) { case "gzip": args.ProxySession.Response.ResponseBody = CompressionHelper.CompressGzip(args.ProxySession.Response.ResponseBody); break; case "deflate": args.ProxySession.Response.ResponseBody = CompressionHelper.CompressDeflate(args.ProxySession.Response.ResponseBody); break; case "zlib": args.ProxySession.Response.ResponseBody = CompressionHelper.CompressZlib(args.ProxySession.Response.ResponseBody); break; } WriteResponseStatus(args.ProxySession.Response.HttpVersion, args.ProxySession.Response.ResponseStatusCode, args.ProxySession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter); WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders, args.ProxySession.Response.ResponseBody.Length, isChunked); WriteResponseBody(args.Client.ClientStream, args.ProxySession.Response.ResponseBody, isChunked); } else { WriteResponseStatus(args.ProxySession.Response.HttpVersion, args.ProxySession.Response.ResponseStatusCode, args.ProxySession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter); WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders); if (args.ProxySession.Response.IsChunked || args.ProxySession.Response.ContentLength > 0) WriteResponseBody(args.ProxySession.ProxyClient.ServerStreamReader, args.Client.ClientStream, args.ProxySession.Response.IsChunked, args.ProxySession.Response.ContentLength); } args.Client.ClientStream.Flush(); } catch { Dispose(args.Client.TcpClient, args.Client.ClientStream, args.Client.ClientStreamReader, args.Client.ClientStreamWriter, args); } finally { args.Dispose(); } }
/// <summary> /// This is the core request handler method for a particular connection from client. /// Will create new session (request/response) sequence until /// client/server abruptly terminates connection or by normal HTTP termination. /// </summary> /// <param name="endPoint">The proxy endpoint.</param> /// <param name="clientStream">The client stream.</param> /// <param name="cancellationTokenSource">The cancellation token source for this async task.</param> /// <param name="connectArgs">The Connect request if this is a HTTPS request from explicit endpoint.</param> /// <param name="prefetchConnectionTask">Prefetched server connection for current client using Connect/SNI headers.</param> /// <param name="isHttps">Is HTTPS</param> private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, HttpClientStream clientStream, CancellationTokenSource cancellationTokenSource, TunnelConnectSessionEventArgs?connectArgs = null, Task <TcpServerConnection>?prefetchConnectionTask = null, bool isHttps = false) { var connectRequest = connectArgs?.HttpClient.ConnectRequest; var prefetchTask = prefetchConnectionTask; TcpServerConnection?connection = null; bool closeServerConnection = false; try { var cancellationToken = cancellationTokenSource.Token; // Loop through each subsequent request on this particular client connection // (assuming HTTP connection is kept alive by client) while (true) { if (clientStream.IsClosed) { return; } // read the request line var requestLine = await clientStream.ReadRequestLine(cancellationToken); if (requestLine.IsEmpty()) { return; } var args = new SessionEventArgs(this, endPoint, clientStream, connectRequest, cancellationTokenSource) { UserData = connectArgs?.UserData }; var request = args.HttpClient.Request; if (isHttps) { request.IsHttps = true; } try { try { // Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStream, args.HttpClient.Request.Headers, cancellationToken); if (connectRequest != null) { request.IsHttps = connectRequest.IsHttps; request.Authority = connectRequest.Authority; } request.RequestUriString8 = requestLine.RequestUri; request.Method = requestLine.Method; request.HttpVersion = requestLine.Version; // we need this to syphon out data from connection if API user changes them. request.SetOriginalHeaders(); // If user requested interception do it await onBeforeRequest(args); if (!args.IsTransparent && !args.IsSocks) { // proxy authorization check if (connectRequest == null && await checkAuthorization(args) == false) { await onBeforeResponse(args); // send the response await clientStream.WriteResponseAsync(args.HttpClient.Response, cancellationToken); return; } prepareRequestHeaders(request.Headers); request.Host = request.RequestUri.Authority; } // if win auth is enabled // we need a cache of request body // so that we can send it after authentication in WinAuthHandler.cs if (args.EnableWinAuth && request.HasBody) { await args.GetRequestBody(cancellationToken); } var response = args.HttpClient.Response; if (request.CancelRequest) { if (!(Enable100ContinueBehaviour && request.ExpectContinue)) { // syphon out the request body from client before setting the new body await args.SyphonOutBodyAsync(true, cancellationToken); } await handleHttpSessionResponse(args); if (!response.KeepAlive) { return; } continue; } // If prefetch task is available. if (connection == null && prefetchTask != null) { try { connection = await prefetchTask; } catch (SocketException e) { if (e.SocketErrorCode != SocketError.HostNotFound) { throw; } } prefetchTask = null; } if (connection != null) { var socket = connection.TcpSocket; bool part1 = socket.Poll(1000, SelectMode.SelectRead); bool part2 = socket.Available == 0; if (part1 & part2) { //connection is closed await tcpConnectionFactory.Release(connection, true); connection = null; } } // create a new connection if cache key changes. // only gets hit when connection pool is disabled. // or when prefetch task has a unexpectedly different connection. if (connection != null && (await tcpConnectionFactory.GetConnectionCacheKey(this, args, clientStream.Connection.NegotiatedApplicationProtocol) != connection.CacheKey)) { await tcpConnectionFactory.Release(connection); connection = null; } var result = await handleHttpSessionRequest(args, connection, clientStream.Connection.NegotiatedApplicationProtocol, cancellationToken, cancellationTokenSource); // update connection to latest used connection = result.LatestConnection; closeServerConnection = !result.Continue; // throw if exception happened if (result.Exception != null) { throw result.Exception; } if (!result.Continue) { return; } // user requested if (args.HttpClient.CloseServerConnection) { closeServerConnection = true; return; } // if connection is closing exit if (!response.KeepAlive) { closeServerConnection = true; return; } if (cancellationTokenSource.IsCancellationRequested) { throw new Exception("Session was terminated by user."); } // Release server connection for each HTTP session instead of per client connection. // This will be more efficient especially when client is idly holding server connection // between sessions without using it. // Do not release authenticated connections for performance reasons. // Otherwise it will keep authenticating per session. if (EnableConnectionPool && connection != null && !connection.IsWinAuthenticated) { await tcpConnectionFactory.Release(connection); connection = null; } } catch (Exception e) when(!(e is ProxyHttpException)) { throw new ProxyHttpException("Error occured whilst handling session request", e, args); } } catch (Exception e) { args.Exception = e; closeServerConnection = true; throw; } finally { await onAfterResponse(args); args.Dispose(); } } } finally { if (connection != null) { await tcpConnectionFactory.Release(connection, closeServerConnection); } await tcpConnectionFactory.Release(prefetchTask, closeServerConnection); } }
/// <summary> /// This is the core request handler method for a particular connection from client /// Will create new session (request/response) sequence until /// client/server abruptly terminates connection or by normal HTTP termination /// </summary> /// <param name="client"></param> /// <param name="clientStream"></param> /// <param name="clientStreamReader"></param> /// <param name="clientStreamWriter"></param> /// <param name="httpsConnectHostname"></param> /// <param name="endPoint"></param> /// <param name="connectRequest"></param> /// <param name="isTransparentEndPoint"></param> /// <returns></returns> private async Task HandleHttpSessionRequest(TcpClient client, CustomBufferedStream clientStream, CustomBinaryReader clientStreamReader, HttpResponseWriter clientStreamWriter, string httpsConnectHostname, ProxyEndPoint endPoint, ConnectRequest connectRequest, bool isTransparentEndPoint = false) { TcpConnection connection = null; try { //Loop through each subsequest request on this particular client connection //(assuming HTTP connection is kept alive by client) while (true) { // read the request line string httpCmd = await clientStreamReader.ReadLineAsync(); if (string.IsNullOrEmpty(httpCmd)) { break; } var args = new SessionEventArgs(BufferSize, endPoint, ExceptionFunc) { ProxyClient = { TcpClient = client }, WebSession = { ConnectRequest = connectRequest } }; try { Request.ParseRequestLine(httpCmd, out string httpMethod, out string httpUrl, out var version); //Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStreamReader, args.WebSession.Request.Headers); Uri httpRemoteUri; if (uriSchemeRegex.IsMatch(httpUrl)) { try { httpRemoteUri = new Uri(httpUrl); } catch (Exception ex) { throw new Exception($"Invalid URI: '{httpUrl}'", ex); } } else { string host = args.WebSession.Request.Host ?? httpsConnectHostname; string hostAndPath = host; if (httpUrl.StartsWith("/")) { hostAndPath += httpUrl; } string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath); try { httpRemoteUri = new Uri(url); } catch (Exception ex) { throw new Exception($"Invalid URI: '{url}'", ex); } } args.WebSession.Request.RequestUri = httpRemoteUri; args.WebSession.Request.OriginalUrl = httpUrl; args.WebSession.Request.Method = httpMethod; args.WebSession.Request.HttpVersion = version; args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStreamReader = clientStreamReader; args.ProxyClient.ClientStreamWriter = clientStreamWriter; //proxy authorization check if (!args.IsTransparent && httpsConnectHostname == null && await CheckAuthorization(clientStreamWriter, args) == false) { break; } if (!isTransparentEndPoint) { PrepareRequestHeaders(args.WebSession.Request.Headers); args.WebSession.Request.Host = args.WebSession.Request.RequestUri.Authority; } //if win auth is enabled //we need a cache of request body //so that we can send it after authentication in WinAuthHandler.cs if (isWindowsAuthenticationEnabledAndSupported && args.WebSession.Request.HasBody) { await args.GetRequestBody(); } //If user requested interception do it if (BeforeRequest != null) { await BeforeRequest.InvokeAsync(this, args, ExceptionFunc); } var response = args.WebSession.Response; if (args.WebSession.Request.CancelRequest) { await HandleHttpSessionResponse(args); if (!response.KeepAlive) { break; } continue; } //create a new connection if hostname/upstream end point changes if (connection != null && (!connection.HostName.Equals(args.WebSession.Request.RequestUri.Host, StringComparison.OrdinalIgnoreCase) || (args.WebSession.UpStreamEndPoint != null && !args.WebSession.UpStreamEndPoint.Equals(connection.UpStreamEndPoint)))) { connection.Dispose(); connection = null; } if (connection == null) { connection = await GetServerConnection(args, false); } //if upgrading to websocket then relay the requet without reading the contents if (args.WebSession.Request.UpgradeToWebSocket) { //prepare the prefix content var requestHeaders = args.WebSession.Request.Headers; await connection.StreamWriter.WriteLineAsync(httpCmd); await connection.StreamWriter.WriteHeadersAsync(requestHeaders); string httpStatus = await connection.StreamReader.ReadLineAsync(); Response.ParseResponseLine(httpStatus, out var responseVersion, out int responseStatusCode, out string responseStatusDescription); response.HttpVersion = responseVersion; response.StatusCode = responseStatusCode; response.StatusDescription = responseStatusDescription; await HeaderParser.ReadHeaders(connection.StreamReader, response.Headers); if (!args.IsTransparent) { await clientStreamWriter.WriteResponseAsync(response); } //If user requested call back then do it if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) { await BeforeResponse.InvokeAsync(this, args, ExceptionFunc); } await TcpHelper.SendRaw(clientStream, connection.Stream, BufferSize, (buffer, offset, count) => { args.OnDataSent(buffer, offset, count); }, (buffer, offset, count) => { args.OnDataReceived(buffer, offset, count); }, ExceptionFunc); break; } //construct the web request that we are going to issue on behalf of the client. await HandleHttpSessionRequestInternal(connection, args); //if connection is closing exit if (!response.KeepAlive) { break; } } catch (Exception e) when(!(e is ProxyHttpException)) { throw new ProxyHttpException("Error occured whilst handling session request", e, args); } finally { args.Dispose(); } } } finally { connection?.Dispose(); } }
//Called asynchronously when a request was successfully and we received the response private static void HandleHttpSessionResponse(SessionEventArgs args) { args.ProxySession.ReceiveResponse(); try { args.ProxySession.Response.ResponseHeaders = ReadResponseHeaders(args.ProxySession); args.ProxySession.Response.ResponseStream = args.ProxySession.ProxyClient.ServerStreamReader.BaseStream; if (BeforeResponse != null) { args.ProxySession.Response.Encoding = args.ProxySession.GetResponseEncoding(); BeforeResponse(null, args); } args.ProxySession.Response.ResponseLocked = true; if (args.ProxySession.Response.ResponseBodyRead) { var isChunked = args.ProxySession.Response.IsChunked; var contentEncoding = args.ProxySession.Response.ContentEncoding; if (contentEncoding != null) { switch (contentEncoding) { case "gzip": args.ProxySession.Response.ResponseBody = CompressionHelper.CompressGzip(args.ProxySession.Response.ResponseBody); break; case "deflate": args.ProxySession.Response.ResponseBody = CompressionHelper.CompressDeflate(args.ProxySession.Response.ResponseBody); break; case "zlib": args.ProxySession.Response.ResponseBody = CompressionHelper.CompressZlib(args.ProxySession.Response.ResponseBody); break; } } WriteResponseStatus(args.ProxySession.Response.HttpVersion, args.ProxySession.Response.ResponseStatusCode, args.ProxySession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter); WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders, args.ProxySession.Response.ResponseBody.Length, isChunked); WriteResponseBody(args.Client.ClientStream, args.ProxySession.Response.ResponseBody, isChunked); } else { WriteResponseStatus(args.ProxySession.Response.HttpVersion, args.ProxySession.Response.ResponseStatusCode, args.ProxySession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter); WriteResponseHeaders(args.Client.ClientStreamWriter, args.ProxySession.Response.ResponseHeaders); if (args.ProxySession.Response.IsChunked || args.ProxySession.Response.ContentLength > 0) { WriteResponseBody(args.ProxySession.ProxyClient.ServerStreamReader, args.Client.ClientStream, args.ProxySession.Response.IsChunked, args.ProxySession.Response.ContentLength); } } args.Client.ClientStream.Flush(); } catch { Dispose(args.Client.TcpClient, args.Client.ClientStream, args.Client.ClientStreamReader, args.Client.ClientStreamWriter, args); } finally { args.Dispose(); } }
//Called asynchronously when a request was successfully and we received the response public static async Task HandleHttpSessionResponse(SessionEventArgs args) { await args.WebSession.ReceiveResponse().ConfigureAwait(false); try { if (!args.WebSession.Response.ResponseBodyRead) { args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream; } if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) { Delegate[] invocationList = BeforeResponse.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).ConfigureAwait(false); } args.WebSession.Response.ResponseLocked = true; if (args.WebSession.Response.Is100Continue) { await WriteResponseStatus(args.WebSession.Response.HttpVersion, "100", "Continue", args.Client.ClientStreamWriter); await args.Client.ClientStreamWriter.WriteLineAsync(); } else if (args.WebSession.Response.ExpectationFailed) { await WriteResponseStatus(args.WebSession.Response.HttpVersion, "417", "Expectation Failed", args.Client.ClientStreamWriter); await args.Client.ClientStreamWriter.WriteLineAsync(); } await WriteResponseStatus(args.WebSession.Response.HttpVersion, args.WebSession.Response.ResponseStatusCode, args.WebSession.Response.ResponseStatusDescription, args.Client.ClientStreamWriter); if (args.WebSession.Response.ResponseBodyRead) { var isChunked = args.WebSession.Response.IsChunked; var contentEncoding = args.WebSession.Response.ContentEncoding; if (contentEncoding != null) { args.WebSession.Response.ResponseBody = await GetCompressedResponseBody(contentEncoding, args.WebSession.Response.ResponseBody).ConfigureAwait(false); if (isChunked == false) { args.WebSession.Response.ContentLength = args.WebSession.Response.ResponseBody.Length; } else { args.WebSession.Response.ContentLength = -1; } } await WriteResponseHeaders(args.Client.ClientStreamWriter, args.WebSession.Response.ResponseHeaders).ConfigureAwait(false); await WriteResponseBody(args.Client.ClientStream, args.WebSession.Response.ResponseBody, isChunked).ConfigureAwait(false); } else { await WriteResponseHeaders(args.Client.ClientStreamWriter, args.WebSession.Response.ResponseHeaders); if (args.WebSession.Response.IsChunked || args.WebSession.Response.ContentLength > 0 || (args.WebSession.Response.HttpVersion.Major == 1 && args.WebSession.Response.HttpVersion.Minor == 0)) { await WriteResponseBody(args.WebSession.ServerConnection.StreamReader, args.Client.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength).ConfigureAwait(false); } } await args.Client.ClientStream.FlushAsync(); } catch { Dispose(args.Client.TcpClient, args.Client.ClientStream, args.Client.ClientStreamReader, args.Client.ClientStreamWriter, args); } finally { args.Dispose(); } }
/// <summary> /// This is the core request handler method for a particular connection from client. /// Will create new session (request/response) sequence until /// client/server abruptly terminates connection or by normal HTTP termination. /// </summary> /// <param name="endPoint">The proxy endpoint.</param> /// <param name="clientConnection">The client connection.</param> /// <param name="clientStream">The client stream.</param> /// <param name="clientStreamWriter">The client stream writer.</param> /// <param name="cancellationTokenSource">The cancellation token source for this async task.</param> /// <param name="httpsConnectHostname"> /// The https hostname as appeared in CONNECT request if this is a HTTPS request from /// explicit endpoint. /// </param> /// <param name="connectRequest">The Connect request if this is a HTTPS request from explicit endpoint.</param> /// <param name="prefetchConnectionTask">Prefetched server connection for current client using Connect/SNI headers.</param> private async Task handleHttpSessionRequest(ProxyEndPoint endPoint, TcpClientConnection clientConnection, CustomBufferedStream clientStream, HttpResponseWriter clientStreamWriter, CancellationTokenSource cancellationTokenSource, string httpsConnectHostname, ConnectRequest connectRequest, Task <TcpServerConnection> prefetchConnectionTask = null) { var prefetchTask = prefetchConnectionTask; TcpServerConnection connection = null; bool closeServerConnection = false; try { var cancellationToken = cancellationTokenSource.Token; System.Diagnostics.Debug.WriteLine("{2}:HttpSessionRequestHandler: ReqURL: {0}{1}", connectRequest == null ? string.Empty : connectRequest.RequestUri.ToString(), string.Empty, System.Threading.Thread.CurrentThread.ManagedThreadId); // Loop through each subsequest request on this particular client connection // (assuming HTTP connection is kept alive by client) while (true) { // read the request line string httpCmd = await clientStream.ReadLineAsync(cancellationToken); if (string.IsNullOrEmpty(httpCmd)) { return; } var args = new SessionEventArgs(this, endPoint, cancellationTokenSource) { ProxyClient = { ClientConnection = clientConnection }, WebSession = { ConnectRequest = connectRequest } }; try { try { string httpMethod; string httpUrl; Version version; Request.ParseRequestLine(httpCmd, out httpMethod, out httpUrl, out version); // Read the request headers in to unique and non-unique header collections await HeaderParser.ReadHeaders(clientStream, args.WebSession.Request.Headers, cancellationToken); Uri httpRemoteUri; if (uriSchemeRegex.IsMatch(httpUrl)) { try { httpRemoteUri = new Uri(httpUrl); } catch (Exception ex) { throw new Exception($"Invalid URI: '{httpUrl}'", ex); } } else { string host = args.WebSession.Request.Host ?? httpsConnectHostname; string hostAndPath = host; if (httpUrl.StartsWith("/")) { hostAndPath += httpUrl; } string url = string.Concat(httpsConnectHostname == null ? "http://" : "https://", hostAndPath); try { httpRemoteUri = new Uri(url); } catch (Exception ex) { throw new Exception($"Invalid URI: '{url}'", ex); } } var request = args.WebSession.Request; request.RequestUri = httpRemoteUri; request.OriginalUrl = httpUrl; request.Method = httpMethod; request.HttpVersion = version; args.ProxyClient.ClientStream = clientStream; args.ProxyClient.ClientStreamWriter = clientStreamWriter; if (!args.IsTransparent) { // proxy authorization check if (httpsConnectHostname == null && await checkAuthorization(args) == false) { await invokeBeforeResponse(args); // send the response await clientStreamWriter.WriteResponseAsync(args.WebSession.Response, cancellationToken : cancellationToken); return; } prepareRequestHeaders(request.Headers); request.Host = request.RequestUri.Authority; } // if win auth is enabled // we need a cache of request body // so that we can send it after authentication in WinAuthHandler.cs if (isWindowsAuthenticationEnabledAndSupported && request.HasBody) { await args.GetRequestBody(cancellationToken); } //we need this to syphon out data from connection if API user changes them. request.SetOriginalHeaders(); // If user requested interception do it await invokeBeforeRequest(args); var response = args.WebSession.Response; if (request.CancelRequest) { // syphon out the request body from client before setting the new body await args.SyphonOutBodyAsync(true, cancellationToken); await handleHttpSessionResponse(args); if (!response.KeepAlive) { return; } continue; } //If prefetch task is available. if (connection == null && prefetchTask != null) { connection = await prefetchTask; prefetchTask = null; } // create a new connection if cache key changes. // only gets hit when connection pool is disabled. // or when prefetch task has a unexpectedly different connection. if (connection != null && (await tcpConnectionFactory.GetConnectionCacheKey(this, args, clientConnection.NegotiatedApplicationProtocol) != connection.CacheKey)) { await tcpConnectionFactory.Release(connection); connection = null; } //a connection generator task with captured parameters via closure. Func <Task <TcpServerConnection> > generator = () => tcpConnectionFactory.GetServerConnection(this, args, isConnect: false, applicationProtocol: clientConnection.NegotiatedApplicationProtocol, noCache: false, cancellationToken: cancellationToken); //for connection pool, retry fails until cache is exhausted. var result = await retryPolicy <ServerConnectionException>().ExecuteAsync(async(serverConnection) => { // if upgrading to websocket then relay the request without reading the contents if (request.UpgradeToWebSocket) { await handleWebSocketUpgrade(httpCmd, args, request, response, clientStream, clientStreamWriter, serverConnection, cancellationTokenSource, cancellationToken); closeServerConnection = true; return(false); } // construct the web request that we are going to issue on behalf of the client. await handleHttpSessionRequestInternal(serverConnection, args); return(true); }, generator, connection); //update connection to latest used connection = result.LatestConnection; //throw if exception happened if (!result.IsSuccess) { throw result.Exception; } if (!result.Continue) { return; } //user requested if (args.WebSession.CloseServerConnection) { closeServerConnection = true; return; } // if connection is closing exit if (!response.KeepAlive) { closeServerConnection = true; return; } if (cancellationTokenSource.IsCancellationRequested) { throw new Exception("Session was terminated by user."); } //Get/release server connection for each HTTP session instead of per client connection. //This will be more efficient especially when client is idly holding server connection //between sessions without using it. //Do not release authenticated connections for performance reasons. //Otherwise it will keep authenticating per session. if (EnableConnectionPool && connection != null && !connection.IsWinAuthenticated) { await tcpConnectionFactory.Release(connection); connection = null; } } catch (Exception e) when(!(e is ProxyHttpException)) { throw new ProxyHttpException("Error occured whilst handling session request", e, args); } } catch (Exception e) { args.Exception = e; closeServerConnection = true; throw; } finally { await invokeAfterResponse(args); args.Dispose(); } } } finally { await tcpConnectionFactory.Release(connection, closeServerConnection); await tcpConnectionFactory.Release(prefetchTask, closeServerConnection); } }