Skip to content

Commit

Permalink
http3: discard body from responses to HEAD requests (#4115)
Browse files Browse the repository at this point in the history
* http3: HEAD method should not have a body

* add tests

* Update http3/server.go

Co-authored-by: Marten Seemann <martenseemann@gmail.com>

* ruduce the size of responseWriter

---------

Co-authored-by: Marten Seemann <martenseemann@gmail.com>
  • Loading branch information
Glonee and marten-seemann committed Oct 23, 2023
1 parent a263164 commit 36f7fe7
Show file tree
Hide file tree
Showing 3 changed files with 48 additions and 1 deletion.
7 changes: 6 additions & 1 deletion http3/response_writer.go
Expand Up @@ -67,9 +67,10 @@ type responseWriter struct {
bufferedStr *bufio.Writer
buf []byte

headerWritten bool
contentLen int64 // if handler set valid Content-Length header
numWritten int64 // bytes written
headerWritten bool
isHead bool
}

var (
Expand Down Expand Up @@ -162,6 +163,10 @@ func (w *responseWriter) Write(p []byte) (int, error) {
return 0, http.ErrContentLength
}

if w.isHead {
return len(p), nil
}

df := &dataFrame{Length: uint64(len(p))}
w.buf = w.buf[:0]
w.buf = df.Append(w.buf)
Expand Down
3 changes: 3 additions & 0 deletions http3/server.go
Expand Up @@ -599,6 +599,9 @@ func (s *Server) handleRequest(conn quic.Connection, str quic.Stream, decoder *q
ctx = context.WithValue(ctx, http.LocalAddrContextKey, conn.LocalAddr())
req = req.WithContext(ctx)
r := newResponseWriter(str, conn, s.logger)
if req.Method == http.MethodHead {
r.isHead = true
}
handler := s.Handler
if handler == nil {
handler = http.DefaultServeMux
Expand Down
39 changes: 39 additions & 0 deletions http3/server_test.go
Expand Up @@ -221,6 +221,45 @@ var _ = Describe("Server", func() {
Expect(hfs).To(HaveLen(3))
})

It("response to HEAD request should not have body", func() {
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("foobar"))
})

headRequest, err := http.NewRequest("HEAD", "https://www.example.com", nil)
Expect(err).ToNot(HaveOccurred())
responseBuf := &bytes.Buffer{}
setRequest(encodeRequest(headRequest))
str.EXPECT().Context().Return(reqContext)
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
str.EXPECT().CancelRead(gomock.Any())
serr := s.handleRequest(conn, str, qpackDecoder, nil)
Expect(serr.err).ToNot(HaveOccurred())
hfs := decodeHeader(responseBuf)
Expect(hfs).To(HaveKeyWithValue(":status", []string{"200"}))
Expect(responseBuf.Bytes()).To(HaveLen(0))
})

It("response to HEAD request should also do content sniffing", func() {
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("<html></html>"))
})

headRequest, err := http.NewRequest("HEAD", "https://www.example.com", nil)
Expect(err).ToNot(HaveOccurred())
responseBuf := &bytes.Buffer{}
setRequest(encodeRequest(headRequest))
str.EXPECT().Context().Return(reqContext)
str.EXPECT().Write(gomock.Any()).DoAndReturn(responseBuf.Write).AnyTimes()
str.EXPECT().CancelRead(gomock.Any())
serr := s.handleRequest(conn, str, qpackDecoder, nil)
Expect(serr.err).ToNot(HaveOccurred())
hfs := decodeHeader(responseBuf)
Expect(hfs).To(HaveKeyWithValue(":status", []string{"200"}))
Expect(hfs).To(HaveKeyWithValue("content-length", []string{"13"}))
Expect(hfs).To(HaveKeyWithValue("content-type", []string{"text/html; charset=utf-8"}))
})

It("handles a aborting handler", func() {
s.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
panic(http.ErrAbortHandler)
Expand Down

0 comments on commit 36f7fe7

Please sign in to comment.