Skip to content

Commit fb4bb02

Browse files
authored
IHttpRequestBodyDetectionFeature for HTTP/3 GET requests (#62275)
* Enhance HTTP/3 request body detection and testing - Updated `IHttpRequestBodyDetectionFeature` to include HTTP/3 descriptions for the `END_STREAM` flag. - Modified request body handling logic to return zero length content body instance when there endstream flag is set. It also completes the message body for empty requests, so that the RequestBodyPipe.Reader is closed (needed when stream is reused by the pool and the Pipe is reset). - Added unit test `CanHaveBody_ReturnsFalseWithoutRequestBody` to validate behavior when no request body is present.
1 parent 4594be6 commit fb4bb02

File tree

3 files changed

+53
-0
lines changed

3 files changed

+53
-0
lines changed

src/Http/Http.Features/src/IHttpRequestBodyDetectionFeature.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public interface IHttpRequestBodyDetectionFeature
2020
/// <item><description>
2121
/// It's an HTTP/2 request that did not set the END_STREAM flag on the initial headers frame.
2222
/// </description></item>
23+
/// <item><description>
24+
/// It's an HTTP/3 request that did not set the END_STREAM flag on the initial headers frame.
25+
/// </description></item>
2326
/// </list>
2427
/// The final request body length may still be zero for the chunked or HTTP/2 scenarios.
2528
/// <para>
@@ -35,6 +38,9 @@ public interface IHttpRequestBodyDetectionFeature
3538
/// <item><description>
3639
/// It's an HTTP/2 request that set END_STREAM on the initial headers frame.
3740
/// </description></item>
41+
/// <item><description>
42+
/// It's an HTTP/3 request that set END_STREAM on the initial headers frame.
43+
/// </description></item>
3844
/// </list>
3945
/// </para>
4046
/// When false, the request body should never return data.

src/Servers/Kestrel/Core/src/Internal/Http3/Http3Stream.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
5454
private bool _isMethodConnect;
5555
private bool _isWebTransportSessionAccepted;
5656
private Http3MessageBody? _messageBody;
57+
private bool _requestBodyStarted;
5758

5859
private readonly ManualResetValueTaskSource<object?> _appCompletedTaskSource = new();
5960
private readonly Lock _completionLock = new();
@@ -65,6 +66,17 @@ internal abstract partial class Http3Stream : HttpProtocol, IHttp3Stream, IHttpS
6566
private bool IsAbortedRead => (_completionState & StreamCompletionFlags.AbortedRead) == StreamCompletionFlags.AbortedRead;
6667
public bool IsCompleted => (_completionState & StreamCompletionFlags.Completed) == StreamCompletionFlags.Completed;
6768

69+
public bool ReceivedEmptyRequestBody
70+
{
71+
get
72+
{
73+
lock (_completionLock)
74+
{
75+
return EndStreamReceived && !_requestBodyStarted;
76+
}
77+
}
78+
}
79+
6880
public Pipe RequestBodyPipe { get; private set; } = default!;
6981
public long? InputRemaining { get; internal set; }
7082
public QPackDecoder QPackDecoder { get; private set; } = default!;
@@ -560,6 +572,8 @@ private void CompleteStream(bool errored)
560572
TryClose();
561573
}
562574

575+
RequestBodyPipe.Reader.Complete();
576+
563577
_http3Output.Complete();
564578

565579
// Stream will be pooled after app completed.
@@ -929,6 +943,8 @@ private Task ProcessDataFrameAsync(in ReadOnlySequence<byte> payload)
929943
return Task.CompletedTask;
930944
}
931945

946+
_requestBodyStarted = true;
947+
932948
foreach (var segment in payload)
933949
{
934950
RequestBodyPipe.Writer.Write(segment.Span);
@@ -966,6 +982,11 @@ protected override string CreateRequestId()
966982

967983
protected override MessageBody CreateMessageBody()
968984
{
985+
if (ReceivedEmptyRequestBody)
986+
{
987+
return MessageBody.ZeroContentLengthClose;
988+
}
989+
969990
if (_messageBody != null)
970991
{
971992
_messageBody.Reset();

src/Servers/Kestrel/test/InMemory.FunctionalTests/Http3/Http3StreamTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3291,6 +3291,32 @@ public async Task Control_FrameParsingSingleByteAtATimeWorks()
32913291
await outboundcontrolStream.ReceiveEndAsync();
32923292
}
32933293

3294+
[Theory]
3295+
[InlineData(true)]
3296+
[InlineData(false)]
3297+
public async Task CanHaveBody_ReturnsFalseWithoutRequestBody(bool endstream)
3298+
{
3299+
var headers = new[]
3300+
{
3301+
new KeyValuePair<string, string>(InternalHeaderNames.Method, "GET"),
3302+
new KeyValuePair<string, string>(InternalHeaderNames.Path, "/"),
3303+
new KeyValuePair<string, string>(InternalHeaderNames.Scheme, "http"),
3304+
new KeyValuePair<string, string>(InternalHeaderNames.Authority, "localhost:80"),
3305+
};
3306+
3307+
var requestStream = await Http3Api.InitializeConnectionAndStreamsAsync(context =>
3308+
{
3309+
Assert.NotEqual(endstream, context.Features.Get<IHttpRequestBodyDetectionFeature>().CanHaveBody);
3310+
context.Response.StatusCode = 200;
3311+
return Task.CompletedTask;
3312+
}, headers, endStream: endstream);
3313+
3314+
var responseHeaders = await requestStream.ExpectHeadersAsync();
3315+
Assert.Equal("200", responseHeaders[InternalHeaderNames.Status]);
3316+
3317+
await requestStream.ExpectReceiveEndOfStream();
3318+
}
3319+
32943320
private async Task WriteOneByteAtATime(PipeReader reader, PipeWriter writer)
32953321
{
32963322
var res = await reader.ReadAsync();

0 commit comments

Comments
 (0)