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

Reorder request draining logic for responses without response bodies #2102

Description

@Tratcher

For requests with bodies and responses without them, kestrel delays sending the response until the request is drained. In the process it can also send a 100 Continue contrary to the applications intentions.

Aside from adding extra latency to the response this also has functional impacts for clients, especially for authenticated upload scenarios.

Expect: 100-continue is designed to allow the server application to specify if the body will be accepted or not before the body gets sent. This allows the application to preemptively respond with failures like 401 for auth and can save significant time and bandwidth. When the application completes the response without reading the body the server has a choice, it can close the connection or drain the body. When the client receives the final status code (401, 404) instead of the 100 Continue it has three choices. 1) It can close the connection rather than upload the data, or 2) for chunked it can send just the chunked terminator, or 3) for content-length it sends the full body. For small bodies it's worth sending it to avoid the cost of creating a new connection, but for large bodies is far more economical close the connection.

When kestrel sends the 100 Continue without prompting from the application it negates the purpose of 100 Continue. The client does not get to see the final status code and does not get to make any of their choices. If the client did not buffer their data then they may not even be able to replay the request.

The reason given for Kestrel's current behavior of delaying responses until the request is drained is to allow it to replace the response with an error code in the event of a request body error. What errors are expected from draining the request body? Client disconnect, timeouts, or a protocol violation seem like the only errors you could hit during drain. Those all warrant closing the connection anyways, there's limited value in flowing an error to the client in those cases, especially when the application disregarded the request body.

The reason kestrel sends the 100 continue is to encourage the client to send the body in a timely fashion so kestrel can drain it. It does this because it has not sent the final status code provided by the app. If it send the apps response then the client would also be unblocked.

Workaround: Kestrel does not delay or send the 100 continue if the application flushes the response headers. This is not a long term solution, there are many components in the stack that send responses without bodies. it also causes your responses to show as having empty chunked bodies rather than no body. When is the chunked body completed? Before or after the request body drain?

Recommended changes:
When the request pipeline unwinds without draining the request body or staring the response then kestrel should send the response headers before draining the request. It may also make a choice to add Connection: close if it thinks draining the request body is not worth it (e.g. large content-length remaining).

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions