Skip to content

Commit

Permalink
Request body exception status code granularity (#2227)
Browse files Browse the repository at this point in the history
  • Loading branch information
antonybstack authored Aug 30, 2023
1 parent 82d0748 commit dfb0d4d
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 2 deletions.
9 changes: 7 additions & 2 deletions src/ReverseProxy/Forwarder/HttpForwarder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public async ValueTask<ForwarderError> SendAsync(
Debug.Assert(false == (bool?)hre.Data["SETTINGS_ENABLE_CONNECT_PROTOCOL"]);
Log.RetryingWebSocketDowngradeNoConnect(_logger);
}
// This is how SocketsHttpHandler communicates to us that we tried HTTP/2, but the server only supports
// This is how SocketsHttpHandler communicates to us that we tried HTTP/2, but the server only supports
// HTTP/1.x (as determined by ALPN). We'll only get this when using TLS/https. Retry on HTTP/1.1.
// We don't let SocketsHttpHandler downgrade automatically for us because we need to send different headers.
else if (hre.Data.Contains("HTTP2_ENABLED"))
Expand Down Expand Up @@ -577,7 +577,12 @@ private ForwarderError HandleRequestBodyFailure(HttpContext context, StreamCopyR
// 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 = timedOut ? StatusCodes.Status408RequestTimeout : StatusCodes.Status400BadRequest;
statusCode = timedOut
? StatusCodes.Status408RequestTimeout
// Attempt to capture the status code from the request exception if available.
: requestBodyException is BadHttpRequestException badHttpRequestException
? badHttpRequestException.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:
Expand Down
60 changes: 60 additions & 0 deletions test/ReverseProxy.Tests/Forwarder/HttpForwarderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
Expand Down Expand Up @@ -1689,6 +1690,42 @@ public async Task RequestBodyClientErrorBeforeResponseError_Returns400()
});
}

[Theory]
[InlineData(StatusCodes.Status413PayloadTooLarge)]
public async Task NonGenericRequestBodyClientErrorCode_ReturnsNonGenericClientErrorCode(int statusCode)
{
var events = TestEventListener.Collect();

var httpContext = new DefaultHttpContext();
httpContext.Request.Method = "POST";
httpContext.Request.Host = new HostString("example.com:3456");
httpContext.Request.ContentLength = 1;
httpContext.Request.Body = new ThrowBadHttpRequestExceptionStream(statusCode);

var destinationPrefix = "https://localhost/";
var sut = CreateProxy();
var client = MockHttpHandler.CreateClient(
async (request, _) =>
{
Assert.NotNull(request.Content);
await request.Content.CopyToWithCancellationAsync(Stream.Null);
return new HttpResponseMessage(HttpStatusCode.OK) { Content = new ByteArrayContent(Array.Empty<byte>()) };
});

var proxyError = await sut.SendAsync(httpContext, destinationPrefix, client);

Assert.Equal(ForwarderError.RequestBodyClient, proxyError);
Assert.Equal(statusCode, httpContext.Response.StatusCode);
var errorFeature = httpContext.Features.Get<IForwarderErrorFeature>();
Assert.Equal(ForwarderError.RequestBodyClient, errorFeature.Error);
Assert.IsType<AggregateException>(errorFeature.Exception);

AssertProxyStartFailedStop(events, destinationPrefix, httpContext.Response.StatusCode, errorFeature.Error);
events.AssertContainProxyStages(new[] { ForwarderStage.SendAsyncStart, ForwarderStage.RequestContentTransferStart });
}

[Fact]
public async Task RequestBodyDestinationErrorBeforeResponseError_Returns502()
{
Expand Down Expand Up @@ -3162,4 +3199,27 @@ public override async ValueTask TransformResponseTrailersAsync(HttpContext httpC
await OnResponseTrailers(httpContext, proxyResponse);
}
}

private class ThrowBadHttpRequestExceptionStream : DelegatingStream
{
public ThrowBadHttpRequestExceptionStream(int statusCode)
: base(Stream.Null)
{
StatusCode = statusCode;
}

private int StatusCode { get; }

public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
if (buffer.Length == 0)
{
return new ValueTask<int>(0);
}

cancellationToken.ThrowIfCancellationRequested();

throw new BadHttpRequestException(ReasonPhrases.GetReasonPhrase(StatusCode), StatusCode);
}
}
}

0 comments on commit dfb0d4d

Please sign in to comment.