/// <summary> /// Converts <see cref="HttpResponseMessage"/> to <see cref="RelayResponse"/>. /// </summary> /// <param name="httpResponseMessage">Response message.</param> /// <returns>Relay response.</returns> private async Task <RelayResponse> ToRelayResponseAsync(HttpResponseMessage httpResponseMessage) { RelayResponse relayResponse = new RelayResponse { HttpStatusCode = httpResponseMessage.StatusCode, StatusDescription = httpResponseMessage.ReasonPhrase, Headers = new WebHeaderCollection(), RequestEndDateTime = DateTimeOffset.Now, }; // Copy the response headers. foreach (KeyValuePair <string, IEnumerable <string> > responseHeader in httpResponseMessage.Headers) { relayResponse.Headers.Add(responseHeader.Key, string.Join(",", responseHeader.Value)); } if (httpResponseMessage.Content != null) { foreach (KeyValuePair <string, IEnumerable <string> > responseHeader in httpResponseMessage.Content.Headers) { relayResponse.Headers.Add(responseHeader.Key, string.Join(",", responseHeader.Value)); } relayResponse.OutputStream = await httpResponseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false); } return(relayResponse); }
/// <summary> /// Handles the HTTP request received over the hybrid connection. /// </summary> /// <param name="context">Relayed request context.</param> /// <returns>Task tracking operation.</returns> private async Task HandleProxyRequestAsync(RelayedHttpListenerContext context) { RelayRequest relayRequest = new RelayRequest { Headers = context.Request.Headers, HttpMethod = new HttpMethod(context.Request.HttpMethod), InputStream = context.Request.InputStream, RequestPathAndQuery = context.Request.Url.AbsoluteUri.Substring(this.relayUrl.Length), RequestStartDateTime = DateTimeOffset.Now, }; try { RelayResponse relayResponse = await this.relayManager.HandleRelayRequestAsync(relayRequest).ConfigureAwait(false); context.Response.StatusCode = relayResponse.HttpStatusCode; context.Response.StatusDescription = relayResponse.StatusDescription; // Copy over outgoing headers. foreach (string headerName in relayResponse.Headers.Keys) { // Not copying over the Transfer-Encoding header as the communication between server and Relay, and Relay and client are // different connections. if (headerName.Equals("Transfer-Encoding", StringComparison.OrdinalIgnoreCase)) { continue; } context.Response.Headers[headerName] = relayResponse.Headers[headerName]; } if (relayResponse.OutputStream != null) { await relayResponse.OutputStream.CopyToAsync(context.Response.OutputStream).ConfigureAwait(false); } await context.Response.CloseAsync().ConfigureAwait(false); } catch (Exception ex) { context.Response.StatusCode = HttpStatusCode.InternalServerError; context.Response.StatusDescription = "Exception occurred at Server"; byte[] errorMessage = Encoding.UTF8.GetBytes(ex.Message); await context.Response.OutputStream.WriteAsync(errorMessage, 0, errorMessage.Length).ConfigureAwait(false); } }
/// <summary> /// Handles the incoming request, passes it down to the internal service and returns the response. /// </summary> /// <param name="relayRequest">Incoming relay request.</param> /// <returns>Response from the internal service.</returns> public async Task <RelayResponse> HandleRelayRequestAsync(RelayRequest relayRequest) { if (relayRequest is null) { throw new ArgumentNullException(nameof(relayRequest)); } string requestId = Guid.NewGuid().ToString(); this.logger.LogTrace("Received request with Id '{0}'", requestId); // Inform listener that a request has been received. if (this.relayRequestEventListener != null) { RelayRequest clonedRequest = relayRequest.Clone() as RelayRequest; #pragma warning disable CS4014 // We want eventing pipeline to run in parallel and not block the actual request execution. Task.Run(async() => { try { await this.relayRequestEventListener.RequestReceivedAsync(requestId, clonedRequest).ConfigureAwait(false); } catch (Exception ex) { // Ignoring exceptions from listeners. this.logger.LogWarning("Relay request event listener failed for request Id '{0}' during 'RequestReceivedAsync' phase with exception '{1}'. Ignoring.", requestId, ex); } }); #pragma warning restore CS4014 // We want eventing pipeline to run in parallel and not block the actual request execution. } HttpRequestMessage httpRequestMessage = null; try { httpRequestMessage = await this.ToHttpRequestMessageAsync(relayRequest, requestId).ConfigureAwait(false); } catch (Exception ex) { this.logger.LogError("CRITICAL ERROR!!! Failed to convert Relay request into outgoing request. Error - '{0}'. Request Id - '{1}'", ex, requestId); throw; } // Pass the request through all the plugins. foreach (ITunnelRelayPlugin tunnelRelayPlugin in this.tunnelRelayPlugins) { try { httpRequestMessage = await tunnelRelayPlugin.PreProcessRequestToServiceAsync(httpRequestMessage).ConfigureAwait(false); } catch (Exception ex) { this.logger.LogError("CRITICAL ERROR!!! Plugin '{0}' failed with error - '{1}' for request Id '{2}' during 'PreProcessRequestToServiceAsync' phase", tunnelRelayPlugin.PluginName, ex, requestId); throw; } } HttpResponseMessage httpResponseMessage; try { this.logger.LogTrace("Making external request to server for request Id '{0}'", requestId); httpResponseMessage = await this.httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false); this.logger.LogTrace("Received response from server for request Id '{0}'", requestId); } catch (HttpRequestException httpException) { this.logger.LogError("Hit exception while sending request to server for request Id '{0}'. Error '{1}'", requestId, httpException); httpResponseMessage = new HttpResponseMessage(HttpStatusCode.BadGateway) { Content = new StringContent(httpException.ToString()), }; } catch (Exception ex) { this.logger.LogError("Hit critical exception while processing request '{0}'. Error '{1}'", requestId, ex); throw; } // Pass the response through all the plugins. foreach (ITunnelRelayPlugin tunnelRelayPlugin in this.tunnelRelayPlugins) { try { httpResponseMessage = await tunnelRelayPlugin.PostProcessResponseFromServiceAsync(httpResponseMessage).ConfigureAwait(false); } catch (Exception ex) { this.logger.LogError("CRITICAL ERROR!!! Plugin '{0}' failed with error - '{1}' for request Id '{2}' during 'PostProcessResponseFromServiceAsync' phase", tunnelRelayPlugin.PluginName, ex, requestId); throw; } } RelayResponse relayResponse = null; try { relayResponse = await this.ToRelayResponseAsync(httpResponseMessage).ConfigureAwait(false); } catch (Exception ex) { this.logger.LogError("Failed to convert outgoing response into relay response for request '{0}'. Error '{1}'", requestId, ex); } if (this.relayRequestEventListener != null) { RelayResponse clonedResponse = relayResponse.Clone() as RelayResponse; #pragma warning disable CS4014 // We want eventing pipeline to run in parallel and not slowdown the actual request execution. Task.Run(async() => { try { await this.relayRequestEventListener.ResponseSentAsync(requestId, clonedResponse).ConfigureAwait(false); } catch (Exception ex) { // Ignoring exceptions from listeners. this.logger.LogWarning("Relay request event listener failed for request Id '{0}' during 'ResponseSentAsync' phase with exception '{1}'. Ignoring.", requestId, ex); } }); #pragma warning restore CS4014 // We want eventing pipeline to run in parallel and not slowdown the actual request execution. } return(relayResponse); }