private ForwarderError HandleRequestBodyFailure(HttpContext context, StreamCopyResult requestBodyCopyResult, Exception requestBodyException, Exception additionalException) { ForwarderError requestBodyError; int statusCode; switch (requestBodyCopyResult) { // Failed while trying to copy the request body from the client. It's ambiguous if the request or response failed first. case StreamCopyResult.InputError: requestBodyError = ForwarderError.RequestBodyClient; statusCode = StatusCodes.Status400BadRequest; break; // Failed while trying to copy the request body to the destination. It's ambiguous if the request or response failed first. case StreamCopyResult.OutputError: requestBodyError = ForwarderError.RequestBodyDestination; statusCode = StatusCodes.Status502BadGateway; break; // Canceled while trying to copy the request body, either due to a client disconnect or a timeout. This probably caused the response to fail as a secondary error. case StreamCopyResult.Canceled: requestBodyError = ForwarderError.RequestBodyCanceled; // Timeouts (504s) are handled at the SendAsync call site. // The request body should only be canceled by the RequestAborted token. statusCode = StatusCodes.Status502BadGateway; break; default: throw new NotImplementedException(requestBodyCopyResult.ToString()); } ReportProxyError(context, requestBodyError, new AggregateException(requestBodyException, additionalException)); // We don't know if the client is still around to see this error, but set it for diagnostics to see. 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 = statusCode; return(requestBodyError); } ResetOrAbort(context, isCancelled: requestBodyCopyResult == StreamCopyResult.Canceled); return(requestBodyError); }
private async ValueTask <ForwarderError> HandleResponseBodyErrorAsync(HttpContext context, StreamCopyHttpContent?requestContent, StreamCopyResult responseBodyCopyResult, Exception responseBodyException, CancellationTokenSource requestCancellationSource) { if (requestContent is not null && requestContent.Started) { var alreadyFinished = requestContent.ConsumptionTask.IsCompleted == true; if (!alreadyFinished) { requestCancellationSource.Cancel(); } var(requestBodyCopyResult, requestBodyError) = await requestContent.ConsumptionTask; // Check for request body errors, these may have triggered the response error. if (alreadyFinished && requestBodyCopyResult != StreamCopyResult.Success) { return(HandleRequestBodyFailure(context, requestBodyCopyResult, requestBodyError !, responseBodyException)); } } var error = responseBodyCopyResult switch { StreamCopyResult.InputError => ForwarderError.ResponseBodyDestination, StreamCopyResult.OutputError => ForwarderError.ResponseBodyClient, StreamCopyResult.Canceled => ForwarderError.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(error); } // 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); return(error); }