1
2
3
4
5 package http3
6
7 import (
8 "errors"
9 "io"
10 "net/http"
11 "net/http/httptrace"
12 "net/http/internal/ascii"
13 "net/http/internal/httpcommon"
14 "net/textproto"
15 "strconv"
16 "strings"
17 "sync"
18
19 "golang.org/x/net/http/httpguts"
20 "golang.org/x/net/quic"
21 )
22
23 type roundTripState struct {
24 cc *clientConn
25 st *stream
26
27
28 onceCloseReqBody sync.Once
29 reqBody io.ReadCloser
30
31 reqBodyWriter bodyWriter
32
33
34 respBody io.ReadCloser
35
36 trace *httptrace.ClientTrace
37 headerSize int64
38 trailerSize int64
39
40 errOnce sync.Once
41 err error
42 }
43
44
45
46 func (rt *roundTripState) abort(err error) error {
47 rt.errOnce.Do(func() {
48 rt.err = err
49
50 rt.cc.mu.Lock()
51 rt.cc.active--
52 rt.cc.mu.Unlock()
53 rt.cc.maybeCallStateHook()
54
55 switch e := err.(type) {
56 case *connectionError:
57 rt.cc.abort(e)
58 case *streamError:
59 rt.st.CloseRead(uint64(e.code))
60 rt.st.Reset(uint64(e.code))
61 default:
62 rt.st.CloseRead(uint64(errH3NoError))
63 rt.st.Reset(uint64(errH3NoError))
64 }
65 })
66 return rt.err
67 }
68
69
70 func (rt *roundTripState) closeReqBody() {
71 if rt.reqBody != nil {
72 rt.onceCloseReqBody.Do(func() {
73 rt.reqBody.Close()
74 })
75 }
76 }
77
78
79 func (rt *roundTripState) maybeCallGot1xxResponse(status int, h http.Header) error {
80 if rt.trace == nil || rt.trace.Got1xxResponse == nil {
81 return nil
82 }
83 return rt.trace.Got1xxResponse(status, textproto.MIMEHeader(h))
84 }
85
86 func (rt *roundTripState) maybeCallGot100Continue() {
87 if rt.trace == nil || rt.trace.Got100Continue == nil {
88 return
89 }
90 rt.trace.Got100Continue()
91 }
92
93 func (rt *roundTripState) maybeCallWait100Continue() {
94 if rt.trace == nil || rt.trace.Wait100Continue == nil {
95 return
96 }
97 rt.trace.Wait100Continue()
98 }
99
100
101 func (cc *clientConn) RoundTrip(req *http.Request) (_ *http.Response, err error) {
102 cc.mu.Lock()
103 if cc.reserved > 0 {
104 cc.reserved--
105 }
106 cc.active++
107 cc.mu.Unlock()
108
109
110 st, err := newConnStream(req.Context(), cc.qconn, streamTypeRequest)
111 if err != nil {
112 cc.mu.Lock()
113 cc.active--
114 cc.mu.Unlock()
115 cc.maybeCallStateHook()
116 return nil, err
117 }
118 rt := &roundTripState{
119 cc: cc,
120 st: st,
121 trace: httptrace.ContextClientTrace(req.Context()),
122 reqBody: req.Body,
123 }
124 if rt.reqBody == nil {
125 rt.reqBody = http.NoBody
126 }
127
128 var wg sync.WaitGroup
129 defer func() {
130 if err != nil {
131 err = rt.abort(err)
132
133
134
135
136
137
138
139
140
141
142
143
144
145 rt.closeReqBody()
146 wg.Wait()
147 }
148 }()
149
150
151 st.stream.SetReadContext(req.Context())
152 st.stream.SetWriteContext(req.Context())
153
154 addedGzip := httpcommon.IsRequestGzip(req.Method, req.Header, cc.tr.tr1.DisableCompression)
155 headers := cc.enc.encode(func(yield func(itype indexType, name, value string)) {
156 _, err = httpcommon.EncodeHeaders(req.Context(), httpcommon.EncodeHeadersParam{
157 Request: httpcommon.Request{
158 URL: req.URL,
159 Method: req.Method,
160 Host: req.Host,
161 Header: req.Header,
162 Trailer: req.Trailer,
163 ActualContentLength: actualContentLength(req),
164 },
165 AddGzipHeader: addedGzip,
166 PeerMaxHeaderListSize: 0,
167 DefaultUserAgent: "Go-http-client/3.0",
168 }, func(name, value string) {
169
170 yield(mayIndex, name, value)
171 })
172 })
173 if err != nil {
174 return nil, err
175 }
176
177
178 st.writeVarint(int64(frameTypeHeaders))
179 st.writeVarint(int64(len(headers)))
180 st.Write(headers)
181
182 is100ContinueReq := httpguts.HeaderValuesContainsToken(req.Header["Expect"], "100-continue")
183 hasUnsent100Body := false
184 switch {
185 case rt.reqBody == http.NoBody && len(req.Trailer) == 0:
186 st.CloseWrite()
187 case is100ContinueReq:
188 if err := st.Flush(); err != nil {
189 return nil, err
190 }
191 hasUnsent100Body = true
192 rt.maybeCallWait100Continue()
193 default:
194 if err := st.Flush(); err != nil {
195 return nil, err
196 }
197 wg.Go(func() { cc.writeBodyAndTrailer(rt, req) })
198 }
199
200
201 for {
202 ftype, err := st.readFrameHeader()
203 if err != nil {
204 return nil, err
205 }
206 switch ftype {
207 case frameTypeHeaders:
208 statusCode, h, err := rt.handleHeaders()
209 if err != nil {
210 return nil, err
211 }
212
213 if isInfoStatus(statusCode) {
214 if err := rt.maybeCallGot1xxResponse(statusCode, h); err != nil {
215 return nil, err
216 }
217 if statusCode == 100 {
218 rt.maybeCallGot100Continue()
219 if hasUnsent100Body {
220 hasUnsent100Body = false
221 wg.Go(func() { cc.writeBodyAndTrailer(rt, req) })
222 }
223 }
224 continue
225 }
226
227
228
229 contentLength, err := parseResponseContentLength(req.Method, statusCode, h)
230 if err != nil {
231 return nil, err
232 }
233
234 trailer := make(http.Header)
235 extractTrailerFromHeader(h, trailer)
236 delete(h, "Trailer")
237
238 if (contentLength != 0 && req.Method != http.MethodHead) || len(trailer) > 0 {
239 rt.respBody = &bodyReader{
240 st: st,
241 remain: contentLength,
242 trailer: trailer,
243 maxHeaderBytes: cc.tr.maxResponseHeaderBytes(),
244 }
245 } else {
246 rt.respBody = http.NoBody
247 }
248 resp := &http.Response{
249 Proto: "HTTP/3.0",
250 ProtoMajor: 3,
251 Header: h,
252 StatusCode: statusCode,
253 Status: strconv.Itoa(statusCode) + " " + http.StatusText(statusCode),
254 ContentLength: contentLength,
255 Trailer: trailer,
256 Body: (*transportResponseBody)(rt),
257 }
258 if addedGzip && ascii.EqualFold(h.Get("Content-Encoding"), "gzip") {
259 resp.Body = &httpcommon.GzipReader{Body: resp.Body}
260 h.Del("Content-Encoding")
261 h.Del("Content-Length")
262 resp.ContentLength = -1
263 resp.Uncompressed = true
264 }
265 return resp, nil
266 case frameTypePushPromise:
267 if err := cc.handlePushPromise(st); err != nil {
268 return nil, err
269 }
270 default:
271 if err := st.discardUnknownFrame(ftype); err != nil {
272 return nil, err
273 }
274 }
275 }
276 }
277
278
279
280 func actualContentLength(req *http.Request) int64 {
281 if req.Body == nil || req.Body == http.NoBody {
282 return 0
283 }
284 if req.ContentLength != 0 {
285 return req.ContentLength
286 }
287 return -1
288 }
289
290
291
292
293
294
295
296 func reqBodyIgnored(err error) bool {
297 if streamErr, ok := errors.AsType[quic.StreamError](err); ok {
298 return http3Error(streamErr) == errH3NoError
299 }
300 return false
301 }
302
303
304
305 func (cc *clientConn) writeBodyAndTrailer(rt *roundTripState, req *http.Request) {
306 defer rt.closeReqBody()
307
308 declaredTrailer := req.Trailer.Clone()
309
310 rt.reqBodyWriter.st = rt.st
311 rt.reqBodyWriter.remain = actualContentLength(req)
312 rt.reqBodyWriter.flush = true
313 rt.reqBodyWriter.name = "request"
314 rt.reqBodyWriter.trailer = req.Trailer
315 rt.reqBodyWriter.enc = &cc.enc
316
317 if _, err := io.Copy(&rt.reqBodyWriter, rt.reqBody); err != nil {
318 if reqBodyIgnored(err) {
319 return
320 }
321 rt.abort(err)
322 return
323 }
324
325
326
327 for name := range req.Trailer {
328 if _, ok := declaredTrailer[name]; !ok {
329 delete(req.Trailer, name)
330 }
331 }
332 if err := rt.reqBodyWriter.Close(); err != nil {
333 rt.abort(err)
334 }
335 }
336
337
338 type transportResponseBody roundTripState
339
340
341 func (b *transportResponseBody) Read(p []byte) (n int, err error) {
342 return b.respBody.Read(p)
343 }
344
345 var errRespBodyClosed = errors.New("response body closed")
346
347
348
349 func (b *transportResponseBody) Close() error {
350 rt := (*roundTripState)(b)
351
352 err := rt.respBody.Close()
353 if err == nil {
354 err = errRespBodyClosed
355 }
356 err = rt.abort(err)
357
358
359 rt.closeReqBody()
360 if err == errRespBodyClosed {
361
362
363 return nil
364 }
365 return err
366 }
367
368 func parseResponseContentLength(method string, statusCode int, h http.Header) (int64, error) {
369 clens := h["Content-Length"]
370 if len(clens) == 0 {
371 return -1, nil
372 }
373
374
375
376 for _, v := range clens[1:] {
377 if clens[0] != v {
378 return -1, &streamError{errH3MessageError, "mismatching Content-Length headers"}
379 }
380 }
381
382
383
384
385
386
387 if (statusCode >= 100 && statusCode < 200) ||
388 statusCode == 204 ||
389 (method == "CONNECT" && statusCode >= 200 && statusCode < 300) {
390
391
392 return -1, nil
393 }
394
395 contentLen, err := strconv.ParseUint(clens[0], 10, 63)
396 if err != nil {
397 return -1, &streamError{errH3MessageError, "invalid Content-Length header"}
398 }
399 return int64(contentLen), nil
400 }
401
402 func (rt *roundTripState) handleHeaders() (statusCode int, h http.Header, err error) {
403 maxHeaderBytes := rt.cc.tr.maxResponseHeaderBytes()
404 haveStatus := false
405 cookie := ""
406
407
408 if rt.st.lim > maxHeaderBytes-rt.headerSize {
409 return 0, nil, errHeadersTooLarge
410 }
411
412
413 err = rt.cc.dec.decode(rt.st, func(_ indexType, name, value string) error {
414 if name == "trailer" {
415 fieldCount := int64(strings.Count(value, ",") + 1)
416 rt.trailerSize += int64(len(value)) - fieldCount + 1 + fieldCount*headerFieldOverhead
417 } else {
418 rt.headerSize += int64(len(name)) + int64(len(value)) + headerFieldOverhead
419 }
420 if rt.headerSize > maxHeaderBytes || rt.trailerSize > maxHeaderBytes {
421 return errHeadersTooLarge
422 }
423 if !httpguts.ValidHeaderFieldValue(value) {
424 return &streamError{errH3MessageError, "invalid field value"}
425 }
426 switch {
427 case name == ":status":
428 if haveStatus {
429 return &streamError{errH3MessageError, "duplicate :status"}
430 }
431 haveStatus = true
432 statusCode, err = strconv.Atoi(value)
433 if err != nil {
434 return &streamError{errH3MessageError, "invalid :status"}
435 }
436 case name[0] == ':':
437
438
439
440
441 return &streamError{errH3MessageError, "undefined pseudo-header"}
442 case name == "cookie":
443
444
445
446
447 if cookie == "" {
448 cookie = value
449 } else {
450 cookie += "; " + value
451 }
452 default:
453 if !validWireHeaderFieldName(name) {
454 return &streamError{errH3MessageError, "invalid field name"}
455 }
456 if h == nil {
457 h = make(http.Header)
458 }
459
460
461
462 cname := httpcommon.CanonicalHeader(name)
463
464
465
466
467
468
469 h[cname] = append(h[cname], value)
470 }
471 return nil
472 })
473 if err != nil {
474 return 0, nil, err
475 }
476 if !haveStatus {
477
478
479 return 0, nil, errH3MessageError
480 }
481 if cookie != "" {
482 if h == nil {
483 h = make(http.Header)
484 }
485 h["Cookie"] = []string{cookie}
486 }
487 if err := rt.st.endFrame(); err != nil {
488 return 0, nil, err
489 }
490
491 if isInfoStatus(statusCode) && rt.trace != nil && rt.trace.Got1xxResponse != nil {
492 rt.headerSize = 0
493 rt.trailerSize = 0
494 }
495 return statusCode, h, nil
496 }
497
498 func (cc *clientConn) handlePushPromise(st *stream) error {
499
500
501
502 return &connectionError{
503 code: errH3IDError,
504 message: "PUSH_PROMISE received when no MAX_PUSH_ID has been sent",
505 }
506 }
507
View as plain text