diff --git a/internal/http3/server.go b/internal/http3/server.go index 9d8937d1..8c960809 100644 --- a/internal/http3/server.go +++ b/internal/http3/server.go @@ -463,6 +463,10 @@ func (rw *responseWriter) Write(b []byte) (n int, err error) { rw.mu.Lock() defer rw.mu.Unlock() + if rw.statusCode == http.StatusNotModified { + return 0, http.ErrBodyNotAllowed + } + b, trimmed := rw.trimWriteLocked(b) if trimmed { defer func() { diff --git a/internal/http3/server_test.go b/internal/http3/server_test.go index 7b938a74..f5216968 100644 --- a/internal/http3/server_test.go +++ b/internal/http3/server_test.go @@ -5,6 +5,7 @@ package http3 import ( + "errors" "io" "maps" "net/http" @@ -912,6 +913,25 @@ func TestServer103EarlyHints(t *testing.T) { }) } +func TestServer304NotModified(t *testing.T) { + synctest.Test(t, func(t *testing.T) { + ts := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNotModified) + if _, err := w.Write([]byte("body should not be allowed")); !errors.Is(err, http.ErrBodyNotAllowed) { + t.Errorf("got %v error when calling Write after WriteHeader(304), want %v error", err, http.ErrBodyNotAllowed) + } + })) + tc := ts.connect() + tc.greet() + + reqStream := tc.newStream(streamTypeRequest) + reqStream.writeHeaders(requestHeader(nil)) + synctest.Wait() + reqStream.wantSomeHeaders(http.Header{":status": {"304"}}) + reqStream.wantClosed("request is complete") + }) +} + type testServer struct { t testing.TB s *Server