Skip to content

Commit 4a490d4

Browse files
committed
internal/http3: add Expect: 100-continue support to ClientConn
When sending a request containing the "Expect: 100-continue" header, ClientConn.RoundTrip will now only send the request body after receiving an HTTP 100 status response from the server. For golang/go#70914 Change-Id: Ib3acea68b078486bda96426952897c3f2d51b47b Reviewed-on: https://go-review.googlesource.com/c/net/+/742540 Reviewed-by: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Nicholas Husin <husin@google.com>
1 parent 73fe701 commit 4a490d4

File tree

4 files changed

+108
-4
lines changed

4 files changed

+108
-4
lines changed

http2/clientconn_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ func (rt *testRoundTrip) wantStatus(want int) {
437437
}
438438
}
439439

440-
// body reads the contents of the response body.
440+
// readBody reads the contents of the response body.
441441
func (rt *testRoundTrip) readBody() ([]byte, error) {
442442
t := rt.t
443443
t.Helper()

internal/http3/roundtrip.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"strconv"
1212
"sync"
1313

14+
"golang.org/x/net/http/httpguts"
1415
"golang.org/x/net/internal/httpcommon"
1516
)
1617

@@ -113,14 +114,18 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (_ *http.Response, err error)
113114
return nil, err
114115
}
115116

117+
is100ContinueReq := httpguts.HeaderValuesContainsToken(req.Header["Expect"], "100-continue")
116118
if encr.HasBody {
117-
// TODO: Defer sending the request body when "Expect: 100-continue" is set.
118119
rt.reqBody = req.Body
119120
rt.reqBodyWriter.st = st
120121
rt.reqBodyWriter.remain = contentLength
121122
rt.reqBodyWriter.flush = true
122123
rt.reqBodyWriter.name = "request"
123-
go copyRequestBody(rt)
124+
125+
if !is100ContinueReq {
126+
encr.HasBody = false
127+
go copyRequestBody(rt)
128+
}
124129
}
125130

126131
// Read the response headers.
@@ -138,7 +143,19 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (_ *http.Response, err error)
138143

139144
if statusCode >= 100 && statusCode < 199 {
140145
// TODO: Handle 1xx responses.
141-
continue
146+
switch statusCode {
147+
case 100:
148+
if encr.HasBody && is100ContinueReq {
149+
encr.HasBody = false
150+
go copyRequestBody(rt)
151+
continue
152+
}
153+
// If we did not send "Expect: 100-continue" request but
154+
// received status 100 anyways, just continue per usual and
155+
// let the caller decide what to do with the response.
156+
default:
157+
continue
158+
}
142159
}
143160

144161
// We have the response headers.

internal/http3/roundtrip_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,70 @@ func TestRoundTripRequestBodyErrorAfterHeaders(t *testing.T) {
352352
}
353353
})
354354
}
355+
356+
func TestRoundTripExpect100Continue(t *testing.T) {
357+
synctest.Test(t, func(t *testing.T) {
358+
tc := newTestClientConn(t)
359+
tc.greet()
360+
clientBody := []byte("client's body that will be sent later")
361+
serverBody := []byte("server's body")
362+
363+
// Client sends an Expect: 100-continue request.
364+
req, _ := http.NewRequest("PUT", "https://example.tld/", bytes.NewBuffer(clientBody))
365+
req.Header = http.Header{"Expect": {"100-continue"}}
366+
rt := tc.roundTrip(req)
367+
st := tc.wantStream(streamTypeRequest)
368+
369+
// Server reads the header.
370+
st.wantHeaders(nil)
371+
st.wantIdle("client has yet to send its body")
372+
373+
// Server responds with HTTP status 100.
374+
st.writeHeaders(http.Header{
375+
":status": []string{"100"},
376+
})
377+
378+
// Client sends its body after receiving HTTP status 100 response.
379+
st.wantData(clientBody)
380+
381+
// The server sends its response after getting the client's body.
382+
st.writeHeaders(http.Header{
383+
":status": []string{"200"},
384+
})
385+
st.writeData(serverBody)
386+
st.stream.stream.CloseWrite()
387+
388+
// Client receives the response from server.
389+
rt.wantStatus(200)
390+
rt.wantBody(serverBody)
391+
})
392+
}
393+
394+
func TestRoundTripExpect100ContinueRejected(t *testing.T) {
395+
synctest.Test(t, func(t *testing.T) {
396+
tc := newTestClientConn(t)
397+
tc.greet()
398+
399+
// Client sends an Expect: 100-continue request.
400+
req, _ := http.NewRequest("PUT", "https://example.tld/", bytes.NewBufferString("client's body"))
401+
req.Header = http.Header{"Expect": {"100-continue"}}
402+
rt := tc.roundTrip(req)
403+
st := tc.wantStream(streamTypeRequest)
404+
405+
// Server reads the header.
406+
st.wantHeaders(nil)
407+
st.wantIdle("client has yet to send its body")
408+
409+
// Server rejects it.
410+
st.writeHeaders(http.Header{
411+
":status": []string{"200"},
412+
})
413+
st.wantIdle("client does not send its body without getting status 100")
414+
serverBody := []byte("server's body")
415+
st.writeData(serverBody)
416+
st.stream.stream.CloseWrite()
417+
418+
rt.wantStatus(200)
419+
rt.wantBody(serverBody)
420+
})
421+
}

internal/http3/transport_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -454,6 +454,26 @@ func (rt *testRoundTrip) wantHeaders(want http.Header) {
454454
}
455455
}
456456

457+
// readBody reads the contents of the response body.
458+
func (rt *testRoundTrip) readBody() ([]byte, error) {
459+
t := rt.t
460+
t.Helper()
461+
return io.ReadAll(rt.response().Body)
462+
}
463+
464+
// wantBody consumes the a body and asserts that it is as expected.
465+
func (rt *testRoundTrip) wantBody(want []byte) {
466+
t := rt.t
467+
t.Helper()
468+
got, err := rt.readBody()
469+
if err != nil {
470+
t.Fatalf("unexpected error reading response body: %v", err)
471+
}
472+
if !bytes.Equal(got, want) {
473+
t.Fatalf("unexpected response body:\ngot: %q\nwant: %q", got, want)
474+
}
475+
}
476+
457477
func (tc *testClientConn) roundTrip(req *http.Request) *testRoundTrip {
458478
rt := &testRoundTrip{t: tc.t}
459479
go func() {

0 commit comments

Comments
 (0)