Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't send body in HEAD response when using PipeWriter.Advance before headers flushed #59725

Merged
merged 5 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 32 additions & 28 deletions src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ internal class Http1OutputProducer : IHttpOutputProducer, IDisposable
// Once write or flush is called, we modify the _currentChunkMemory to prepend the size of data written
// and append the end terminator.

private bool _autoChunk;
private ResponseBodyMode _responseBodyMode;

private bool _writeStreamSuffixCalled;

Expand Down Expand Up @@ -121,7 +121,7 @@ public ValueTask<FlushResult> WriteStreamSuffixAsync()
{
if (!_writeStreamSuffixCalled)
{
if (_autoChunk)
if (_responseBodyMode == ResponseBodyMode.Chunked)
{
var writer = new BufferWriter<PipeWriter>(_pipeWriter);
result = WriteAsyncInternal(ref writer, EndChunkedResponseBytes);
Expand All @@ -147,7 +147,7 @@ public ValueTask<FlushResult> FlushAsync(CancellationToken cancellationToken = d
return new ValueTask<FlushResult>(new FlushResult(false, true));
}

if (_autoChunk)
if (_responseBodyMode == ResponseBodyMode.Chunked)
{
if (_advancedBytesForChunk > 0)
{
Expand All @@ -173,7 +173,7 @@ static ValueTask<FlushResult> FlushAsyncChunked(Http1OutputProducer producer, Ca
// Local function so in the common-path the stack space for BufferWriter isn't reserved and cleared when it isn't used.

Debug.Assert(!producer._pipeWriterCompleted);
Debug.Assert(producer._autoChunk && producer._advancedBytesForChunk > 0);
Debug.Assert(producer._responseBodyMode == ResponseBodyMode.Chunked && producer._advancedBytesForChunk > 0);

var writer = new BufferWriter<PipeWriter>(producer._pipeWriter);
producer.WriteCurrentChunkMemoryToPipeWriter(ref writer);
Expand Down Expand Up @@ -203,7 +203,7 @@ public Memory<byte> GetMemory(int sizeHint = 0)
{
return LeasedMemory(sizeHint);
}
else if (_autoChunk)
else if (_responseBodyMode == ResponseBodyMode.Chunked)
{
return GetChunkedMemory(sizeHint);
}
Expand All @@ -228,7 +228,7 @@ public Span<byte> GetSpan(int sizeHint = 0)
{
return LeasedMemory(sizeHint).Span;
}
else if (_autoChunk)
else if (_responseBodyMode == ResponseBodyMode.Chunked)
{
return GetChunkedMemory(sizeHint).Span;
}
Expand Down Expand Up @@ -262,7 +262,7 @@ public void Advance(int bytes)
_position += bytes;
}
}
else if (_autoChunk)
else if (_responseBodyMode == ResponseBodyMode.Chunked)
{
if (_advancedBytesForChunk > _currentChunkMemory.Length - _currentMemoryPrefixBytes - EndChunkLength - bytes)
{
Expand Down Expand Up @@ -333,7 +333,7 @@ private void CommitChunkInternal(ref BufferWriter<PipeWriter> writer, ReadOnlySp
writer.Commit();
}

public void WriteResponseHeaders(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, bool appComplete)
public void WriteResponseHeaders(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, ResponseBodyMode responseBodyMode, bool appComplete)
{
lock (_contextLock)
{
Expand All @@ -346,11 +346,11 @@ public void WriteResponseHeaders(int statusCode, string? reasonPhrase, HttpRespo

var buffer = _pipeWriter;
var writer = new BufferWriter<PipeWriter>(buffer);
WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, autoChunk);
WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, responseBodyMode);
}
}

private void WriteResponseHeadersInternal(ref BufferWriter<PipeWriter> writer, int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk)
private void WriteResponseHeadersInternal(ref BufferWriter<PipeWriter> writer, int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, ResponseBodyMode responseBodyMode)
{
writer.Write(HttpVersion11Bytes);
var statusBytes = ReasonPhrases.ToStatusBytes(statusCode, reasonPhrase);
Expand All @@ -360,7 +360,8 @@ private void WriteResponseHeadersInternal(ref BufferWriter<PipeWriter> writer, i

writer.Commit();

_autoChunk = autoChunk;
Debug.Assert(responseBodyMode != ResponseBodyMode.Uninitialized);
_responseBodyMode = responseBodyMode;
WriteDataWrittenBeforeHeaders(ref writer);
_unflushedBytes += writer.BytesCommitted;

Expand All @@ -373,11 +374,11 @@ private void WriteDataWrittenBeforeHeaders(ref BufferWriter<PipeWriter> writer)
{
foreach (var segment in _completedSegments)
{
if (_autoChunk)
if (_responseBodyMode == ResponseBodyMode.Chunked)
{
CommitChunkInternal(ref writer, segment.Span);
}
else
else if (_responseBodyMode == ResponseBodyMode.ContentLength)
{
writer.Write(segment.Span);
writer.Commit();
Expand All @@ -391,16 +392,19 @@ private void WriteDataWrittenBeforeHeaders(ref BufferWriter<PipeWriter> writer)

if (!_currentSegment.IsEmpty)
{
var segment = _currentSegment.Slice(0, _position);

if (_autoChunk)
if (_responseBodyMode != ResponseBodyMode.Disabled)
{
CommitChunkInternal(ref writer, segment.Span);
}
else
{
writer.Write(segment.Span);
writer.Commit();
var segment = _currentSegment.Slice(0, _position);

if (_responseBodyMode == ResponseBodyMode.Chunked)
{
CommitChunkInternal(ref writer, segment.Span);
}
else if (_responseBodyMode == ResponseBodyMode.ContentLength)
{
writer.Write(segment.Span);
writer.Commit();
}
}

_position = 0;
Expand Down Expand Up @@ -491,7 +495,7 @@ public ValueTask<FlushResult> Write100ContinueAsync()
return WriteAsync(ContinueBytes);
}

public ValueTask<FlushResult> FirstWriteAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
public ValueTask<FlushResult> FirstWriteAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, ResponseBodyMode responseBodyMode, ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
{
lock (_contextLock)
{
Expand All @@ -505,13 +509,13 @@ public ValueTask<FlushResult> FirstWriteAsync(int statusCode, string? reasonPhra
// Uses same BufferWriter to write response headers and response
var writer = new BufferWriter<PipeWriter>(_pipeWriter);

WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, autoChunk);
WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, responseBodyMode);

return WriteAsyncInternal(ref writer, buffer, cancellationToken);
}
}

public ValueTask<FlushResult> FirstWriteChunkedAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
public ValueTask<FlushResult> FirstWriteChunkedAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, ResponseBodyMode responseBodyMode, ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
{
lock (_contextLock)
{
Expand All @@ -525,7 +529,7 @@ public ValueTask<FlushResult> FirstWriteChunkedAsync(int statusCode, string? rea
// Uses same BufferWriter to write response headers and chunk
var writer = new BufferWriter<PipeWriter>(_pipeWriter);

WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, autoChunk);
WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, responseBodyMode);

CommitChunkInternal(ref writer, buffer);

Expand All @@ -541,7 +545,7 @@ public void Reset()
Debug.Assert(_completedSegments == null || _completedSegments.Count == 0);
// Cleared in sequential address ascending order
_currentMemoryPrefixBytes = 0;
_autoChunk = false;
_responseBodyMode = ResponseBodyMode.Uninitialized;
_writeStreamSuffixCalled = false;
_currentChunkMemoryUpdated = false;
_startCalled = false;
Expand Down Expand Up @@ -570,7 +574,7 @@ private ValueTask<FlushResult> WriteAsyncInternal(
ReadOnlySpan<byte> buffer,
CancellationToken cancellationToken = default)
{
if (_autoChunk)
if (_responseBodyMode == ResponseBodyMode.Chunked)
{
if (_advancedBytesForChunk > 0)
{
Expand Down
Loading
Loading