-
Notifications
You must be signed in to change notification settings - Fork 523
Don't emit TE header or body for non-body responses #962
Conversation
So this allows 304 responses with bodies & connection: close? How well do browsers handle that? |
Is progress anyway... Will add an IO exception on write if status code doesn't allow body? Or just dump the writes? |
Probably should dump the body so HEAD requests will work when they return content |
f6efde2
to
f9656ef
Compare
Now doesn't emit TE or body for non body responses |
@@ -486,6 +490,9 @@ public Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationTo | |||
return WriteAsyncAwaited(data, cancellationToken); | |||
} | |||
|
|||
// Don't write to body for HEAD requests or 101, 204, 205, 304 responses. | |||
if (!_canHaveBody) return TaskUtilities.CompletedTask; |
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.
What about WriteAsyncAwaited?
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
This is an interesting way to enforce the protocol. The downside is that it's silent and the user doesn't know what they've done wrong. E.g. they would keep trying to copy a 5gb file not realizing they're responding to a HEAD request. Something like this should at least be logged as Debug. |
@benaadams Can you add tests? |
if (!_canHaveBody) | ||
{ | ||
// Don't write to body for HEAD requests or 101, 204, 205, 304 responses. | ||
LogNonBodyRequestWrite(data.Count); |
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 throw if there is any actual data. Flush seems fine (though unnecessary), but not anything else.
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.
Question on whether GET responses can serve HEAD requests?
Can differentiate the response codes from method, so 101, 204, 205, 304 throw and HEAD eats?
@halter73 re: execptions, depends how you want HEAD requests to work; do they work automatically out of the box or does the user need to code a specific response for HEAD? |
14caa67
to
4741968
Compare
@CesarBS added tests - however it still needs test to check the socket output (e.g. that its sending 500s and not doing something strange when it errors) |
frame.HttpVersion = "HTTP/1.1"; | ||
((IHttpResponseFeature)frame).StatusCode = 304; | ||
|
||
//Act |
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.
nit: space after //
@benaadams I've added a few more comments. Address those and I think we're good to go 👍 |
0b9baf4
to
5f4e0f2
Compare
@CesarBS probably want to do another round on the exceptions when best approach in coreclr becomes more apparent. |
Microsoft.AspNetCore.Server.KestrelTests.SocketOutputTests.WritesDontCompleteImmediatelyWhenTooManyBytesAreAlreadyPreCompleted [FAIL] #1002 |
|
||
namespace Microsoft.AspNetCore.Server.Kestrel | ||
{ | ||
public sealed class BadHttpResponseException : InvalidOperationException |
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.
What is the rationale for having a new exception type instead of just throwing InvalidOperationException
?
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 exception type would only be appropriate for attempting to write a body when it's not allowed by the spec. In the other cases, like modifying the headers/statuscode/callbacks too late, I think a BadHttpResponseException
is confusing. I would prefer an InvalidOperationException
in all cases. It's not like this is an exception type you would have want to handle specifically. It should always be avoidable.
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.
Differentiate server created exceptions from user code created exceptions?
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 wouldn't consider these server created exceptions. These are application created exceptions. Most APIs will throw plain InvalidOperationException
s if called incorrectly.
I actually don't mind BadHttpResponseException
for the caller attempting to do things that would break the HTTP spec like setting Transfer-Encoding
and a non-body status. I personally wouldn't do it, but I don't mind.
Throwing a BadHttpResponseException
for calling OnStarted
too late or attempting to add response headers too late seems confusing and wrong though.
5f4e0f2
to
d8c57d2
Compare
Rebased, some merge clash bugs still |
I have a fix for the keep alive test failure here: #1090 |
@benaadams Can you rebase? |
I think rebase is going to be messy... |
One thing I've noticed: I've pulled this PR to my machine and I'm working on rebasing it and fixing any test failures. It has useful stuff for #175. |
Ok works for me 👍 |
{ | ||
// Since the app has completed and we are only now generating | ||
// the headers we can safely set the Content-Length to 0. | ||
responseHeaders.SetRawContentLength("0", _bytesContentLengthZero); | ||
} | ||
} | ||
else if(_keepAlive) |
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'm curious why you removed this condition in your change. I had to re-introduce the keep-alive check, otherwise some tests would fail.
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.
Is it relevant? Unless you are saying only keep-alive can be chunked?
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.
That's the current logic. Why bother chunking on Connection: close
responses?
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.
Haven't looked into it too closely; but couldn't you be returning a 200, chunked data and a Connection: close
?
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 have a test (ChunkedResponseTests.ResponsesAreNotChunkedAutomaticallyForHttp10RequestsAndHttp11NonKeepAliveRequests
) that specifically checks that we don't chunk HTTP/1.1 non-keepalive responses. Is that in the spec?
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.
You'd have to buffer all the data then to work out the size?
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 part of the spec for HTTP/1.1. It obviously is for HTTP/1.0 requests.
Chunked or not, the client won't work out the total size of the response until the connection closes. Why go through the extra cycles doing chunking for no apparent benefit?
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.
Oh, you mean because the connection is closing it doesn't need a Content-Length
or Transfer-Encoding
header because the FIN indicates that? How does the client detect a partial response?
e.g. exception thrown mid file read
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.
Agreed with @benaadams - if we don't chunk, the client has no way to tell whether the response ended or the connection closed abruptly. The client could in theory verify if it was a clean connection close or a reset, but we shouldn't count on that.
If it's not in the spec I say we should chunk HTTP/1.1 even on Connection: close
.
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.
Good point about application errors. That's a fact of life for HTTP/1.0, but reliably knowing that a response has gracefully closed is a benefit of chunked encoding even if it's a non keep-alive connection.
I'm now convinced about this changed. Just make sure we don't regress the behavior for HTTP/1.0 requests. It's easier to mess up now that all responses are HTTP/1.1.
Closing in favor of #1134. |
Now doesn't emit TE or body for non body responses
Resolves #952