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

Handle non-body responses #1134

Merged
merged 2 commits into from
Oct 3, 2016
Merged

Handle non-body responses #1134

merged 2 commits into from
Oct 3, 2016

Conversation

cesarblum
Copy link
Contributor

@cesarblum cesarblum commented Sep 30, 2016

Rebased @benaadams's #962 and made some changes.

Like the original PR, this addresses #952.

@halter73 @benaadams @Tratcher

@halter73
Copy link
Member

Let's close #962 then.

@cesarblum
Copy link
Contributor Author

Updated.

if (_keepAlive && hasConnection)
{
var connectionValue = responseHeaders.HeaderConnection.ToString();
_keepAlive = connectionValue.Equals("keep-alive", StringComparison.OrdinalIgnoreCase);
}

if (!responseHeaders.HasTransferEncoding && !responseHeaders.HasContentLength)
// Set whether response can have body
_canHaveBody = StatusCanHaveBody(StatusCode) && Method != "HEAD";
Copy link
Member

Choose a reason for hiding this comment

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

Since 101 status can have a body of sorts, I would prefer _canHaveBody is true in that case. When deciding whether or not to set Content-Length: 0 we can check for 101 specifically.

{
// Don't set the Content-Length or Transfer-Encoding headers
// automatically for HEAD requests or 101, 204, 205, 304 responses.
Copy link
Member

Choose a reason for hiding this comment

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

I would like to keep this comment somewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will move above if (_canHaveBody).

else
{
// Don't set the Content-Length or Transfer-Encoding headers
// automatically for HEAD requests or 204, 205, 304 responses.
Copy link
Member

Choose a reason for hiding this comment

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

This comment doesn't makes sense here since no header is being automatically set.

Also, why not reject 101 responses with a Transfer-Encoding?
And why not reject 101/204/205/304 responses with a content-length. It looks like that's also illegal.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can 101/204/205/304 have a Content-Length of 0 (if set)?

Copy link
Member

Choose a reason for hiding this comment

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

https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5

If the conditional GET used a strong cache validator (see section 13.3.3), the response SHOULD NOT include other entity-headers. Otherwise (i.e., the conditional GET used a weak validator), the response MUST NOT include other entity-headers; this prevents inconsistencies between cached entity-bodies and updated headers.

Entity-headers include Content-Length, but I don't know anything about strong cache validators, so I guess there is some wiggle room. The other status codes don't seem to mandate anything.

Where is the spec requiring that Transfer-Encoding header can't be sent? Like Content-Length it doesn't really make sense, and we wouldn't set it automatically, but where is it explicitely prohibited?

Copy link
Contributor

Choose a reason for hiding this comment

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

https://tools.ietf.org/html/rfc7230#section-3.3.1

Transfer-Encoding MAY be sent in a response to a HEAD request or in a 304 (Not Modified) response (Section 4.1 of [RFC7232]) to a GET request, neither of which includes a message body, to indicate that the origin server would have applied a transfer coding to the message body if the request had been an unconditional GET. This indication is not required, however, because any recipient on the response chain (including the origin server) can remove transfer codings when they are not needed.

A server MUST NOT send a Transfer-Encoding header field in any response with a status code of 1xx (Informational) or 204 (No Content). A server MUST NOT send a Transfer-Encoding header field in any 2xx (Successful) response to a CONNECT request (Section 4.3.6 of [RFC7231]).

Has lots more here

A sender MUST NOT send a Content-Length header field in any message that contains a Transfer-Encoding header field.

A user agent SHOULD send a Content-Length in a request message when no Transfer-Encoding is sent and the request method defines a meaning for an enclosed payload body. For example, a Content-Length header field is normally sent in a POST request even when the value is 0 (indicating an empty payload body). A user agent SHOULD NOT send a Content-Length header field when the request message does not contain a payload body and the method semantics do not anticipate such a body.

A server MAY send a Content-Length header field in a response to a HEAD request (Section 4.3.2 of [RFC7231]); a server MUST NOT send Content-Length in such a response unless its field-value equals the decimal number of octets that would have been sent in the payload body of a response if the same request had used the GET method.

A server MAY send a Content-Length header field in a 304 (Not Modified) response to a conditional GET request (Section 4.1 of [RFC7232]); a server MUST NOT send Content-Length in such a response unless its field-value equals the decimal number of octets that would have been sent in the payload body of a 200 (OK) response to the same request.

etc

Copy link
Contributor

Choose a reason for hiding this comment

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

Note: Not saying my logic was completely right according to spec - it seems to have got bigger since I read it last (which it also obviously hasn't... 😟)

if (Method == "HEAD")
{
// Don't write to body for HEAD requests.
Log.ConnectionHeadResponseBodyWrite(ConnectionId, count);
Copy link
Member

Choose a reason for hiding this comment

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

Can we have a flag so this is only logged once per request/response? Maybe log it at the end of the response so we can count the size of all the writes.

@cesarblum
Copy link
Contributor Author

@halter73 I've addressed your feedback.

@@ -548,9 +548,6 @@ public void ConnectionCanReadAndWrite(TestServiceContext testContext)
"POST / HTTP/1.1",
"Content-Length: 3",
"",
"101POST / HTTP/1.1",
"Content-Length: 3",
"",
Copy link
Member

Choose a reason for hiding this comment

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

It's weird to set a 101 response and complete without writing anything, but this is still a valid test case right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

101 is now closed after response so that made this test fail. That's why I added the new test.

{
await connection.SendEnd(
"GET / HTTP/1.0",
"Connection: kee-alive",
Copy link
Member

Choose a reason for hiding this comment

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

kee-alive

@@ -75,6 +75,8 @@ public abstract partial class Frame : IFrameControl
protected readonly long _keepAliveMilliseconds;
private readonly long _requestHeadersTimeoutMilliseconds;

private int _responseBytesWritten;
Copy link
Member

Choose a reason for hiding this comment

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

This should be long. Maybe call this _responseBytesIgnored since we're only using it to log the number of bytes not written.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll use it when fixing #175, so less diff when I make that change 😄

@@ -542,6 +547,8 @@ public Task WriteAsync(ArraySegment<byte> data, CancellationToken cancellationTo
return WriteAsyncAwaited(data, cancellationToken);
}

_responseBytesWritten += 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.

Could this be done just in the !_canHaveBody case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above.

@cesarblum cesarblum force-pushed the cesarbs/handle-non-body-response branch from db1b445 to 9ec8bf0 Compare October 3, 2016 22:58
@cesarblum
Copy link
Contributor Author

Updated.

@halter73
Copy link
Member

halter73 commented Oct 3, 2016

I would also like to see the /plaintext benchmark results for this. :shipit: If there is no degradation.

@cesarblum
Copy link
Contributor Author

dev

[04:39:53.046] Starting scenario Plaintext on benchmark client...
[04:40:04.074] Scenario Plaintext completed on benchmark client
[04:40:04.074] RPS: 1185406.81

PR

[04:40:46.510] Starting scenario Plaintext on benchmark client...
[04:40:57.537] Scenario Plaintext completed on benchmark client
[04:40:57.537] RPS: 1194005.61

Usual fluctuation seen across multiple runs.

- Add functional tests
- Remove BadHttpResponse, ResponseRejectionReasons and TestFrameProtectedMembers
- Chunk non-keepalive HTTP/1.1 responses
- Set _canHaveBody to true when StatusCode == 101
- Add test to check that upgraded connections are always closed when the app
  is done
- Log writes on responses to HEAD requests only once.
@cesarblum cesarblum force-pushed the cesarbs/handle-non-body-response branch from 9ec8bf0 to 4117ad8 Compare October 3, 2016 23:48
@cesarblum cesarblum merged commit 4117ad8 into dev Oct 3, 2016
@cesarblum cesarblum deleted the cesarbs/handle-non-body-response branch October 3, 2016 23:53
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.

4 participants