internal/http3: add Server support for handling HEAD requests

When handling HEAD requests, Server will now always send an HTTP
response with an empty body, even if the HTTP handler would normally
write to the body.

For golang/go#70914

Change-Id: Id656a8f9901b97357a343e204761f6a47f87f9fe
Reviewed-on: https://go-review.googlesource.com/c/net/+/740160
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Nicholas Husin <husin@google.com>
This commit is contained in:
Nicholas S. Husin
2026-01-28 21:47:11 -05:00
committed by Nicholas Husin
parent 57ea86db08
commit 1973e8da2d
2 changed files with 41 additions and 17 deletions

View File

@@ -207,11 +207,21 @@ func (sc *serverConn) handleRequestStream(st *stream) error {
}
defer req.Body.Close()
responseWriter := sc.newResponseWriter(st)
defer responseWriter.close()
rw := &responseWriter{
st: st,
headers: make(http.Header),
isHeadResp: req.Method == "HEAD",
bw: &bodyWriter{
st: st,
remain: -1,
flush: false,
name: "response",
},
}
defer rw.close()
// TODO: handle panic coming from the HTTP handler.
sc.handler.ServeHTTP(responseWriter, req)
sc.handler.ServeHTTP(rw, req)
return nil
}
@@ -234,20 +244,7 @@ type responseWriter struct {
headers http.Header
// TODO: support 1xx status
wroteHeader bool // Non-1xx header has been (logically) written.
}
func (sc *serverConn) newResponseWriter(st *stream) *responseWriter {
rw := &responseWriter{
st: st,
headers: make(http.Header),
bw: &bodyWriter{
st: st,
remain: -1,
flush: false,
name: "response",
},
}
return rw
isHeadResp bool // response is for a HEAD request.
}
func (rw *responseWriter) Header() http.Header {
@@ -295,6 +292,9 @@ func (rw *responseWriter) Write(b []byte) (int, error) {
if !rw.wroteHeader {
rw.writeHeaderLocked(http.StatusOK)
}
if rw.isHeadResp {
return 0, nil
}
return rw.bw.Write(b)
}

View File

@@ -150,6 +150,30 @@ func TestServerBody(t *testing.T) {
})
}
func TestServerHeadResponseNoBody(t *testing.T) {
bodyContent := []byte("response body that will not be sent for HEAD requests")
synctest.Test(t, func(t *testing.T) {
ts := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(bodyContent)
}))
tc := ts.connect()
tc.greet()
reqStream := tc.newStream(streamTypeRequest)
reqStream.writeHeaders(http.Header{":method": {http.MethodGet}})
synctest.Wait()
reqStream.wantHeaders(http.Header{":status": {"200"}})
reqStream.wantData(bodyContent)
reqStream.wantClosed("request is complete")
reqStream = tc.newStream(streamTypeRequest)
reqStream.writeHeaders(http.Header{":method": {http.MethodHead}})
synctest.Wait()
reqStream.wantHeaders(http.Header{":status": {"200"}})
reqStream.wantClosed("request is complete")
})
}
type testServer struct {
t testing.TB
s *Server