-
Notifications
You must be signed in to change notification settings - Fork 521
Set StatusCode before disposing HttpContext (#876) #993
Conversation
} | ||
|
||
await ProduceEnd(); | ||
_application.DisposeContext(context, _applicationException); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be called even if _requestAborted
is true. This can be the case if the app calls HttpContext.Abort()
. Might want to add a test for that.
It will also probably require another finally block because messageBody.Consume();
can throw too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currently, if the app calls HttpContext.Abort()
, then HttpContext.Response.StatusCode
will be 200
when it's disposed (which appears in the logs). I agree this seems wrong. However, if the app aborts the request then no status code is sent to the client, so the status of the request is technically undefined.
What should HttpContext.Response.StatusCode
be in this case? We can set it to 500
which is arguably better than 200
, but something like null
or -1
(property is an int
rather than HttpStatusCode
) might be more appropriate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
503? Service Unavailable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps HttpContext.Response.StatusCode
should just be ignored in this case, and the logging code should check HttpContext.RequestAborted
, and display something other than the status code (e.g. "aborted"
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benaadams: I think HttpContext.Response.StatusCode
should remain whatever it was before the request was aborted, which is already working with my current changes. This seems like a separate issue of whether logging should differentiate between completed and aborted requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One edge case currently not handled, is an app which calls HttpContext.Abort()
and then throws an exception, which should probably result in HttpContext.Response.StatusCode = 500
. We can try to call ProduceEnd()
even if the request has been aborted, which will correct set the status code, but I'm not sure if ProduceEnd()
assumes the request was not aborted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not changing it to something related to it being aborted might confuse automated log parsing?
Check the failures on Travis - you might need to update those tests that failed. |
} | ||
|
||
// ProduceEnd() must be called before _application.DisposeContext(), to ensure | ||
// HttpContext.Response.StatusCode is correctly set when | ||
// IHttpContextFactory.Dispose(HttpContext) is called. | ||
await ProduceEnd(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EngineTests.NoResponseSentWhenConnectionIsClosedByServerBeforeClientFinishesSendingRequest
seems to be failing because ProduceEnd
was moved outside the if block.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure that's the case. That test calls Abort()
, which will close the connection, so anything written after that (the error response written by ProduceEnd()
should not be received by the client.
760225c
to
b822cd2
Compare
// IHttpContextFactory.Dispose(HttpContext) is called. | ||
await ProduceEnd(); | ||
} | ||
finally |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks a bit awful. An alternative would be to call DisposeContext()
is the catch
block for BadHttpRequestException
(this is addressing what @halter73 pointed out - DisposeContext()
won't be called if messageBody.Consume()
throws because of a malformed request.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can get rid of the finally above. I've wanted to clean up the control flow of this method for a while. It's not exactly buggy, but i's confusing because it's not immediately clear what we expect can throw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, not this finally, but the one after catch { ReportApplicationError }.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My suggestion is that we have a new try
immediately after _application.CreateContext(this);
. This try
block would continue all the way until this finally.
Yes. This means you would have try { try { await _application.ProcessRequestAsync(context).ConfigureAwait(false); } catch { ...
, but at least that's easier to follow.
Ping. |
b822cd2
to
81a182a
Compare
|
81a182a
to
25b2c94
Compare
Updated. @halter73 I'm setting status code to 0 when the request is aborted and no response has started. |
25b2c94
to
80b7252
Compare
} | ||
} | ||
|
||
StopStreams(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think StopStreams() should go in the finally?
{ | ||
// If the request was aborted and no response was sent, there's no | ||
// meaningful status code to log. | ||
StatusCode = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this interact with TryProduceInvalidRequestResponse()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking I should move await TryProduceInvalidRequestResponse();
inside the check for _requestAborted == 0
.
}, | ||
expectedClientStatusCode: null, | ||
expectedServerStatusCode: HttpStatusCode.BadRequest, | ||
sendMalformedRequest: true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a test for when the app actual reads the request body, and the swallows the exception or doesn't.
5b03f68
to
f7b7319
Compare
I'll add a test that validates that an app can write it's own 400 response if it chooses to handle a |
f7b7319
to
15dae1a
Compare
_requestRejected = true; | ||
|
||
Log.ConnectionBadRequest(ConnectionId, ex); | ||
_requestRejectedException = ex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't _requestRejectedException
be set in RejectRequest
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is the appropriate place to set that. Connection
calls SetBadRequestState()
when a time out occurs, and that will be picked up by later by TryProduceInvalidRequestResponse()
. Besides, _requestRejectedException
is state too.
2fe2be0
to
2164f8a
Compare
SingleErrorResponseSentWhenAppSwallowsBadRequestException is failed on the last commit on AppVeyor. It was testing this commit where the TCP loopback fast path code was already removed. |
It's a weird issue though since the response data was sent by the server (can see it in Message Analyzer), but it looks as if you can't read buffered data if the connection was reset. The problem is probably all the way down in winsock - we've seen a similar issue in the past. |
The test failure is not related to this PR. Any more feedback? |
2164f8a
to
5b79e7e
Compare
{ | ||
// End the connection for non keep alive as data incoming may have been thrown off | ||
return; | ||
SetBadRequestState(ex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comment that this is for bad request data in the request body (not start line/headers) and SetBadRequestState can't be called later because _application.DisposeContext logs the status code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added comments.
|
a868f6f
to
7858479
Compare
Recommend ignoring whitespace when viewing the diff: https://github.com/aspnet/KestrelHttpServer/pull/993/files?w=0
Addresses #876
@halter73, @CesarBS, @JunTaoLuo