-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Make StartAsync not throw if we haven't started the response #8199
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
Changes from 12 commits
13b969a
5b94f74
3d6d163
34e3381
61640ae
009380f
33d8cd6
e63dac4
08f94ac
0ec739d
cbf8d68
da8b6ed
028e19c
039fdd3
959a31a
63162c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -45,7 +45,11 @@ public abstract partial class HttpProtocol : IDefaultHttpContextContainer, IHttp | |
// Keep-alive is default for HTTP/1.1 and HTTP/2; parsing and errors will change its value | ||
// volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx | ||
protected volatile bool _keepAlive = true; | ||
private bool _canWriteResponseBody; | ||
// _canWriteResponseBody is set in CreateResponseHeaders. | ||
// If we are writing with GetMemory/Advance before calling StartAsync, assume we can write and throw away contents if we can't. | ||
private bool _canWriteResponseBody = true; | ||
private bool _hasAdvanced; | ||
private bool _isLeasedMemoryInvalid = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should _hasAdvanced and _isLeasedMemoryInvalid be reset between requests? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a regression test that will fail if we forgot to reset? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ya caught me. |
||
private bool _autoChunk; | ||
protected Exception _applicationException; | ||
private BadHttpRequestException _requestRejectedException; | ||
|
@@ -921,6 +925,8 @@ private void ProduceStart(bool appCompleted) | |
return; | ||
} | ||
|
||
_isLeasedMemoryInvalid = true; | ||
|
||
_requestProcessingStatus = RequestProcessingStatus.HeadersCommitted; | ||
|
||
var responseHeaders = CreateResponseHeaders(appCompleted); | ||
|
@@ -1066,7 +1072,7 @@ private HttpResponseHeaders CreateResponseHeaders(bool appCompleted) | |
{ | ||
_keepAlive = false; | ||
} | ||
else if (appCompleted || !_canWriteResponseBody) | ||
else if ((appCompleted || !_canWriteResponseBody) && !_hasAdvanced) // Avoid setting contentLength of 0 if we wrote data before calling CreateResponseHeaders | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if appCompleted && _hasAdvanced then we can set ContentLength to a specific value and avoid chunking. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I was thinking about doing this in this PR but wasn't a fan. You would need to peek into the number of advanced bytes inside of the Http1OutputProducer which isn't clean. I'd prefer to do it in a separate pr. |
||
{ | ||
// Don't set the Content-Length header automatically for HEAD requests, 204 responses, or 304 responses. | ||
if (CanAutoSetContentLengthZeroResponseHeader()) | ||
|
@@ -1268,6 +1274,21 @@ public void ReportApplicationError(Exception ex) | |
|
||
public void Advance(int bytes) | ||
{ | ||
if (bytes < 0) | ||
{ | ||
throw new ArgumentOutOfRangeException(nameof(bytes)); | ||
} | ||
else if (bytes > 0) | ||
{ | ||
_hasAdvanced = true; | ||
} | ||
|
||
if (_isLeasedMemoryInvalid) | ||
{ | ||
throw new InvalidOperationException("Invalid ordering of calling StartAsync and Advance. " + | ||
"Call StartAsync before calling GetMemory/GetSpan and Advance."); | ||
} | ||
|
||
if (_canWriteResponseBody) | ||
{ | ||
VerifyAndUpdateWrite(bytes); | ||
|
@@ -1276,7 +1297,6 @@ public void Advance(int bytes) | |
else | ||
{ | ||
HandleNonBodyResponseWrite(); | ||
|
||
// For HEAD requests, we still use the number of bytes written for logging | ||
// how many bytes were written. | ||
VerifyAndUpdateWrite(bytes); | ||
|
@@ -1285,27 +1305,16 @@ public void Advance(int bytes) | |
|
||
public Memory<byte> GetMemory(int sizeHint = 0) | ||
{ | ||
ThrowIfResponseNotStarted(); | ||
|
||
_isLeasedMemoryInvalid = false; | ||
return Output.GetMemory(sizeHint); | ||
} | ||
|
||
public Span<byte> GetSpan(int sizeHint = 0) | ||
{ | ||
ThrowIfResponseNotStarted(); | ||
|
||
_isLeasedMemoryInvalid = false; | ||
return Output.GetSpan(sizeHint); | ||
} | ||
|
||
[StackTraceHidden] | ||
private void ThrowIfResponseNotStarted() | ||
{ | ||
if (!HasResponseStarted) | ||
{ | ||
throw new InvalidOperationException(CoreStrings.StartAsyncBeforeGetMemory); | ||
} | ||
} | ||
|
||
public ValueTask<FlushResult> FlushPipeAsync(CancellationToken cancellationToken) | ||
{ | ||
if (!HasResponseStarted) | ||
|
@@ -1338,6 +1347,7 @@ public void Complete(Exception ex) | |
ApplicationAbort(); | ||
} | ||
} | ||
|
||
Output.Complete(); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -49,7 +49,6 @@ public static async Task EchoAppPipeWriter(HttpContext httpContext) | |
if (buffer.Length > 0) | ||
{ | ||
await request.Body.ReadUntilEndAsync(buffer).DefaultTimeout(); | ||
await response.StartAsync(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should have unrelated tests hit the inefficient code path. Tests like ChunkGetMemoryAndWriteWithoutStart should be sufficient. |
||
var memory = response.BodyWriter.GetMemory(buffer.Length); | ||
buffer.CopyTo(memory); | ||
response.BodyWriter.Advance(buffer.Length); | ||
|
Uh oh!
There was an error while loading. Please reload this page.