Beispiel #1
0
        private async Task HandleRequestFailureAsync(HttpContext context, StreamCopyHttpContent requestContent, Exception requestException)
        {
            // Check for request body errors, these may have triggered the response error.
            if (requestContent?.ConsumptionTask.IsCompleted == true)
            {
                var(requestBodyCopyResult, requestBodyException) = await requestContent.ConsumptionTask;

                if (requestBodyCopyResult != StreamCopyResult.Success)
                {
                    HandleRequestBodyFailure(context, requestBodyCopyResult, requestBodyException, requestException);
                    return;
                }
            }

            // We couldn't communicate with the destination.
            ReportProxyError(context, ProxyError.Request, requestException);
            context.Response.StatusCode = StatusCodes.Status502BadGateway;
        }
Beispiel #2
0
        private async Task HandleResponseBodyErrorAsync(HttpContext context, StreamCopyHttpContent requestContent, StreamCopyResult responseBodyCopyResult, Exception responseBodyException)
        {
            if (requestContent?.ConsumptionTask.IsCompleted == true)
            {
                var(requestBodyCopyResult, requestBodyError) = await requestContent.ConsumptionTask;

                // Check for request body errors, these may have triggered the response error.
                if (requestBodyCopyResult != StreamCopyResult.Success)
                {
                    HandleRequestBodyFailure(context, requestBodyCopyResult, requestBodyError, responseBodyException);
                    return;
                }
            }

            var error = responseBodyCopyResult switch
            {
                StreamCopyResult.InputError => ProxyError.ResponseBodyDestination,
                StreamCopyResult.OutputError => ProxyError.ResponseBodyClient,
                StreamCopyResult.Canceled => ProxyError.ResponseBodyCanceled,
                _ => throw new NotImplementedException(responseBodyCopyResult.ToString()),
            };

            ReportProxyError(context, error, responseBodyException);

            if (!context.Response.HasStarted)
            {
                // Nothing has been sent to the client yet, we can still send a good error response.
                context.Response.Clear();
                context.Response.StatusCode = StatusCodes.Status502BadGateway;
                return;
            }

            // The response has already started, we must forcefully terminate it so the client doesn't get the
            // the mistaken impression that the truncated response is complete.
            ResetOrAbort(context, isCancelled: responseBodyCopyResult == StreamCopyResult.Canceled);
        }
Beispiel #3
0
        private StreamCopyHttpContent SetupRequestBodyCopy(HttpRequest request, bool isStreamingRequest, CancellationToken cancellation)
        {
            // If we generate an HttpContent without a Content-Length then for HTTP/1.1 HttpClient will add a Transfer-Encoding: chunked header
            // even if it's a GET request. Some servers reject requests containing a Transfer-Encoding header if they're not expecting a body.
            // Try to be as specific as possible about the client's intent to send a body. The one thing we don't want to do is to start
            // reading the body early because that has side-effects like 100-continue.
            var hasBody       = true;
            var contentLength = request.Headers.ContentLength;
            var method        = request.Method;

#if NET
            var canHaveBodyFeature = request.HttpContext.Features.Get <IHttpRequestBodyDetectionFeature>();
            if (canHaveBodyFeature != null)
            {
                // 5.0 servers provide a definitive answer for us.
                hasBody = canHaveBodyFeature.CanHaveBody;
            }
            else
#endif
            // https://tools.ietf.org/html/rfc7230#section-3.3.3
            // All HTTP/1.1 requests should have Transfer-Encoding or Content-Length.
            // Http.Sys/IIS will even add a Transfer-Encoding header to HTTP/2 requests with bodies for back-compat.
            // HTTP/1.0 Connection: close bodies are only allowed on responses, not requests.
            // https://tools.ietf.org/html/rfc1945#section-7.2.2
            //
            // Transfer-Encoding overrides Content-Length per spec
            if (request.Headers.TryGetValue(HeaderNames.TransferEncoding, out var transferEncoding) &&
                transferEncoding.Count == 1 &&
                string.Equals("chunked", transferEncoding.ToString(), StringComparison.OrdinalIgnoreCase))
            {
                hasBody = true;
            }
            else if (contentLength.HasValue)
            {
                hasBody = contentLength > 0;
            }
            // Kestrel HTTP/2: There are no required headers that indicate if there is a request body so we need to sniff other fields.
            else if (!ProtocolHelper.IsHttp2OrGreater(request.Protocol))
            {
                hasBody = false;
            }
            // https://tools.ietf.org/html/rfc7231#section-4.3.1
            // A payload within a GET/HEAD/DELETE/CONNECT request message has no defined semantics; sending a payload body on a
            // GET/HEAD/DELETE/CONNECT request might cause some existing implementations to reject the request.
            // https://tools.ietf.org/html/rfc7231#section-4.3.8
            // A client MUST NOT send a message body in a TRACE request.
            else if (HttpMethods.IsGet(method) ||
                     HttpMethods.IsHead(method) ||
                     HttpMethods.IsDelete(method) ||
                     HttpMethods.IsConnect(method) ||
                     HttpMethods.IsTrace(method))
            {
                hasBody = false;
            }
            // else hasBody defaults to true

            StreamCopyHttpContent requestContent = null;
            if (hasBody)
            {
                if (isStreamingRequest)
                {
                    DisableMinRequestBodyDataRateAndMaxRequestBodySize(request.HttpContext);
                }

                // Note on `autoFlushHttpClientOutgoingStream: isStreamingRequest`:
                // The.NET Core HttpClient stack keeps its own buffers on top of the underlying outgoing connection socket.
                // We flush those buffers down to the socket on every write when this is set,
                // but it does NOT result in calls to flush on the underlying socket.
                // This is necessary because we proxy http2 transparently,
                // and we are deliberately unaware of packet structure used e.g. in gRPC duplex channels.
                // Because the sockets aren't flushed, the perf impact of this choice is expected to be small.
                // Future: It may be wise to set this to true for *all* http2 incoming requests,
                // but for now, out of an abundance of caution, we only do it for requests that look like gRPC.
                requestContent = new StreamCopyHttpContent(
                    source: request.Body,
                    autoFlushHttpClientOutgoingStream: isStreamingRequest,
                    clock: _clock,
                    cancellation: cancellation);
            }

            return(requestContent);
        }