From 603e07779ab77d43b01852ef3e41d2a7585ba646 Mon Sep 17 00:00:00 2001 From: Marten Seemann Date: Sat, 23 Mar 2024 08:24:09 +1000 Subject: [PATCH] http3: make it possible to send HEAD requests in 0-RTT (#4378) --- http3/client.go | 18 +++++++++++++----- http3/client_test.go | 37 +++++++++++++++++++++---------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/http3/client.go b/http3/client.go index 402fd0dfe..b25d4f65a 100644 --- a/http3/client.go +++ b/http3/client.go @@ -21,9 +21,14 @@ import ( "github.com/quic-go/qpack" ) -// MethodGet0RTT allows a GET request to be sent using 0-RTT. -// Note that 0-RTT data doesn't provide replay protection. -const MethodGet0RTT = "GET_0RTT" +const ( + // MethodGet0RTT allows a GET request to be sent using 0-RTT. + // Note that 0-RTT doesn't provide replay protection and should only be used for idempotent requests. + MethodGet0RTT = "GET_0RTT" + // MethodHead0RTT allows a HEAD request to be sent using 0-RTT. + // Note that 0-RTT doesn't provide replay protection and should only be used for idempotent requests. + MethodHead0RTT = "HEAD_0RTT" +) const ( defaultUserAgent = "quic-go HTTP/3" @@ -298,9 +303,12 @@ func (c *client) roundTripOpt(req *http.Request, opt RoundTripOpt) (*http.Respon conn := *c.conn.Load() // Immediately send out this request, if this is a 0-RTT request. - if req.Method == MethodGet0RTT { + switch req.Method { + case MethodGet0RTT: req.Method = http.MethodGet - } else { + case MethodHead0RTT: + req.Method = http.MethodHead + default: // wait for the handshake to complete select { case <-conn.HandshakeComplete(): diff --git a/http3/client_test.go b/http3/client_test.go index 5665aa418..badd7d4d3 100644 --- a/http3/client_test.go +++ b/http3/client_test.go @@ -807,22 +807,27 @@ var _ = Describe("Client", func() { Expect(err).To(MatchError(testErr)) }) - It("performs a 0-RTT request", func() { - testErr := errors.New("stream open error") - req.Method = MethodGet0RTT - // don't EXPECT any calls to HandshakeComplete() - conn.EXPECT().OpenStreamSync(context.Background()).Return(str, nil) - buf := &bytes.Buffer{} - str.EXPECT().Write(gomock.Any()).DoAndReturn(buf.Write).AnyTimes() - str.EXPECT().Close() - str.EXPECT().CancelWrite(gomock.Any()) - str.EXPECT().Read(gomock.Any()).DoAndReturn(func([]byte) (int, error) { - return 0, testErr - }) - _, err := cl.RoundTripOpt(req, RoundTripOpt{}) - Expect(err).To(MatchError(testErr)) - Expect(decodeHeader(buf)).To(HaveKeyWithValue(":method", "GET")) - }) + DescribeTable( + "performs a 0-RTT request", + func(method, serialized string) { + testErr := errors.New("stream open error") + req.Method = method + // don't EXPECT any calls to HandshakeComplete() + conn.EXPECT().OpenStreamSync(context.Background()).Return(str, nil) + buf := &bytes.Buffer{} + str.EXPECT().Write(gomock.Any()).DoAndReturn(buf.Write).AnyTimes() + str.EXPECT().Close() + str.EXPECT().CancelWrite(gomock.Any()) + str.EXPECT().Read(gomock.Any()).DoAndReturn(func([]byte) (int, error) { + return 0, testErr + }) + _, err := cl.RoundTripOpt(req, RoundTripOpt{}) + Expect(err).To(MatchError(testErr)) + Expect(decodeHeader(buf)).To(HaveKeyWithValue(":method", serialized)) + }, + Entry("GET", MethodGet0RTT, http.MethodGet), + Entry("HEAD", MethodHead0RTT, http.MethodHead), + ) It("returns a response", func() { rspBuf := bytes.NewBuffer(getResponse(418))