diff --git a/http3/client.go b/http3/client.go index 117becab..f5ae8332 100644 --- a/http3/client.go +++ b/http3/client.go @@ -15,6 +15,10 @@ import ( "github.com/marten-seemann/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 defaultUserAgent = "quic-go HTTP/3" const defaultMaxResponseHeaderBytes = 10 * 1 << 20 // 10 MB @@ -150,11 +154,16 @@ func (c *client) RoundTrip(req *http.Request) (*http.Response, error) { return nil, c.handshakeErr } - // wait for the handshake to complete - select { - case <-c.session.HandshakeComplete().Done(): - case <-req.Context().Done(): - return nil, req.Context().Err() + // Immediately send out this request, if this is a 0-RTT request. + if req.Method == MethodGet0RTT { + req.Method = http.MethodGet + } else { + // wait for the handshake to complete + select { + case <-c.session.HandshakeComplete().Done(): + case <-req.Context().Done(): + return nil, req.Context().Err() + } } str, err := c.session.OpenStreamSync(req.Context()) diff --git a/http3/client_test.go b/http3/client_test.go index f7785f97..8b2dee27 100644 --- a/http3/client_test.go +++ b/http3/client_test.go @@ -187,6 +187,25 @@ var _ = Describe("Client", func() { Expect(err).ToNot(HaveOccurred()) }) + It("performs a 0-RTT request", func() { + testErr := errors.New("stream open error") + request.Method = MethodGet0RTT + // don't EXPECT any calls to HandshakeComplete() + sess.EXPECT().OpenStreamSync(context.Background()).Return(str, nil) + buf := &bytes.Buffer{} + str.EXPECT().Write(gomock.Any()).DoAndReturn(func(p []byte) (int, error) { + return buf.Write(p) + }).AnyTimes() + str.EXPECT().Close() + str.EXPECT().CancelWrite(gomock.Any()) + str.EXPECT().Read(gomock.Any()).DoAndReturn(func([]byte) (int, error) { + return 0, testErr + }) + _, err := client.RoundTrip(request) + Expect(err).To(MatchError(testErr)) + Expect(decodeHeader(buf)).To(HaveKeyWithValue(":method", "GET")) + }) + It("returns a response", func() { rspBuf := &bytes.Buffer{} rw := newResponseWriter(rspBuf, utils.DefaultLogger)