Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.

Don't emit TE header or body for non-body responses #962

Closed
wants to merge 1 commit into from

Conversation

benaadams
Copy link
Contributor

@benaadams benaadams commented Jul 9, 2016

Now doesn't emit TE or body for non body responses

Resolves #952

@Tratcher
Copy link
Member

Tratcher commented Jul 9, 2016

So this allows 304 responses with bodies & connection: close? How well do browsers handle that?

@benaadams
Copy link
Contributor Author

Is progress anyway... Will add an IO exception on write if status code doesn't allow body? Or just dump the writes?

@benaadams
Copy link
Contributor Author

benaadams commented Jul 9, 2016

Probably should dump the body so HEAD requests will work when they return content

@benaadams benaadams force-pushed the flush-304-fix branch 2 times, most recently from f6efde2 to f9656ef Compare July 9, 2016 13:59
@benaadams
Copy link
Contributor Author

Now doesn't emit TE or body for non body responses

@benaadams benaadams changed the title Don't set TE header for non-body responses Don't set TE header or body for non-body responses Jul 9, 2016
@benaadams benaadams changed the title Don't set TE header or body for non-body responses Don't emit TE header or body for non-body responses Jul 9, 2016
@@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about WriteAsyncAwaited?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

@Tratcher
Copy link
Member

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.

@cesarblum
Copy link
Contributor

@benaadams Can you add tests?

@benaadams
Copy link
Contributor Author

@Tratcher Added logging as per ConnectionDisconnectedWrite
@CesarBS will do

if (!_canHaveBody)
{
// Don't write to body for HEAD requests or 101, 204, 205, 304 responses.
LogNonBodyRequestWrite(data.Count);
Copy link
Member

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.

Copy link
Contributor Author

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?

@benaadams
Copy link
Contributor Author

@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?

@benaadams benaadams force-pushed the flush-304-fix branch 8 times, most recently from 14caa67 to 4741968 Compare July 19, 2016 23:54
@benaadams
Copy link
Contributor Author

@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)

@benaadams benaadams changed the title Don't emit TE header or body for non-body responses [Wip] Don't emit TE header or body for non-body responses Jul 22, 2016
frame.HttpVersion = "HTTP/1.1";
((IHttpResponseFeature)frame).StatusCode = 304;

//Act
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: space after //

@cesarblum
Copy link
Contributor

@benaadams I've added a few more comments. Address those and I think we're good to go 👍

@benaadams
Copy link
Contributor Author

@CesarBS probably want to do another round on the exceptions when best approach in coreclr becomes more apparent.

@benaadams
Copy link
Contributor Author

Microsoft.AspNetCore.Server.KestrelTests.SocketOutputTests.WritesDontCompleteImmediatelyWhenTooManyBytesAreAlreadyPreCompleted [FAIL] #1002

@benaadams benaadams changed the title [Wip] Don't emit TE header or body for non-body responses Don't emit TE header or body for non-body responses Aug 8, 2016

namespace Microsoft.AspNetCore.Server.Kestrel
{
public sealed class BadHttpResponseException : InvalidOperationException
Copy link
Contributor

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?

Copy link
Member

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.

Copy link
Contributor Author

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?

Copy link
Member

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 InvalidOperationExceptions 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.

@benaadams
Copy link
Contributor Author

Rebased, some merge clash bugs still

@cesarblum
Copy link
Contributor

I have a fix for the keep alive test failure here: #1090

@cesarblum
Copy link
Contributor

@benaadams Can you rebase?

@benaadams
Copy link
Contributor Author

I think rebase is going to be messy...

@cesarblum
Copy link
Contributor

One thing I've noticed: StatusCanHaveBody should be changed to allow 101 to have a body. Otherwise WebSockets are broken because Write and WriteAsync will throw on writes.

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.

@benaadams
Copy link
Contributor Author

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)
Copy link
Contributor

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.

Copy link
Contributor Author

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?

Copy link
Member

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?

Copy link
Contributor Author

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?

Copy link
Contributor

@cesarblum cesarblum Sep 30, 2016

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?

Copy link
Contributor Author

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?

Copy link
Member

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?

Copy link
Contributor Author

@benaadams benaadams Sep 30, 2016

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

Copy link
Contributor

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.

Copy link
Member

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.

@cesarblum cesarblum mentioned this pull request Sep 30, 2016
1 task
@cesarblum
Copy link
Contributor

Closing in favor of #1134.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants