/// <summary> /// default init /// </summary> public ProxyMiddleware() { _defaultOptions = new InternalProxyOptions() { SendChunked = false }; // if port is not specified default one is taken if (!_defaultOptions.Port.HasValue) { if (string.Equals(_defaultOptions.Scheme, "https", StringComparison.OrdinalIgnoreCase)) { _defaultOptions.Port = 443; } else { _defaultOptions.Port = 80; } } //if no scheme is choosen, http is taken if (string.IsNullOrEmpty(_defaultOptions.Scheme)) { _defaultOptions.Scheme = "http"; } _httpClient = new HttpClient(_defaultOptions.BackChannelMessageHandler ?? new HttpClientHandler()); }
/// <summary> /// Handle also Web socket, calling pump method inside /// </summary> /// <param name="context"></param> /// <param name="_options"></param> /// <param name="destination"></param> /// <param name="host"></param> /// <param name="port"></param> /// <param name="scheme"></param> /// <returns></returns> private async Task HandleWebSocketRequest(HttpContext context, InternalProxyOptions _options, Node destination, string host, int port, string scheme) { using (var client = new ClientWebSocket()) { foreach (var headerEntry in context.Request.Headers) { if (!NotForwardedWebSocketHeaders.Contains(headerEntry.Key, StringComparer.OrdinalIgnoreCase)) { client.Options.SetRequestHeader(headerEntry.Key, headerEntry.Value); } } var wsScheme = string.Equals(destination.Scheme, "https", StringComparison.OrdinalIgnoreCase) ? "wss" : "ws"; string url = GetUri(context, host, port, scheme); if (_options.WebSocketKeepAliveInterval.HasValue) { client.Options.KeepAliveInterval = _options.WebSocketKeepAliveInterval.Value; } try { await client.ConnectAsync(new Uri(url), context.RequestAborted); } catch (WebSocketException) { context.Response.StatusCode = 400; return; } using (var server = await context.WebSockets.AcceptWebSocketAsync(client.SubProtocol)) { await Task.WhenAll(PumpWebSocket(context, client, server, _options, context.RequestAborted), PumpWebSocket(context, server, client, _options, context.RequestAborted)); } } }
/// <summary> /// Handle a simple http request dumping remote content to the client /// </summary> /// <param name="context"></param> /// <param name="_options"></param> /// <param name="destination"></param> /// <param name="host"></param> /// <param name="port"></param> /// <param name="scheme"></param> /// <returns></returns> private async Task HandleHttpRequest(HttpContext context, InternalProxyOptions _options, Node destination, string host, int port, string scheme) { var requestMessage = new HttpRequestMessage(); var requestMethod = context.Request.Method; if (!HttpMethods.IsGet(requestMethod) && !HttpMethods.IsHead(requestMethod) && !HttpMethods.IsDelete(requestMethod) && !HttpMethods.IsTrace(requestMethod)) { var streamContent = new StreamContent(context.Request.Body); requestMessage.Content = streamContent; } // All request headers and cookies must be transferend to remote server. Some headers will be skipped foreach (var header in context.Request.Headers) { if (!requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()) && requestMessage.Content != null) { requestMessage.Content?.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); } } requestMessage.Headers.Host = host; //recreate remote url string uriString = GetUri(context, host, port, scheme); requestMessage.RequestUri = new Uri(uriString); requestMessage.Method = new HttpMethod(context.Request.Method); using (var responseMessage = await _httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead, context.RequestAborted)) { context.Response.StatusCode = (int)responseMessage.StatusCode; foreach (var header in responseMessage.Headers) { context.Response.Headers[header.Key] = header.Value.ToArray(); } foreach (var header in responseMessage.Content.Headers) { context.Response.Headers[header.Key] = header.Value.ToArray(); } if (!_options.SendChunked) { //tell to the browser that response is not chunked context.Response.Headers.Remove("transfer-encoding"); await responseMessage.Content.CopyToAsync(context.Response.Body); } else { var buffer = new byte[_options.BufferSize ?? DefaultBufferSize]; using (var responseStream = await responseMessage.Content.ReadAsStreamAsync()) { //long pos = responseStream.Position; //if (pos > 0) //{ // responseStream.Seek(0, SeekOrigin.Begin); //} //context.Response.Body = new MemoryStream(); int len = 0; int full = 0; while ((len = await responseStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { await context.Response.Body.WriteAsync(buffer, 0, buffer.Length); // await context.Response.Body.FlushAsync(); full += buffer.Length; } // context.Response.ContentLength = full; context.Response.Headers.Remove("transfer-encoding"); } } } }
/// <summary> /// Core pump method /// </summary> /// <param name="context"></param> /// <param name="source"></param> /// <param name="destination"></param> /// <param name="_options"></param> /// <param name="cancellationToken"></param> /// <returns></returns> private async Task PumpWebSocket(HttpContext context, WebSocket source, WebSocket destination, InternalProxyOptions _options, CancellationToken cancellationToken) { var buffer = new byte[_options.BufferSize ?? DefaultBufferSize]; while (true) { WebSocketReceiveResult result; try { result = await source.ReceiveAsync(new ArraySegment <byte>(buffer), cancellationToken); } catch (OperationCanceledException) { await destination.CloseOutputAsync(WebSocketCloseStatus.EndpointUnavailable, null, cancellationToken); return; } if (result.MessageType == WebSocketMessageType.Close) { await destination.CloseOutputAsync(source.CloseStatus.Value, source.CloseStatusDescription, cancellationToken); return; } await destination.SendAsync(new ArraySegment <byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, cancellationToken); } }