internal/http3: make read-data tests usable for server handlers · golang/net@5f45c77 (original) (raw)
``
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
`+
}
`