Skip to content

net/http: expect: 100-continue handling is broken in various ways #67555

@neild

Description

@neild

This is a follow-up to #67382. See that issue for some prior discussion. (That issue is also full of watchflakes reports, so I'm letting it be about the test flakiness. This issue is about the underlying problem.)

An HTTP/1 or HTTP/2 client can send an Expect: 100-continue header in a request to indicate that it might not send the request body until the server asks for it. The client is permitted to send the request body anyway, either immediately or after a delay.

A server may respond to a Expect: 100-continue request with an informational 100 status, indicating that it would like the client to send the request body. A server may also respond with a non-1xx status, without waiting for the request body.

For HTTP/1, a client must send the body for a request or close the connection. If a client sends a Expect: 100-continue request and receives a non-1xx response from the server, it still needs to finish sending the current request's body before sending any further requests on the connection.

For HTTP/2, a client can reset a request's stream and continue to use the connection without sending the body.

This issue is about how our client and server implementations handle the case of a server responding to an Expect: 100-continue request with a non-1xx response.

Client: If we receive a non-1xx response to an Expect: 100-continue request, should we send the request body on the assumption that the server might want it? Or should we assume that receiving response headers from the server indicates that the server didn't need the body? Should we modify this behavior for different response codes? A 200 OK might indicate that the server is going to use the request body, but a 403 Forbidden probably does not. Should we modify this behavior based on the presence or absence of a Connection: close header in the response? (Note that for HTTP/1 requests, if we do not send the request body, we may not reuse the connection for further requests.)

Server: When handling an Expect: 100-continue request, we automatically send a 100 Continue response if the server handler reads from the request body. What should we do if the server handler calls WriteHeader to send a non-1xx response, and then reads from the request body? Should we assume the server handler knows what it's doing, and trust that the client is going to send us a request body? Or should we return an error from the body read?

Our current behavior is inconsistent and occasionally wrong.

The HTTP/1 client does not send the request body if it receives a non-1xx response before ExpectContinueTimeout expires. This can leave the connection in an invalid state. (Server responds with a non-1xx response and attempts to read the request body; client reuses the connection for another request without sending the body.)

The HTTP/2 client always sends the request body after ExpectContinueTimeout, regardless of whether it has received a non-1xx response or not. If the server is trying to read the body, it will eventually get it, but only after a timeout.

Both HTTP/1 and HTTP/2 servers appear to permit reading from a request body even after sending a non-1xx response (and no 100 Continue response).

I'm not certain yet what we should be doing, but at a minimum:

  • The HTTP/1 client should not send any further requests on a connection if it decides not to send a request body.
  • The HTTP/2 client should disable the ExpectContinueTimeout timer after receiving a non-1xx response. It should either send or not send the request body, but it shouldn't keep delaying.
  • We should make an intentional decision on whether a server handler reading from a Expect: 100-continue request body after sending response headers gets an error or not.

Metadata

Metadata

Assignees

Labels

NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions