Skip to content

Commit cf501e0

Browse files
committed
net/http: send body or close connection on expect-100-continue requests
When sending a request with an "Expect: 100-continue" header, we must send the request body before sending any further requests on the connection. When receiving a non-1xx response to an "Expect: 100-continue" request, send the request body if the connection isn't being closed after processing the response. In other words, if either the request or response contains a "Connection: close" header, then skip sending the request body (because the connection will not be used for further requests), but otherwise send it. Correct a comment on the server-side Expect: 100-continue handling that implied sending the request body is optional. It isn't. For #67555 Change-Id: Ia2f12091bee697771087f32ac347509ec5922d54 Reviewed-on: https://go-review.googlesource.com/c/go/+/591255 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]>
1 parent 2f6ba0c commit cf501e0

File tree

3 files changed

+164
-97
lines changed

3 files changed

+164
-97
lines changed

src/net/http/server.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,16 +1399,21 @@ func (cw *chunkWriter) writeHeader(p []byte) {
13991399

14001400
// If the client wanted a 100-continue but we never sent it to
14011401
// them (or, more strictly: we never finished reading their
1402-
// request body), don't reuse this connection because it's now
1403-
// in an unknown state: we might be sending this response at
1404-
// the same time the client is now sending its request body
1405-
// after a timeout. (Some HTTP clients send Expect:
1406-
// 100-continue but knowing that some servers don't support
1407-
// it, the clients set a timer and send the body later anyway)
1408-
// If we haven't seen EOF, we can't skip over the unread body
1409-
// because we don't know if the next bytes on the wire will be
1410-
// the body-following-the-timer or the subsequent request.
1411-
// See Issue 11549.
1402+
// request body), don't reuse this connection.
1403+
//
1404+
// This behavior was first added on the theory that we don't know
1405+
// if the next bytes on the wire are going to be the remainder of
1406+
// the request body or the subsequent request (see issue 11549),
1407+
// but that's not correct: If we keep using the connection,
1408+
// the client is required to send the request body whether we
1409+
// asked for it or not.
1410+
//
1411+
// We probably do want to skip reusing the connection in most cases,
1412+
// however. If the client is offering a large request body that we
1413+
// don't intend to use, then it's better to close the connection
1414+
// than to read the body. For now, assume that if we're sending
1415+
// headers, the handler is done reading the body and we should
1416+
// drop the connection if we haven't seen EOF.
14121417
if ecr, ok := w.req.Body.(*expectContinueReader); ok && !ecr.sawEOF.Load() {
14131418
w.closeAfterReply = true
14141419
}

src/net/http/transport.go

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2397,17 +2397,12 @@ func (pc *persistConn) readResponse(rc requestAndChan, trace *httptrace.ClientTr
23972397
return
23982398
}
23992399
resCode := resp.StatusCode
2400-
if continueCh != nil {
2401-
if resCode == 100 {
2402-
if trace != nil && trace.Got100Continue != nil {
2403-
trace.Got100Continue()
2404-
}
2405-
continueCh <- struct{}{}
2406-
continueCh = nil
2407-
} else if resCode >= 200 {
2408-
close(continueCh)
2409-
continueCh = nil
2400+
if continueCh != nil && resCode == StatusContinue {
2401+
if trace != nil && trace.Got100Continue != nil {
2402+
trace.Got100Continue()
24102403
}
2404+
continueCh <- struct{}{}
2405+
continueCh = nil
24112406
}
24122407
is1xx := 100 <= resCode && resCode <= 199
24132408
// treat 101 as a terminal status, see issue 26161
@@ -2430,6 +2425,25 @@ func (pc *persistConn) readResponse(rc requestAndChan, trace *httptrace.ClientTr
24302425
if resp.isProtocolSwitch() {
24312426
resp.Body = newReadWriteCloserBody(pc.br, pc.conn)
24322427
}
2428+
if continueCh != nil {
2429+
// We send an "Expect: 100-continue" header, but the server
2430+
// responded with a terminal status and no 100 Continue.
2431+
//
2432+
// If we're going to keep using the connection, we need to send the request body.
2433+
// Tell writeLoop to skip sending the body if we're going to close the connection,
2434+
// or to send it otherwise.
2435+
//
2436+
// The case where we receive a 101 Switching Protocols response is a bit
2437+
// ambiguous, since we don't know what protocol we're switching to.
2438+
// Conceivably, it's one that doesn't need us to send the body.
2439+
// Given that we'll send the body if ExpectContinueTimeout expires,
2440+
// be consistent and always send it if we aren't closing the connection.
2441+
if resp.Close || rc.treq.Request.Close {
2442+
close(continueCh) // don't send the body; the connection will close
2443+
} else {
2444+
continueCh <- struct{}{} // send the body
2445+
}
2446+
}
24332447

24342448
resp.TLS = pc.tlsState
24352449
return

src/net/http/transport_test.go

Lines changed: 125 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1185,94 +1185,142 @@ func testTransportGzip(t *testing.T, mode testMode) {
11851185
}
11861186
}
11871187

1188-
// If a request has Expect:100-continue header, the request blocks sending body until the first response.
1189-
// Premature consumption of the request body should not be occurred.
1190-
func TestTransportExpect100Continue(t *testing.T) {
1191-
run(t, testTransportExpect100Continue, []testMode{http1Mode})
1188+
// A transport100Continue test exercises Transport behaviors when sending a
1189+
// request with an Expect: 100-continue header.
1190+
type transport100ContinueTest struct {
1191+
t *testing.T
1192+
1193+
reqdone chan struct{}
1194+
resp *Response
1195+
respErr error
1196+
1197+
conn net.Conn
1198+
reader *bufio.Reader
11921199
}
1193-
func testTransportExpect100Continue(t *testing.T, mode testMode) {
1194-
ts := newClientServerTest(t, mode, HandlerFunc(func(rw ResponseWriter, req *Request) {
1195-
switch req.URL.Path {
1196-
case "/100":
1197-
// This endpoint implicitly responds 100 Continue and reads body.
1198-
if _, err := io.Copy(io.Discard, req.Body); err != nil {
1199-
t.Error("Failed to read Body", err)
1200-
}
1201-
rw.WriteHeader(StatusOK)
1202-
case "/200":
1203-
// Go 1.5 adds Connection: close header if the client expect
1204-
// continue but not entire request body is consumed.
1205-
rw.WriteHeader(StatusOK)
1206-
case "/500":
1207-
rw.WriteHeader(StatusInternalServerError)
1208-
case "/keepalive":
1209-
// This hijacked endpoint responds error without Connection:close.
1210-
_, bufrw, err := rw.(Hijacker).Hijack()
1211-
if err != nil {
1212-
log.Fatal(err)
1213-
}
1214-
bufrw.WriteString("HTTP/1.1 500 Internal Server Error\r\n")
1215-
bufrw.WriteString("Content-Length: 0\r\n\r\n")
1216-
bufrw.Flush()
1217-
case "/timeout":
1218-
// This endpoint tries to read body without 100 (Continue) response.
1219-
// After ExpectContinueTimeout, the reading will be started.
1220-
conn, bufrw, err := rw.(Hijacker).Hijack()
1221-
if err != nil {
1222-
log.Fatal(err)
1223-
}
1224-
if _, err := io.CopyN(io.Discard, bufrw, req.ContentLength); err != nil {
1225-
t.Error("Failed to read Body", err)
1226-
}
1227-
bufrw.WriteString("HTTP/1.1 200 OK\r\n\r\n")
1228-
bufrw.Flush()
1229-
conn.Close()
1230-
}
12311200

1232-
})).ts
1201+
const transport100ContinueTestBody = "request body"
12331202

1234-
tests := []struct {
1235-
path string
1236-
body []byte
1237-
sent int
1238-
status int
1239-
}{
1240-
{path: "/100", body: []byte("hello"), sent: 5, status: 200}, // Got 100 followed by 200, entire body is sent.
1241-
{path: "/200", body: []byte("hello"), sent: 0, status: 200}, // Got 200 without 100. body isn't sent.
1242-
{path: "/500", body: []byte("hello"), sent: 0, status: 500}, // Got 500 without 100. body isn't sent.
1243-
{path: "/keepalive", body: []byte("hello"), sent: 0, status: 500}, // Although without Connection:close, body isn't sent.
1244-
{path: "/timeout", body: []byte("hello"), sent: 5, status: 200}, // Timeout exceeded and entire body is sent.
1203+
// newTransport100ContinueTest creates a Transport and sends an Expect: 100-continue
1204+
// request on it.
1205+
func newTransport100ContinueTest(t *testing.T, timeout time.Duration) *transport100ContinueTest {
1206+
ln := newLocalListener(t)
1207+
defer ln.Close()
1208+
1209+
test := &transport100ContinueTest{
1210+
t: t,
1211+
reqdone: make(chan struct{}),
12451212
}
12461213

1247-
c := ts.Client()
1248-
for i, v := range tests {
1249-
tr := &Transport{
1250-
ExpectContinueTimeout: 2 * time.Second,
1251-
}
1252-
defer tr.CloseIdleConnections()
1253-
c.Transport = tr
1254-
body := bytes.NewReader(v.body)
1255-
req, err := NewRequest("PUT", ts.URL+v.path, body)
1256-
if err != nil {
1257-
t.Fatal(err)
1258-
}
1214+
tr := &Transport{
1215+
ExpectContinueTimeout: timeout,
1216+
}
1217+
go func() {
1218+
defer close(test.reqdone)
1219+
body := strings.NewReader(transport100ContinueTestBody)
1220+
req, _ := NewRequest("PUT", "http://"+ln.Addr().String(), body)
12591221
req.Header.Set("Expect", "100-continue")
1260-
req.ContentLength = int64(len(v.body))
1222+
req.ContentLength = int64(len(transport100ContinueTestBody))
1223+
test.resp, test.respErr = tr.RoundTrip(req)
1224+
test.resp.Body.Close()
1225+
}()
12611226

1262-
resp, err := c.Do(req)
1263-
if err != nil {
1264-
t.Fatal(err)
1227+
c, err := ln.Accept()
1228+
if err != nil {
1229+
t.Fatalf("Accept: %v", err)
1230+
}
1231+
t.Cleanup(func() {
1232+
c.Close()
1233+
})
1234+
br := bufio.NewReader(c)
1235+
_, err = ReadRequest(br)
1236+
if err != nil {
1237+
t.Fatalf("ReadRequest: %v", err)
1238+
}
1239+
test.conn = c
1240+
test.reader = br
1241+
t.Cleanup(func() {
1242+
<-test.reqdone
1243+
tr.CloseIdleConnections()
1244+
got, _ := io.ReadAll(test.reader)
1245+
if len(got) > 0 {
1246+
t.Fatalf("Transport sent unexpected bytes: %q", got)
12651247
}
1266-
resp.Body.Close()
1248+
})
12671249

1268-
sent := len(v.body) - body.Len()
1269-
if v.status != resp.StatusCode {
1270-
t.Errorf("test %d: status code should be %d but got %d. (%s)", i, v.status, resp.StatusCode, v.path)
1271-
}
1272-
if v.sent != sent {
1273-
t.Errorf("test %d: sent body should be %d but sent %d. (%s)", i, v.sent, sent, v.path)
1250+
return test
1251+
}
1252+
1253+
// respond sends response lines from the server to the transport.
1254+
func (test *transport100ContinueTest) respond(lines ...string) {
1255+
for _, line := range lines {
1256+
if _, err := test.conn.Write([]byte(line + "\r\n")); err != nil {
1257+
test.t.Fatalf("Write: %v", err)
12741258
}
12751259
}
1260+
if _, err := test.conn.Write([]byte("\r\n")); err != nil {
1261+
test.t.Fatalf("Write: %v", err)
1262+
}
1263+
}
1264+
1265+
// wantBodySent ensures the transport has sent the request body to the server.
1266+
func (test *transport100ContinueTest) wantBodySent() {
1267+
got, err := io.ReadAll(io.LimitReader(test.reader, int64(len(transport100ContinueTestBody))))
1268+
if err != nil {
1269+
test.t.Fatalf("unexpected error reading body: %v", err)
1270+
}
1271+
if got, want := string(got), transport100ContinueTestBody; got != want {
1272+
test.t.Fatalf("unexpected body: got %q, want %q", got, want)
1273+
}
1274+
}
1275+
1276+
// wantRequestDone ensures the Transport.RoundTrip has completed with the expected status.
1277+
func (test *transport100ContinueTest) wantRequestDone(want int) {
1278+
<-test.reqdone
1279+
if test.respErr != nil {
1280+
test.t.Fatalf("unexpected RoundTrip error: %v", test.respErr)
1281+
}
1282+
if got := test.resp.StatusCode; got != want {
1283+
test.t.Fatalf("unexpected response code: got %v, want %v", got, want)
1284+
}
1285+
}
1286+
1287+
func TestTransportExpect100ContinueSent(t *testing.T) {
1288+
test := newTransport100ContinueTest(t, 1*time.Hour)
1289+
// Server sends a 100 Continue response, and the client sends the request body.
1290+
test.respond("HTTP/1.1 100 Continue")
1291+
test.wantBodySent()
1292+
test.respond("HTTP/1.1 200", "Content-Length: 0")
1293+
test.wantRequestDone(200)
1294+
}
1295+
1296+
func TestTransportExpect100Continue200ResponseNoConnClose(t *testing.T) {
1297+
test := newTransport100ContinueTest(t, 1*time.Hour)
1298+
// No 100 Continue response, no Connection: close header.
1299+
test.respond("HTTP/1.1 200", "Content-Length: 0")
1300+
test.wantBodySent()
1301+
test.wantRequestDone(200)
1302+
}
1303+
1304+
func TestTransportExpect100Continue200ResponseWithConnClose(t *testing.T) {
1305+
test := newTransport100ContinueTest(t, 1*time.Hour)
1306+
// No 100 Continue response, Connection: close header set.
1307+
test.respond("HTTP/1.1 200", "Connection: close", "Content-Length: 0")
1308+
test.wantRequestDone(200)
1309+
}
1310+
1311+
func TestTransportExpect100Continue500ResponseNoConnClose(t *testing.T) {
1312+
test := newTransport100ContinueTest(t, 1*time.Hour)
1313+
// No 100 Continue response, no Connection: close header.
1314+
test.respond("HTTP/1.1 500", "Content-Length: 0")
1315+
test.wantBodySent()
1316+
test.wantRequestDone(500)
1317+
}
1318+
1319+
func TestTransportExpect100Continue500ResponseTimeout(t *testing.T) {
1320+
test := newTransport100ContinueTest(t, 5*time.Millisecond) // short timeout
1321+
test.wantBodySent() // after timeout
1322+
test.respond("HTTP/1.1 200", "Content-Length: 0")
1323+
test.wantRequestDone(200)
12761324
}
12771325

12781326
func TestSOCKS5Proxy(t *testing.T) {

0 commit comments

Comments
 (0)