/// <summary> /// Invoke before response if it is set. /// </summary> /// <param name="args"></param> /// <returns></returns> protected async Task onBeforeResponse(SessionEventArgs args) { if (BeforeResponse != null) { await BeforeResponse.InvokeAsync(this, args, ExceptionFunc); } }
/// <summary> /// Invoke before response if it is set. /// </summary> /// <param name="args"></param> /// <returns></returns> private async Task invokeBeforeResponse(SessionEventArgs args) { if (BeforeResponse != null) { await BeforeResponse.InvokeAsync(this, args, ExceptionFunc); } }
/// <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(); } }
/// <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 (BeforeResponse != null && !response.ResponseLocked) { await BeforeResponse.InvokeAsync(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(); 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); } }