Skip to content

Commit 5f45c77

Browse files
neildgopherbot
authored andcommitted
internal/http3: make read-data tests usable for server handlers
A reading a transport response body behaves much the same as a server handler reading a request body. Move the transport test into body_test.go and rearrange it a bit so we can reuse it as a server test. For golang/go#70914 Change-Id: I24e10dd078ffab867c9b678e1d0b99172763b069 Reviewed-on: https://go-review.googlesource.com/c/net/+/652457 Auto-Submit: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
1 parent 43c2540 commit 5f45c77

File tree

2 files changed

+276
-268
lines changed

2 files changed

+276
-268
lines changed

internal/http3/body_test.go

+276
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
// Copyright 2025 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.24 && goexperiment.synctest
6+
7+
package http3
8+
9+
import (
10+
"bytes"
11+
"fmt"
12+
"io"
13+
"net/http"
14+
"testing"
15+
)
16+
17+
// TestReadData tests servers reading request bodies, and clients reading response bodies.
18+
func TestReadData(t *testing.T) {
19+
// These tests consist of a series of steps,
20+
// where each step is either something arriving on the stream
21+
// or the client/server reading from the body.
22+
type (
23+
// HEADERS frame arrives (headers).
24+
receiveHeaders struct {
25+
contentLength int64 // -1 for no content-length
26+
}
27+
// DATA frame header arrives.
28+
receiveDataHeader struct {
29+
size int64
30+
}
31+
// DATA frame content arrives.
32+
receiveData struct {
33+
size int64
34+
}
35+
// HEADERS frame arrives (trailers).
36+
receiveTrailers struct{}
37+
// Some other frame arrives.
38+
receiveFrame struct {
39+
ftype frameType
40+
data []byte
41+
}
42+
// Stream closed, ending the body.
43+
receiveEOF struct{}
44+
// Server reads from Request.Body, or client reads from Response.Body.
45+
wantBody struct {
46+
size int64
47+
eof bool
48+
}
49+
wantError struct{}
50+
)
51+
for _, test := range []struct {
52+
name string
53+
respHeader http.Header
54+
steps []any
55+
wantError bool
56+
}{{
57+
name: "no content length",
58+
steps: []any{
59+
receiveHeaders{contentLength: -1},
60+
receiveDataHeader{size: 10},
61+
receiveData{size: 10},
62+
receiveEOF{},
63+
wantBody{size: 10, eof: true},
64+
},
65+
}, {
66+
name: "valid content length",
67+
steps: []any{
68+
receiveHeaders{contentLength: 10},
69+
receiveDataHeader{size: 10},
70+
receiveData{size: 10},
71+
receiveEOF{},
72+
wantBody{size: 10, eof: true},
73+
},
74+
}, {
75+
name: "data frame exceeds content length",
76+
steps: []any{
77+
receiveHeaders{contentLength: 5},
78+
receiveDataHeader{size: 10},
79+
receiveData{size: 10},
80+
wantError{},
81+
},
82+
}, {
83+
name: "data frame after all content read",
84+
steps: []any{
85+
receiveHeaders{contentLength: 5},
86+
receiveDataHeader{size: 5},
87+
receiveData{size: 5},
88+
wantBody{size: 5},
89+
receiveDataHeader{size: 1},
90+
receiveData{size: 1},
91+
wantError{},
92+
},
93+
}, {
94+
name: "content length too long",
95+
steps: []any{
96+
receiveHeaders{contentLength: 10},
97+
receiveDataHeader{size: 5},
98+
receiveData{size: 5},
99+
receiveEOF{},
100+
wantBody{size: 5},
101+
wantError{},
102+
},
103+
}, {
104+
name: "stream ended by trailers",
105+
steps: []any{
106+
receiveHeaders{contentLength: -1},
107+
receiveDataHeader{size: 5},
108+
receiveData{size: 5},
109+
receiveTrailers{},
110+
wantBody{size: 5, eof: true},
111+
},
112+
}, {
113+
name: "trailers and content length too long",
114+
steps: []any{
115+
receiveHeaders{contentLength: 10},
116+
receiveDataHeader{size: 5},
117+
receiveData{size: 5},
118+
wantBody{size: 5},
119+
receiveTrailers{},
120+
wantError{},
121+
},
122+
}, {
123+
name: "unknown frame before headers",
124+
steps: []any{
125+
receiveFrame{
126+
ftype: 0x1f + 0x21, // reserved frame type
127+
data: []byte{1, 2, 3, 4},
128+
},
129+
receiveHeaders{contentLength: -1},
130+
receiveDataHeader{size: 10},
131+
receiveData{size: 10},
132+
wantBody{size: 10},
133+
},
134+
}, {
135+
name: "unknown frame after headers",
136+
steps: []any{
137+
receiveHeaders{contentLength: -1},
138+
receiveFrame{
139+
ftype: 0x1f + 0x21, // reserved frame type
140+
data: []byte{1, 2, 3, 4},
141+
},
142+
receiveDataHeader{size: 10},
143+
receiveData{size: 10},
144+
wantBody{size: 10},
145+
},
146+
}, {
147+
name: "invalid frame",
148+
steps: []any{
149+
receiveHeaders{contentLength: -1},
150+
receiveFrame{
151+
ftype: frameTypeSettings, // not a valid frame on this stream
152+
data: []byte{1, 2, 3, 4},
153+
},
154+
wantError{},
155+
},
156+
}, {
157+
name: "data frame consumed by several reads",
158+
steps: []any{
159+
receiveHeaders{contentLength: -1},
160+
receiveDataHeader{size: 16},
161+
receiveData{size: 16},
162+
wantBody{size: 2},
163+
wantBody{size: 4},
164+
wantBody{size: 8},
165+
wantBody{size: 2},
166+
},
167+
}, {
168+
name: "read multiple frames",
169+
steps: []any{
170+
receiveHeaders{contentLength: -1},
171+
receiveDataHeader{size: 2},
172+
receiveData{size: 2},
173+
receiveDataHeader{size: 4},
174+
receiveData{size: 4},
175+
receiveDataHeader{size: 8},
176+
receiveData{size: 8},
177+
wantBody{size: 2},
178+
wantBody{size: 4},
179+
wantBody{size: 8},
180+
},
181+
}} {
182+
183+
runTest := func(t testing.TB, h http.Header, st *testQUICStream, body func() io.ReadCloser) {
184+
var (
185+
bytesSent int
186+
bytesReceived int
187+
)
188+
for _, step := range test.steps {
189+
switch step := step.(type) {
190+
case receiveHeaders:
191+
header := h.Clone()
192+
if step.contentLength != -1 {
193+
header["content-length"] = []string{
194+
fmt.Sprint(step.contentLength),
195+
}
196+
}
197+
st.writeHeaders(header)
198+
case receiveDataHeader:
199+
t.Logf("receive DATA frame header: size=%v", step.size)
200+
st.writeVarint(int64(frameTypeData))
201+
st.writeVarint(step.size)
202+
st.Flush()
203+
case receiveData:
204+
t.Logf("receive DATA frame content: size=%v", step.size)
205+
for range step.size {
206+
st.stream.stream.WriteByte(byte(bytesSent))
207+
bytesSent++
208+
}
209+
st.Flush()
210+
case receiveTrailers:
211+
st.writeHeaders(http.Header{
212+
"x-trailer": []string{"trailer"},
213+
})
214+
case receiveFrame:
215+
st.writeVarint(int64(step.ftype))
216+
st.writeVarint(int64(len(step.data)))
217+
st.Write(step.data)
218+
st.Flush()
219+
case receiveEOF:
220+
t.Logf("receive EOF on request stream")
221+
st.stream.stream.CloseWrite()
222+
case wantBody:
223+
t.Logf("read %v bytes from response body", step.size)
224+
want := make([]byte, step.size)
225+
for i := range want {
226+
want[i] = byte(bytesReceived)
227+
bytesReceived++
228+
}
229+
got := make([]byte, step.size)
230+
n, err := body().Read(got)
231+
got = got[:n]
232+
if !bytes.Equal(got, want) {
233+
t.Errorf("resp.Body.Read:")
234+
t.Errorf(" got: {%x}", got)
235+
t.Fatalf(" want: {%x}", want)
236+
}
237+
if err != nil {
238+
if step.eof && err == io.EOF {
239+
continue
240+
}
241+
t.Fatalf("resp.Body.Read: unexpected error %v", err)
242+
}
243+
if step.eof {
244+
if n, err := body().Read([]byte{0}); n != 0 || err != io.EOF {
245+
t.Fatalf("resp.Body.Read() = %v, %v; want io.EOF", n, err)
246+
}
247+
}
248+
case wantError:
249+
if n, err := body().Read([]byte{0}); n != 0 || err == nil || err == io.EOF {
250+
t.Fatalf("resp.Body.Read() = %v, %v; want error", n, err)
251+
}
252+
default:
253+
t.Fatalf("unknown test step %T", step)
254+
}
255+
}
256+
257+
}
258+
259+
runSynctestSubtest(t, test.name+"/client", func(t testing.TB) {
260+
tc := newTestClientConn(t)
261+
tc.greet()
262+
263+
req, _ := http.NewRequest("GET", "https://example.tld/", nil)
264+
rt := tc.roundTrip(req)
265+
st := tc.wantStream(streamTypeRequest)
266+
st.wantHeaders(nil)
267+
268+
header := http.Header{
269+
":status": []string{"200"},
270+
}
271+
runTest(t, header, st, func() io.ReadCloser {
272+
return rt.response().Body
273+
})
274+
})
275+
}
276+
}

0 commit comments

Comments
 (0)