Skip to content

Commit 000f75d

Browse files
committed
fb
1 parent 96c885b commit 000f75d

File tree

6 files changed

+69
-76
lines changed

6 files changed

+69
-76
lines changed

src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs

Lines changed: 27 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ internal class Http1OutputProducer : IHttpOutputProducer, IDisposable
4040
private bool _aborted;
4141
private long _unflushedBytes;
4242
private int _currentMemoryPrefixBytes;
43-
private bool _canWriteBody = true;
4443

4544
private readonly ConcurrentPipeWriter _pipeWriter;
4645
private IMemoryOwner<byte>? _fakeMemoryOwner;
@@ -52,7 +51,7 @@ internal class Http1OutputProducer : IHttpOutputProducer, IDisposable
5251
// Once write or flush is called, we modify the _currentChunkMemory to prepend the size of data written
5352
// and append the end terminator.
5453

55-
private bool _autoChunk;
54+
private ResponseBodyMode _responseBodyMode;
5655

5756
private bool _writeStreamSuffixCalled;
5857

@@ -122,7 +121,7 @@ public ValueTask<FlushResult> WriteStreamSuffixAsync()
122121
{
123122
if (!_writeStreamSuffixCalled)
124123
{
125-
if (_autoChunk && _canWriteBody)
124+
if (_responseBodyMode == ResponseBodyMode.Chunked)
126125
{
127126
var writer = new BufferWriter<PipeWriter>(_pipeWriter);
128127
result = WriteAsyncInternal(ref writer, EndChunkedResponseBytes);
@@ -148,7 +147,7 @@ public ValueTask<FlushResult> FlushAsync(CancellationToken cancellationToken = d
148147
return new ValueTask<FlushResult>(new FlushResult(false, true));
149148
}
150149

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

176175
Debug.Assert(!producer._pipeWriterCompleted);
177-
Debug.Assert(producer._autoChunk && producer._advancedBytesForChunk > 0);
176+
Debug.Assert(producer._responseBodyMode == ResponseBodyMode.Chunked && producer._advancedBytesForChunk > 0);
178177

179178
var writer = new BufferWriter<PipeWriter>(producer._pipeWriter);
180179
producer.WriteCurrentChunkMemoryToPipeWriter(ref writer);
@@ -204,7 +203,7 @@ public Memory<byte> GetMemory(int sizeHint = 0)
204203
{
205204
return LeasedMemory(sizeHint);
206205
}
207-
else if (_autoChunk)
206+
else if (_responseBodyMode == ResponseBodyMode.Chunked)
208207
{
209208
return GetChunkedMemory(sizeHint);
210209
}
@@ -229,7 +228,7 @@ public Span<byte> GetSpan(int sizeHint = 0)
229228
{
230229
return LeasedMemory(sizeHint).Span;
231230
}
232-
else if (_autoChunk)
231+
else if (_responseBodyMode == ResponseBodyMode.Chunked)
233232
{
234233
return GetChunkedMemory(sizeHint).Span;
235234
}
@@ -263,7 +262,7 @@ public void Advance(int bytes)
263262
_position += bytes;
264263
}
265264
}
266-
else if (_autoChunk)
265+
else if (_responseBodyMode == ResponseBodyMode.Chunked)
267266
{
268267
if (_advancedBytesForChunk > _currentChunkMemory.Length - _currentMemoryPrefixBytes - EndChunkLength - bytes)
269268
{
@@ -334,7 +333,7 @@ private void CommitChunkInternal(ref BufferWriter<PipeWriter> writer, ReadOnlySp
334333
writer.Commit();
335334
}
336335

337-
public void WriteResponseHeaders(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, bool appComplete)
336+
public void WriteResponseHeaders(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, ResponseBodyMode responseBodyMode, bool appComplete)
338337
{
339338
lock (_contextLock)
340339
{
@@ -347,11 +346,11 @@ public void WriteResponseHeaders(int statusCode, string? reasonPhrase, HttpRespo
347346

348347
var buffer = _pipeWriter;
349348
var writer = new BufferWriter<PipeWriter>(buffer);
350-
WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, autoChunk);
349+
WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, responseBodyMode);
351350
}
352351
}
353352

354-
private void WriteResponseHeadersInternal(ref BufferWriter<PipeWriter> writer, int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk)
353+
private void WriteResponseHeadersInternal(ref BufferWriter<PipeWriter> writer, int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, ResponseBodyMode responseBodyMode)
355354
{
356355
writer.Write(HttpVersion11Bytes);
357356
var statusBytes = ReasonPhrases.ToStatusBytes(statusCode, reasonPhrase);
@@ -361,7 +360,7 @@ private void WriteResponseHeadersInternal(ref BufferWriter<PipeWriter> writer, i
361360

362361
writer.Commit();
363362

364-
_autoChunk = autoChunk;
363+
_responseBodyMode = responseBodyMode;
365364
WriteDataWrittenBeforeHeaders(ref writer);
366365
_unflushedBytes += writer.BytesCommitted;
367366

@@ -374,17 +373,14 @@ private void WriteDataWrittenBeforeHeaders(ref BufferWriter<PipeWriter> writer)
374373
{
375374
foreach (var segment in _completedSegments)
376375
{
377-
if (_canWriteBody)
376+
if (_responseBodyMode == ResponseBodyMode.Chunked)
378377
{
379-
if (_autoChunk)
380-
{
381-
CommitChunkInternal(ref writer, segment.Span);
382-
}
383-
else
384-
{
385-
writer.Write(segment.Span);
386-
writer.Commit();
387-
}
378+
CommitChunkInternal(ref writer, segment.Span);
379+
}
380+
else if (_responseBodyMode == ResponseBodyMode.ContentLength)
381+
{
382+
writer.Write(segment.Span);
383+
writer.Commit();
388384
}
389385
segment.Return();
390386
}
@@ -395,15 +391,15 @@ private void WriteDataWrittenBeforeHeaders(ref BufferWriter<PipeWriter> writer)
395391

396392
if (!_currentSegment.IsEmpty)
397393
{
398-
if (_canWriteBody)
394+
if (_responseBodyMode != ResponseBodyMode.Disabled)
399395
{
400396
var segment = _currentSegment.Slice(0, _position);
401397

402-
if (_autoChunk)
398+
if (_responseBodyMode == ResponseBodyMode.Chunked)
403399
{
404400
CommitChunkInternal(ref writer, segment.Span);
405401
}
406-
else
402+
else if (_responseBodyMode == ResponseBodyMode.ContentLength)
407403
{
408404
writer.Write(segment.Span);
409405
writer.Commit();
@@ -498,7 +494,7 @@ public ValueTask<FlushResult> Write100ContinueAsync()
498494
return WriteAsync(ContinueBytes);
499495
}
500496

501-
public ValueTask<FlushResult> FirstWriteAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
497+
public ValueTask<FlushResult> FirstWriteAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, ResponseBodyMode responseBodyMode, ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
502498
{
503499
lock (_contextLock)
504500
{
@@ -512,13 +508,13 @@ public ValueTask<FlushResult> FirstWriteAsync(int statusCode, string? reasonPhra
512508
// Uses same BufferWriter to write response headers and response
513509
var writer = new BufferWriter<PipeWriter>(_pipeWriter);
514510

515-
WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, autoChunk);
511+
WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, responseBodyMode);
516512

517513
return WriteAsyncInternal(ref writer, buffer, cancellationToken);
518514
}
519515
}
520516

521-
public ValueTask<FlushResult> FirstWriteChunkedAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
517+
public ValueTask<FlushResult> FirstWriteChunkedAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, ResponseBodyMode responseBodyMode, ReadOnlySpan<byte> buffer, CancellationToken cancellationToken)
522518
{
523519
lock (_contextLock)
524520
{
@@ -532,7 +528,7 @@ public ValueTask<FlushResult> FirstWriteChunkedAsync(int statusCode, string? rea
532528
// Uses same BufferWriter to write response headers and chunk
533529
var writer = new BufferWriter<PipeWriter>(_pipeWriter);
534530

535-
WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, autoChunk);
531+
WriteResponseHeadersInternal(ref writer, statusCode, reasonPhrase, responseHeaders, responseBodyMode);
536532

537533
CommitChunkInternal(ref writer, buffer);
538534

@@ -542,22 +538,16 @@ public ValueTask<FlushResult> FirstWriteChunkedAsync(int statusCode, string? rea
542538
}
543539
}
544540

545-
public void SetCanWriteBody(bool canWriteBody)
546-
{
547-
_canWriteBody = canWriteBody;
548-
}
549-
550541
public void Reset()
551542
{
552543
Debug.Assert(_currentSegmentOwner == null);
553544
Debug.Assert(_completedSegments == null || _completedSegments.Count == 0);
554545
// Cleared in sequential address ascending order
555546
_currentMemoryPrefixBytes = 0;
556-
_autoChunk = false;
547+
_responseBodyMode = ResponseBodyMode.ContentLength;
557548
_writeStreamSuffixCalled = false;
558549
_currentChunkMemoryUpdated = false;
559550
_startCalled = false;
560-
_canWriteBody = true;
561551
}
562552

563553
private ValueTask<FlushResult> WriteAsync(
@@ -583,7 +573,7 @@ private ValueTask<FlushResult> WriteAsyncInternal(
583573
ReadOnlySpan<byte> buffer,
584574
CancellationToken cancellationToken = default)
585575
{
586-
if (_autoChunk)
576+
if (_responseBodyMode == ResponseBodyMode.Chunked)
587577
{
588578
if (_advancedBytesForChunk > 0)
589579
{

src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,11 @@ internal abstract partial class HttpProtocol : IHttpResponseControl
5454
// Keep-alive is default for HTTP/1.1 and HTTP/2; parsing and errors will change its value
5555
// volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
5656
protected volatile bool _keepAlive = true;
57-
// _canWriteResponseBody is set in CreateResponseHeaders.
57+
// _responseBodyMode is set in CreateResponseHeaders.
5858
// If we are writing with GetMemory/Advance before calling StartAsync, assume we can write and throw away contents if we can't.
59-
private bool _canWriteResponseBody = true;
59+
private ResponseBodyMode _responseBodyMode = ResponseBodyMode.ContentLength;
6060
private bool _hasAdvanced;
6161
private bool _isLeasedMemoryInvalid = true;
62-
private bool _autoChunk;
6362
protected Exception? _applicationException;
6463
private BadHttpRequestException? _requestRejectedException;
6564

@@ -347,7 +346,7 @@ public void Reset()
347346
_routeValues?.Clear();
348347

349348
_requestProcessingStatus = RequestProcessingStatus.RequestPending;
350-
_autoChunk = false;
349+
_responseBodyMode = ResponseBodyMode.ContentLength;
351350
_applicationException = null;
352351
_requestRejectedException = null;
353352

@@ -397,7 +396,6 @@ public void Reset()
397396

398397
_isLeasedMemoryInvalid = true;
399398
_hasAdvanced = false;
400-
_canWriteResponseBody = true;
401399

402400
if (_scheme == null)
403401
{
@@ -999,7 +997,7 @@ private void ProduceStart(bool appCompleted)
999997

1000998
var responseHeaders = CreateResponseHeaders(appCompleted);
1001999

1002-
Output.WriteResponseHeaders(StatusCode, ReasonPhrase, responseHeaders, _autoChunk, appCompleted);
1000+
Output.WriteResponseHeaders(StatusCode, ReasonPhrase, responseHeaders, _responseBodyMode, appCompleted);
10031001
}
10041002

10051003
private void VerifyInitializeState(int firstWriteByteCount)
@@ -1067,7 +1065,7 @@ protected Task ProduceEnd()
10671065

10681066
private Task WriteSuffix()
10691067
{
1070-
if (_autoChunk || _httpVersion >= Http.HttpVersion.Http2)
1068+
if (_responseBodyMode == ResponseBodyMode.Chunked || _httpVersion >= Http.HttpVersion.Http2)
10711069
{
10721070
// For the same reason we call CheckLastWrite() in Content-Length responses.
10731071
PreventRequestAbortedCancellation();
@@ -1161,10 +1159,9 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted)
11611159
}
11621160

11631161
// Set whether response can have body
1164-
_canWriteResponseBody = CanWriteResponseBody();
1165-
Output.SetCanWriteBody(_canWriteResponseBody);
1162+
_responseBodyMode = CanWriteResponseBody() ? ResponseBodyMode.ContentLength : ResponseBodyMode.Disabled;
11661163

1167-
if (!_canWriteResponseBody && hasTransferEncoding)
1164+
if (_responseBodyMode == ResponseBodyMode.Disabled && hasTransferEncoding)
11681165
{
11691166
RejectInvalidHeaderForNonBodyResponse(appCompleted, HeaderNames.TransferEncoding);
11701167
}
@@ -1198,7 +1195,7 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted)
11981195
}
11991196
else if (!hasTransferEncoding && !responseHeaders.ContentLength.HasValue)
12001197
{
1201-
if ((appCompleted || !_canWriteResponseBody) && !_hasAdvanced) // Avoid setting contentLength of 0 if we wrote data before calling CreateResponseHeaders
1198+
if ((appCompleted || _responseBodyMode == ResponseBodyMode.Disabled) && !_hasAdvanced) // Avoid setting contentLength of 0 if we wrote data before calling CreateResponseHeaders
12021199
{
12031200
if (CanAutoSetContentLengthZeroResponseHeader())
12041201
{
@@ -1219,8 +1216,11 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted)
12191216
// The chunked transfer encoding defined in Section 4.1 of [RFC7230] MUST NOT be used in HTTP/2.
12201217
else if (_httpVersion == Http.HttpVersion.Http11)
12211218
{
1222-
_autoChunk = true;
1223-
responseHeaders.SetRawTransferEncoding("chunked", _bytesTransferEncodingChunked);
1219+
if (_responseBodyMode == ResponseBodyMode.ContentLength)
1220+
{
1221+
_responseBodyMode = ResponseBodyMode.Chunked;
1222+
responseHeaders.SetRawTransferEncoding("chunked", _bytesTransferEncodingChunked);
1223+
}
12241224
}
12251225
else
12261226
{
@@ -1471,7 +1471,7 @@ public void Advance(int bytes)
14711471
throw new InvalidOperationException("Invalid ordering of calling StartAsync or CompleteAsync and Advance.");
14721472
}
14731473

1474-
if (_canWriteResponseBody)
1474+
if (_responseBodyMode != ResponseBodyMode.Disabled)
14751475
{
14761476
VerifyAndUpdateWrite(bytes);
14771477
Output.Advance(bytes);
@@ -1592,9 +1592,9 @@ public ValueTask<FlushResult> WritePipeAsync(ReadOnlyMemory<byte> data, Cancella
15921592
VerifyAndUpdateWrite(data.Length);
15931593
}
15941594

1595-
if (_canWriteResponseBody)
1595+
if (_responseBodyMode != ResponseBodyMode.Disabled)
15961596
{
1597-
if (_autoChunk)
1597+
if (_responseBodyMode == ResponseBodyMode.Chunked)
15981598
{
15991599
if (data.Length == 0)
16001600
{
@@ -1640,27 +1640,27 @@ private ValueTask<FlushResult> FirstWriteAsyncInternal(ReadOnlyMemory<byte> data
16401640
{
16411641
var responseHeaders = InitializeResponseFirstWrite(data.Length);
16421642

1643-
if (_canWriteResponseBody)
1643+
if (_responseBodyMode != ResponseBodyMode.Disabled)
16441644
{
1645-
if (_autoChunk)
1645+
if (_responseBodyMode == ResponseBodyMode.Chunked)
16461646
{
16471647
if (data.Length == 0)
16481648
{
1649-
Output.WriteResponseHeaders(StatusCode, ReasonPhrase, responseHeaders, _autoChunk, appCompleted: false);
1649+
Output.WriteResponseHeaders(StatusCode, ReasonPhrase, responseHeaders, _responseBodyMode, appCompleted: false);
16501650
return Output.FlushAsync(cancellationToken);
16511651
}
16521652

1653-
return Output.FirstWriteChunkedAsync(StatusCode, ReasonPhrase, responseHeaders, _autoChunk, data.Span, cancellationToken);
1653+
return Output.FirstWriteChunkedAsync(StatusCode, ReasonPhrase, responseHeaders, _responseBodyMode, data.Span, cancellationToken);
16541654
}
16551655
else
16561656
{
16571657
CheckLastWrite();
1658-
return Output.FirstWriteAsync(StatusCode, ReasonPhrase, responseHeaders, _autoChunk, data.Span, cancellationToken);
1658+
return Output.FirstWriteAsync(StatusCode, ReasonPhrase, responseHeaders, _responseBodyMode, data.Span, cancellationToken);
16591659
}
16601660
}
16611661
else
16621662
{
1663-
Output.WriteResponseHeaders(StatusCode, ReasonPhrase, responseHeaders, _autoChunk, appCompleted: false);
1663+
Output.WriteResponseHeaders(StatusCode, ReasonPhrase, responseHeaders, _responseBodyMode, appCompleted: false);
16641664
HandleNonBodyResponseWrite();
16651665
return Output.FlushAsync(cancellationToken);
16661666
}
@@ -1689,9 +1689,9 @@ public async ValueTask<FlushResult> WriteAsyncAwaited(Task initializeTask, ReadO
16891689

16901690
// WriteAsyncAwaited is only called for the first write to the body.
16911691
// Ensure headers are flushed if Write(Chunked)Async isn't called.
1692-
if (_canWriteResponseBody)
1692+
if (_responseBodyMode != ResponseBodyMode.Disabled)
16931693
{
1694-
if (_autoChunk)
1694+
if (_responseBodyMode == ResponseBodyMode.Chunked)
16951695
{
16961696
if (data.Length == 0)
16971697
{

src/Servers/Kestrel/Core/src/Internal/Http/IHttpOutputProducer.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal interface IHttpOutputProducer
1313
ValueTask<FlushResult> WriteChunkAsync(ReadOnlySpan<byte> data, CancellationToken cancellationToken);
1414
ValueTask<FlushResult> FlushAsync(CancellationToken cancellationToken);
1515
ValueTask<FlushResult> Write100ContinueAsync();
16-
void WriteResponseHeaders(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, bool appCompleted);
16+
void WriteResponseHeaders(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, ResponseBodyMode responseBodyMode, bool appCompleted);
1717
// This takes ReadOnlySpan instead of ReadOnlyMemory because it always synchronously copies data before flushing.
1818
ValueTask<FlushResult> WriteDataToPipeAsync(ReadOnlySpan<byte> data, CancellationToken cancellationToken);
1919
// Test hook
@@ -25,9 +25,14 @@ internal interface IHttpOutputProducer
2525
Memory<byte> GetMemory(int sizeHint = 0);
2626
void CancelPendingFlush();
2727
void Stop();
28-
ValueTask<FlushResult> FirstWriteAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan<byte> data, CancellationToken cancellationToken);
29-
ValueTask<FlushResult> FirstWriteChunkedAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, bool autoChunk, ReadOnlySpan<byte> data, CancellationToken cancellationToken);
28+
ValueTask<FlushResult> FirstWriteAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, ResponseBodyMode responseBodyMode, ReadOnlySpan<byte> data, CancellationToken cancellationToken);
29+
ValueTask<FlushResult> FirstWriteChunkedAsync(int statusCode, string? reasonPhrase, HttpResponseHeaders responseHeaders, ResponseBodyMode responseBodyMode, ReadOnlySpan<byte> data, CancellationToken cancellationToken);
3030
void Reset();
31-
// Only used by Http1OutputProducer for non-body responses
32-
void SetCanWriteBody(bool canWriteBody) { }
31+
}
32+
33+
internal enum ResponseBodyMode
34+
{
35+
Disabled,
36+
Chunked,
37+
ContentLength
3338
}

0 commit comments

Comments
 (0)