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

Lines changed: 276 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy