Skip to content

Commit d7c76fa

Browse files
committed
internal/http3: make responseWriter behave closer to other http.ResponseWriter
While running net/http tests against our HTTP/3 implementation locally, some tests fail due to slight behavior differences in responseWriter compared to other http.ResponseWriter implementations: - responseWriter does not return a 200 OK response if a server handler is completely empty. - responseWriter does not have a Flush method, and therefore does not implement http.Flusher. There are surely more differences, but these are straightforward to fix right now. For golang/go#70914 Change-Id: Ieb729a4de4ccb55d670eac2369e73c240b9ac8f8 Reviewed-on: https://go-review.googlesource.com/c/net/+/741720 Reviewed-by: Nicholas Husin <husin@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Damien Neil <dneil@google.com>
1 parent 64b3af9 commit d7c76fa

File tree

2 files changed

+72
-6
lines changed

2 files changed

+72
-6
lines changed

internal/http3/server.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -251,8 +251,9 @@ func (rw *responseWriter) Header() http.Header {
251251
return rw.headers
252252
}
253253

254-
// Caller must hold rw.mu.
255-
func (rw *responseWriter) writeHeaderLocked(statusCode int) {
254+
// Caller must hold rw.mu. If rw.wroteHeader is true, calling this method is a
255+
// no-op.
256+
func (rw *responseWriter) writeHeaderLockedOnce(statusCode int) {
256257
// TODO: support trailer header.
257258
if rw.wroteHeader {
258259
return
@@ -283,21 +284,26 @@ func (rw *responseWriter) writeHeaderLocked(statusCode int) {
283284
func (rw *responseWriter) WriteHeader(statusCode int) {
284285
rw.mu.Lock()
285286
defer rw.mu.Unlock()
286-
rw.writeHeaderLocked(statusCode)
287+
rw.writeHeaderLockedOnce(statusCode)
287288
}
288289

289290
func (rw *responseWriter) Write(b []byte) (int, error) {
290291
rw.mu.Lock()
291292
defer rw.mu.Unlock()
292-
if !rw.wroteHeader {
293-
rw.writeHeaderLocked(http.StatusOK)
294-
}
293+
rw.writeHeaderLockedOnce(http.StatusOK)
295294
if rw.isHeadResp {
296295
return 0, nil
297296
}
298297
return rw.bw.Write(b)
299298
}
300299

300+
func (rw *responseWriter) Flush() {
301+
rw.bw.st.Flush()
302+
}
303+
301304
func (rw *responseWriter) close() error {
305+
rw.mu.Lock()
306+
defer rw.mu.Unlock()
307+
rw.writeHeaderLockedOnce(http.StatusOK)
302308
return rw.st.stream.Close()
303309
}

internal/http3/server_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"net/netip"
1313
"testing"
1414
"testing/synctest"
15+
"time"
1516

1617
"golang.org/x/net/internal/quic/quicwire"
1718
"golang.org/x/net/quic"
@@ -174,6 +175,65 @@ func TestServerHeadResponseNoBody(t *testing.T) {
174175
})
175176
}
176177

178+
func TestServerHandlerEmpty(t *testing.T) {
179+
synctest.Test(t, func(t *testing.T) {
180+
ts := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
181+
// Empty handler should return a 200 OK
182+
}))
183+
tc := ts.connect()
184+
tc.greet()
185+
186+
reqStream := tc.newStream(streamTypeRequest)
187+
reqStream.writeHeaders(http.Header{":method": {http.MethodGet}})
188+
synctest.Wait()
189+
reqStream.wantHeaders(http.Header{":status": {"200"}})
190+
reqStream.wantClosed("request is complete")
191+
})
192+
}
193+
194+
func TestServerHandlerFlushing(t *testing.T) {
195+
synctest.Test(t, func(t *testing.T) {
196+
ts := newTestServer(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
197+
time.Sleep(time.Second)
198+
w.Write([]byte("first"))
199+
200+
time.Sleep(time.Second)
201+
w.Write([]byte("second"))
202+
w.(http.Flusher).Flush()
203+
204+
time.Sleep(time.Second)
205+
w.Write([]byte("third"))
206+
}))
207+
tc := ts.connect()
208+
tc.greet()
209+
210+
reqStream := tc.newStream(streamTypeRequest)
211+
reqStream.writeHeaders(http.Header{":method": {http.MethodGet}})
212+
synctest.Wait()
213+
214+
respBody := make([]byte, 100)
215+
216+
time.Sleep(time.Second)
217+
synctest.Wait()
218+
if n, err := reqStream.Read(respBody); err == nil {
219+
t.Errorf("want no message yet, got %v bytes read", n)
220+
}
221+
222+
time.Sleep(time.Second)
223+
synctest.Wait()
224+
if _, err := reqStream.Read(respBody); err != nil {
225+
t.Errorf("failed to read partial response from server, got err: %v", err)
226+
}
227+
228+
time.Sleep(time.Second)
229+
synctest.Wait()
230+
if _, err := reqStream.Read(respBody); err != io.EOF {
231+
t.Errorf("expected EOF, got err: %v", err)
232+
}
233+
reqStream.wantClosed("request is complete")
234+
})
235+
}
236+
177237
type testServer struct {
178238
t testing.TB
179239
s *Server

0 commit comments

Comments
 (0)