private async Task handleHttpSessionRequest(TcpServerConnection connection, SessionEventArgs args) { var cancellationToken = args.CancellationTokenSource.Token; var request = args.HttpClient.Request; request.Locked = true; var body = request.CompressBodyAndUpdateContentLength(); // set the connection and send request headers args.HttpClient.SetConnection(connection); await args.HttpClient.SendRequest(Enable100ContinueBehaviour, args.IsTransparent, cancellationToken); // If a successful 100 continue request was made, inform that to the client and reset response if (request.ExpectationSucceeded) { var clientStreamWriter = args.ProxyClient.ClientStreamWriter; var response = args.HttpClient.Response; var headerBuilder = new HeaderBuilder(); headerBuilder.WriteResponseLine(response.HttpVersion, response.StatusCode, response.StatusDescription); headerBuilder.WriteHeaders(response.Headers); await clientStreamWriter.WriteHeadersAsync(headerBuilder, cancellationToken); await args.ClearResponse(cancellationToken); } // send body to server if available if (request.HasBody) { if (request.IsBodyRead) { var writer = args.HttpClient.Connection.StreamWriter; await writer.WriteBodyAsync(body !, request.IsChunked, cancellationToken); } else if (!request.ExpectationFailed) { // get the request body unless an unsuccessful 100 continue request was made HttpWriter writer = args.HttpClient.Connection.StreamWriter !; await args.CopyRequestBodyAsync(writer, TransformationMode.None, cancellationToken); } } args.TimeLine["Request Sent"] = DateTime.Now; // parse and send response await handleHttpSessionResponse(args); }
/// <summary> /// Called asynchronously when a request was successful and we received the response. /// </summary> /// <param name="args">The session event arguments.</param> /// <returns> The task.</returns> protected async Task handleHttpSessionResponse(SessionEventArgs args) { var cancellationToken = args.CancellationTokenSource.Token; // read response & headers from server await args.HttpClient.ReceiveResponse(cancellationToken); // Server may send expect-continue even if not asked for it in request. // According to spec "the client can simply discard this interim response." if (args.HttpClient.Response.StatusCode == (int)HttpStatusCode.Continue) { await args.ClearResponse(cancellationToken); await args.HttpClient.ReceiveResponse(cancellationToken); } args.TimeLine["Response Received"] = DateTime.Now; var response = args.HttpClient.Response; args.ReRequest = false; // check for windows authentication if (args.EnableWinAuth) { if (response.StatusCode == (int)HttpStatusCode.Unauthorized) { await handle401UnAuthorized(args); } else { WinAuthEndPoint.AuthenticatedResponse(args.HttpClient.Data); } } // save original values so that if user changes them // we can still use original values when syphoning out data from attached tcp connection. response.SetOriginalHeaders(); // if user requested call back then do it if (!response.Locked) { await onBeforeResponse(args); } // it may changed in the user event response = args.HttpClient.Response; var clientStream = args.ClientStream; // user set custom response by ignoring original response from server. if (response.Locked) { // write custom user response with body and return. await clientStream.WriteResponseAsync(response, cancellationToken); if (args.HttpClient.HasConnection && !args.HttpClient.CloseServerConnection) { // syphon out the original response body from server connection // so that connection will be good to be reused. await args.SyphonOutBodyAsync(false, cancellationToken); } return; } // if user requested to send request again // likely after making modifications from User Response Handler if (args.ReRequest) { if (args.HttpClient.HasConnection) { await tcpConnectionFactory.Release(args.HttpClient.Connection); } // clear current response await args.ClearResponse(cancellationToken); await handleHttpSessionRequest(args, null, args.ClientConnection.NegotiatedApplicationProtocol, cancellationToken, args.CancellationTokenSource); return; } response.Locked = true; if (!args.IsTransparent) { response.Headers.FixProxyHeaders(); } await clientStream.WriteResponseAsync(response, cancellationToken); if (response.OriginalHasBody) { if (response.IsBodySent) { // syphon out body await args.SyphonOutBodyAsync(false, cancellationToken); } else { // Copy body if exists var serverStream = args.HttpClient.Connection.Stream; await serverStream.CopyBodyAsync(response, false, clientStream, TransformationMode.None, args.OnDataReceived, cancellationToken); } } args.TimeLine["Response Sent"] = DateTime.Now; }
/// <summary> /// Called asynchronously when a request was successfull and we received the response. /// </summary> /// <param name="args">The session event arguments.</param> /// <returns> The task.</returns> private async Task handleHttpSessionResponse(SessionEventArgs args) { var cancellationToken = args.CancellationTokenSource.Token; // read response & headers from server await args.WebSession.ReceiveResponse(cancellationToken); var response = args.WebSession.Response; args.ReRequest = false; // check for windows authentication if (isWindowsAuthenticationEnabledAndSupported) { if (response.StatusCode == (int)HttpStatusCode.Unauthorized) { await handle401UnAuthorized(args); } else { WinAuthEndPoint.AuthenticatedResponse(args.WebSession.Data); } } //save original values so that if user changes them //we can still use original values when syphoning out data from attached tcp connection. response.SetOriginalHeaders(); // if user requested call back then do it if (!response.Locked) { await invokeBeforeResponse(args); } // it may changed in the user event response = args.WebSession.Response; var clientStreamWriter = args.ProxyClient.ClientStreamWriter; //user set custom response by ignoring original response from server. if (response.Locked) { //write custom user response with body and return. await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); if (args.WebSession.ServerConnection != null && !args.WebSession.CloseServerConnection) { // syphon out the original response body from server connection // so that connection will be good to be reused. await args.SyphonOutBodyAsync(false, cancellationToken); } return; } // if user requested to send request again // likely after making modifications from User Response Handler if (args.ReRequest) { // clear current response await args.ClearResponse(cancellationToken); await handleHttpSessionRequestInternal(args.WebSession.ServerConnection, args); return; } response.Locked = true; // Write back to client 100-conitinue response if that's what server returned if (response.Is100Continue) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.Continue, "Continue", cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken); } else if (response.ExpectationFailed) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken); } if (!args.IsTransparent) { response.Headers.FixProxyHeaders(); } if (response.IsBodyRead) { await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); } else { // Write back response status to client await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, response.StatusDescription, cancellationToken); await clientStreamWriter.WriteHeadersAsync(response.Headers, cancellationToken : cancellationToken); // Write body if exists if (response.HasBody) { await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None, cancellationToken); } } }
/// <summary> /// Handle windows NTLM authentication /// Can expand this for Kerberos in future /// Note: NTLM/Kerberos cannot do a man in middle operation /// we do for HTTPS requests. /// As such we will be sending local credentials of current /// User to server to authenticate requests. /// To disable this set ProxyServer.EnableWinAuth to false /// </summary> internal async Task <bool> Handle401UnAuthorized(SessionEventArgs args) { string headerName = null; HttpHeader authHeader = null; //check in non-unique headers first var header = args.WebSession.Response.ResponseHeaders.NonUniqueHeaders.FirstOrDefault( x => authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase))); if (!header.Equals(new KeyValuePair <string, List <HttpHeader> >())) { headerName = header.Key; } if (headerName != null) { authHeader = args.WebSession.Response.ResponseHeaders.NonUniqueHeaders[headerName] .FirstOrDefault(x => authSchemes.Any(y => x.Value.StartsWith(y, StringComparison.OrdinalIgnoreCase))); } //check in unique headers if (authHeader == null) { //check in non-unique headers first var uHeader = args.WebSession.Response.ResponseHeaders.Headers.FirstOrDefault(x => authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase))); if (!uHeader.Equals(new KeyValuePair <string, HttpHeader>())) { headerName = uHeader.Key; } if (headerName != null) { authHeader = authSchemes.Any(x => args.WebSession.Response.ResponseHeaders.Headers[headerName].Value .StartsWith(x, StringComparison.OrdinalIgnoreCase)) ? args.WebSession.Response.ResponseHeaders.Headers[headerName] : null; } } if (authHeader != null) { string scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase)); //clear any existing headers to avoid confusing bad servers if (args.WebSession.Request.RequestHeaders.NonUniqueHeaders.ContainsKey("Authorization")) { args.WebSession.Request.RequestHeaders.NonUniqueHeaders.Remove("Authorization"); } //initial value will match exactly any of the schemes if (scheme != null) { string clientToken = WinAuthHandler.GetInitialAuthToken(args.WebSession.Request.Host, scheme, args.Id); var auth = new HttpHeader("Authorization", string.Concat(scheme, clientToken)); //replace existing authorization header if any if (args.WebSession.Request.RequestHeaders.Headers.ContainsKey("Authorization")) { args.WebSession.Request.RequestHeaders.Headers["Authorization"] = auth; } else { args.WebSession.Request.RequestHeaders.Headers.Add("Authorization", auth); } //don't need to send body for Authorization request if (args.WebSession.Request.HasBody) { args.WebSession.Request.ContentLength = 0; } } //challenge value will start with any of the scheme selected else { scheme = authSchemes.FirstOrDefault(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) && authHeader.Value.Length > x.Length + 1); string serverToken = authHeader.Value.Substring(scheme.Length + 1); string clientToken = WinAuthHandler.GetFinalAuthToken(args.WebSession.Request.Host, serverToken, args.Id); //there will be an existing header from initial client request args.WebSession.Request.RequestHeaders.Headers["Authorization"] = new HttpHeader("Authorization", string.Concat(scheme, clientToken)); //send body for final auth request if (args.WebSession.Request.HasBody) { args.WebSession.Request.ContentLength = args.WebSession.Request.RequestBody.Length; } } //Need to revisit this. //Should we cache all Set-Cokiee headers from server during auth process //and send it to client after auth? //clear current server response await args.ClearResponse(); //request again with updated authorization header //and server cookies bool disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false); return(disposed); } return(false); }
/// <summary> /// Called asynchronously when a request was successfully and we received the response /// </summary> /// <param name="args"></param> /// <returns>true if client/server connection was terminated (and disposed) </returns> private async Task <bool> HandleHttpSessionResponse(SessionEventArgs args) { try { //read response & headers from server await args.WebSession.ReceiveResponse(); var response = args.WebSession.Response; #if NET45 //check for windows authentication if (EnableWinAuth && !RunTime.IsRunningOnMono && response.ResponseStatusCode == (int)HttpStatusCode.Unauthorized) { bool disposed = await Handle401UnAuthorized(args); if (disposed) { return(true); } } #endif args.ReRequest = false; //If user requested call back then do it if (BeforeResponse != null && !response.ResponseLocked) { await BeforeResponse.InvokeParallelAsync(this, args, ExceptionFunc); } //if user requested to send request again //likely after making modifications from User Response Handler if (args.ReRequest) { //clear current response await args.ClearResponse(); bool disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false); return(disposed); } response.ResponseLocked = true; var clientStreamWriter = args.ProxyClient.ClientStreamWriter; //Write back to client 100-conitinue response if that's what server returned if (response.Is100Continue) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.Continue, "Continue"); await clientStreamWriter.WriteLineAsync(); } else if (response.ExpectationFailed) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed"); await clientStreamWriter.WriteLineAsync(); } //Write back response status to client await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.ResponseStatusCode, response.ResponseStatusDescription); response.ResponseHeaders.FixProxyHeaders(); if (response.ResponseBodyRead) { bool isChunked = response.IsChunked; string contentEncoding = response.ContentEncoding; if (contentEncoding != null) { response.ResponseBody = await GetCompressedResponseBody(contentEncoding, response.ResponseBody); if (isChunked == false) { response.ContentLength = response.ResponseBody.Length; } else { response.ContentLength = -1; } } await clientStreamWriter.WriteHeadersAsync(response.ResponseHeaders); await clientStreamWriter.WriteResponseBodyAsync(response.ResponseBody, isChunked); } else { await clientStreamWriter.WriteHeadersAsync(response.ResponseHeaders); //Write body if exists if (response.HasBody) { await clientStreamWriter.WriteResponseBodyAsync(BufferSize, args.WebSession.ServerConnection.StreamReader, response.IsChunked, response.ContentLength); } } await clientStreamWriter.FlushAsync(); } catch (Exception e) { ExceptionFunc(new ProxyHttpException("Error occured whilst handling session response", e, args)); Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection); return(true); } return(false); }
/// <summary> /// Handle windows NTLM authentication /// Can expand this for Kerberos in future /// Note: NTLM/Kerberos cannot do a man in middle operation /// we do for HTTPS requests. /// As such we will be sending local credentials of current /// User to server to authenticate requests. /// To disable this set ProxyServer.EnableWinAuth to false /// </summary> internal async Task <bool> Handle401UnAuthorized(SessionEventArgs args) { string headerName = null; HttpHeader authHeader = null; //check in non-unique headers first var header = args.WebSession.Response .NonUniqueResponseHeaders .FirstOrDefault(x => authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase))); if (!header.Equals(new KeyValuePair <string, List <HttpHeader> >())) { headerName = header.Key; } if (headerName != null) { authHeader = args.WebSession.Response .NonUniqueResponseHeaders[headerName] .Where(x => authSchemes.Any(y => x.Value.StartsWith(y, StringComparison.OrdinalIgnoreCase))) .FirstOrDefault(); } //check in unique headers if (authHeader == null) { //check in non-unique headers first var uHeader = args.WebSession.Response .ResponseHeaders .FirstOrDefault(x => authHeaderNames.Any(y => x.Key.Equals(y, StringComparison.OrdinalIgnoreCase))); if (!uHeader.Equals(new KeyValuePair <string, HttpHeader>())) { headerName = uHeader.Key; } if (headerName != null) { authHeader = authSchemes.Any(x => args.WebSession.Response .ResponseHeaders[headerName].Value.StartsWith(x, StringComparison.OrdinalIgnoreCase)) ? args.WebSession.Response.ResponseHeaders[headerName] : null; } } if (authHeader != null) { var scheme = authSchemes.FirstOrDefault(x => authHeader.Value.Equals(x, StringComparison.OrdinalIgnoreCase)); //initial value will match exactly any of the schemes if (scheme != null) { var clientToken = WinAuthHandler.GetInitialAuthToken(args.WebSession.Request.Host, scheme, args.Id); args.WebSession.Request.RequestHeaders.Add("Authorization", new HttpHeader("Authorization", string.Concat(scheme, clientToken))); } //challenge value will start with any of the scheme selected else { scheme = authSchemes.FirstOrDefault(x => authHeader.Value.StartsWith(x, StringComparison.OrdinalIgnoreCase) && authHeader.Value.Length > x.Length + 1); var serverToken = authHeader.Value.Substring(scheme.Length + 1); var clientToken = WinAuthHandler.GetFinalAuthToken(args.WebSession.Request.Host, serverToken, args.Id); args.WebSession.Request.RequestHeaders["Authorization"] = new HttpHeader("Authorization", string.Concat(scheme, clientToken)); } //clear current response await args.ClearResponse(); var disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false); return(disposed); } return(false); }
/// <summary> /// Called asynchronously when a request was successfully and we received the response /// </summary> /// <param name="args"></param> /// <returns>true if client/server connection was terminated (and disposed) </returns> private async Task HandleHttpSessionResponse(SessionEventArgs args) { try { //read response & headers from server await args.WebSession.ReceiveResponse(); var response = args.WebSession.Response; //check for windows authentication if (isWindowsAuthenticationEnabledAndSupported && response.StatusCode == (int)HttpStatusCode.Unauthorized) { await Handle401UnAuthorized(args); } args.ReRequest = false; //if user requested call back then do it if (!response.ResponseLocked) { await InvokeBeforeResponse(args); } //if user requested to send request again //likely after making modifications from User Response Handler if (args.ReRequest) { //clear current response await args.ClearResponse(); await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args); return; } response.ResponseLocked = true; var clientStreamWriter = args.ProxyClient.ClientStreamWriter; //Write back to client 100-conitinue response if that's what server returned if (response.Is100Continue) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.Continue, "Continue"); await clientStreamWriter.WriteLineAsync(); } else if (response.ExpectationFailed) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed"); await clientStreamWriter.WriteLineAsync(); } //Write back response status to client await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, response.StatusDescription); if (!args.IsTransparent) { response.Headers.FixProxyHeaders(); } if (response.IsBodyRead) { bool isChunked = response.IsChunked; string contentEncoding = response.ContentEncoding; if (contentEncoding != null) { response.Body = await GetCompressedResponseBody(contentEncoding, response.Body); if (isChunked == false) { response.ContentLength = response.Body.Length; } else { response.ContentLength = -1; } } await clientStreamWriter.WriteHeadersAsync(response.Headers); await clientStreamWriter.WriteBodyAsync(response.Body, isChunked); } else { await clientStreamWriter.WriteHeadersAsync(response.Headers); //Write body if exists if (response.HasBody) { await args.CopyResponseBodyAsync(clientStreamWriter, false); } } } catch (Exception e) when(!(e is ProxyHttpException)) { throw new ProxyHttpException("Error occured whilst handling session response", e, args); } }
/// <summary> /// Called asynchronously when a request was successfully and we received the response /// </summary> /// <param name="args"></param> /// <returns>true if client/server connection was terminated (and disposed) </returns> private async Task <bool> HandleHttpSessionResponse(SessionEventArgs args) { try { //read response & headers from server await args.WebSession.ReceiveResponse(); if (!args.WebSession.Response.ResponseBodyRead) { args.WebSession.Response.ResponseStream = args.WebSession.ServerConnection.Stream; } //check for windows authentication if (EnableWinAuth && !RunTime.IsRunningOnMono && args.WebSession.Response.ResponseStatusCode == "401") { var disposed = await Handle401UnAuthorized(args); if (disposed) { return(true); } } args.ReRequest = false; //If user requested call back then do it if (BeforeResponse != null && !args.WebSession.Response.ResponseLocked) { await BeforeResponse.InvokeParallelAsync(this, args); } //if user requested to send request again //likely after making modifications from User Response Handler if (args.ReRequest) { //clear current response await args.ClearResponse(); var disposed = await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args, false); return(disposed); } 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(BufferSize, 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(BufferSize, args.ProxyClient.ClientStream, args.WebSession.Response.IsChunked, args.WebSession.Response.ContentLength); } } await args.ProxyClient.ClientStream.FlushAsync(); } catch (Exception e) { ExceptionFunc(new ProxyHttpException("Error occured whilst handling session response", e, args)); Dispose(args.ProxyClient.ClientStream, args.ProxyClient.ClientStreamReader, args.ProxyClient.ClientStreamWriter, args.WebSession.ServerConnection); return(true); } return(false); }
/// <summary> /// Called asynchronously when a request was successfully and we received the response. /// </summary> /// <param name="args">The session event arguments.</param> /// <returns> The task.</returns> private async Task HandleHttpSessionResponse(SessionEventArgs args) { try { var cancellationToken = args.CancellationTokenSource.Token; // read response & headers from server await args.WebSession.ReceiveResponse(cancellationToken); var response = args.WebSession.Response; args.ReRequest = false; // check for windows authentication if (isWindowsAuthenticationEnabledAndSupported) { if (response.StatusCode == (int)HttpStatusCode.Unauthorized) { await Handle401UnAuthorized(args); } else { WinAuthEndPoint.AuthenticatedResponse(args.WebSession.Data); } } response.OriginalHasBody = response.HasBody; // if user requested call back then do it if (!response.Locked) { await InvokeBeforeResponse(args); } // it may changed in the user event response = args.WebSession.Response; var clientStreamWriter = args.ProxyClient.ClientStreamWriter; if (response.TerminateResponse || response.Locked) { await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); if (!response.TerminateResponse) { // syphon out the response body from server before setting the new body await args.SyphonOutBodyAsync(false, cancellationToken); } else { args.WebSession.ServerConnection.Dispose(); args.WebSession.ServerConnection = null; } return; } // if user requested to send request again // likely after making modifications from User Response Handler if (args.ReRequest) { // clear current response await args.ClearResponse(cancellationToken); await HandleHttpSessionRequestInternal(args.WebSession.ServerConnection, args); return; } response.Locked = true; // Write back to client 100-conitinue response if that's what server returned if (response.Is100Continue) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.Continue, "Continue", cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken); } else if (response.ExpectationFailed) { await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, (int)HttpStatusCode.ExpectationFailed, "Expectation Failed", cancellationToken); await clientStreamWriter.WriteLineAsync(cancellationToken); } if (!args.IsTransparent) { response.Headers.FixProxyHeaders(); } if (response.IsBodyRead) { await clientStreamWriter.WriteResponseAsync(response, cancellationToken : cancellationToken); } else { // Write back response status to client await clientStreamWriter.WriteResponseStatusAsync(response.HttpVersion, response.StatusCode, response.StatusDescription, cancellationToken); await clientStreamWriter.WriteHeadersAsync(response.Headers, cancellationToken : cancellationToken); // Write body if exists if (response.HasBody) { await args.CopyResponseBodyAsync(clientStreamWriter, TransformationMode.None, cancellationToken); } } } catch (Exception e) when(!(e is ProxyHttpException)) { throw new ProxyHttpException("Error occured whilst handling session response", e, args); } }