Repository: quic-go/quic-go
Pull request #4378 (2024-03-22 00:37:52)

http3: make it possible to send HEAD requests in 0-RTT

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/quic-go/quic-go/pull/4378?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 84.90%. Comparing base [(`e14dd2f`)](https://app.codecov.io/gh/quic-go/quic-go/commit/e14dd2fe5b79d1ca1624d4914feb47d87814ef75?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go) to head [(`786362f`)](https://app.codecov.io/gh/quic-go/quic-go/pull/4378?dropdown=coverage&src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).


<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master    #4378      +/-   ##
==========================================
+ Coverage   84.88%   84.90%   +0.02%     
==========================================
  Files         151      151              
  Lines       14334    14337       +3     
==========================================
+ Hits        12167    12172       +5     
+ Misses       1668     1666       -2     
  Partials      499      499              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/quic-go/quic-go/pull/4378?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=quic-go).


Patch:
diff --git a/http3/client.go b/http3/client.go
index 402fd0df..b25d4f65 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():


Test patch:
diff --git a/http3/client_test.go b/http3/client_test.go
index 5665aa41..badd7d4d 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))