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 statusCode == 101 {
215 return nil, &streamError{errH3MessageError, "unsupported 101 response"}
216 }
217 if err := rt.maybeCallGot1xxResponse(statusCode, h); err != nil {
218 return nil, err
219 }
220 if statusCode == 100 {
221 rt.maybeCallGot100Continue()
222 if hasUnsent100Body {
223 hasUnsent100Body = false
224 wg.Go(func() { cc.writeBodyAndTrailer(rt, req) })
225 }
226 }
227 continue
228 }
229
230
231
232 contentLength, err := parseResponseContentLength(req.Method, statusCode, h)
233 if err != nil {
234 return nil, err
235 }
236
237 trailer := make(http.Header)
238 extractTrailerFromHeader(h, trailer)
239 delete(h, "Trailer")
240
241 if (contentLength != 0 && req.Method != http.MethodHead) || len(trailer) > 0 {
242 rt.respBody = &bodyReader{
243 st: st,
244 remain: contentLength,
245 trailer: trailer,
246 maxHeaderBytes: cc.tr.maxResponseHeaderBytes(),
247 }
248 } else {
249 rt.respBody = http.NoBody
250 }
251 resp := &http.Response{
252 Proto: "HTTP/3.0",
253 ProtoMajor: 3,
254 Header: h,
255 StatusCode: statusCode,
256 Status: strconv.Itoa(statusCode) + " " + http.StatusText(statusCode),
257 ContentLength: contentLength,
258 Trailer: trailer,
259 Body: (*transportResponseBody)(rt),
260 }
261 if addedGzip && ascii.EqualFold(h.Get("Content-Encoding"), "gzip") {
262 resp.Body = &httpcommon.GzipReader{Body: resp.Body}
263 h.Del("Content-Encoding")
264 h.Del("Content-Length")
265 resp.ContentLength = -1
266 resp.Uncompressed = true
267 }
268 return resp, nil
269 case frameTypePushPromise:
270 if err := cc.handlePushPromise(st); err != nil {
271 return nil, err
272 }
273 default:
274 if err := st.discardUnknownFrame(ftype); err != nil {
275 return nil, err
276 }
277 }
278 }
279 }
280
281
282
283 func actualContentLength(req *http.Request) int64 {
284 if req.Body == nil || req.Body == http.NoBody {
285 return 0
286 }
287 if req.ContentLength != 0 {
288 return req.ContentLength
289 }
290 return -1
291 }
292
293
294
295
296
297
298
299 func reqBodyIgnored(err error) bool {
300 if streamErr, ok := errors.AsType[quic.StreamError](err); ok {
301 return http3Error(streamErr) == errH3NoError
302 }
303 return false
304 }
305
306
307
308 func (cc *clientConn) writeBodyAndTrailer(rt *roundTripState, req *http.Request) {
309 defer rt.closeReqBody()
310
311 declaredTrailer := req.Trailer.Clone()
312
313 rt.reqBodyWriter.st = rt.st
314 rt.reqBodyWriter.remain = actualContentLength(req)
315 rt.reqBodyWriter.flush = true
316 rt.reqBodyWriter.name = "request"
317 rt.reqBodyWriter.trailer = req.Trailer
318 rt.reqBodyWriter.enc = &cc.enc
319
320 if _, err := io.Copy(&rt.reqBodyWriter, rt.reqBody); err != nil {
321 if reqBodyIgnored(err) {
322 return
323 }
324 rt.abort(err)
325 return
326 }
327
328
329
330 for name := range req.Trailer {
331 if _, ok := declaredTrailer[name]; !ok {
332 delete(req.Trailer, name)
333 }
334 }
335 if err := rt.reqBodyWriter.Close(); err != nil {
336 rt.abort(err)
337 }
338 }
339
340
341 type transportResponseBody roundTripState
342
343
344 func (b *transportResponseBody) Read(p []byte) (n int, err error) {
345 return b.respBody.Read(p)
346 }
347
348 var errRespBodyClosed = errors.New("response body closed")
349
350
351
352 func (b *transportResponseBody) Close() error {
353 rt := (*roundTripState)(b)
354
355 err := rt.respBody.Close()
356 if err == nil {
357 err = errRespBodyClosed
358 }
359 err = rt.abort(err)
360
361
362 rt.closeReqBody()
363 if err == errRespBodyClosed {
364
365
366 return nil
367 }
368 return err
369 }
370
371 func parseResponseContentLength(method string, statusCode int, h http.Header) (int64, error) {
372 clens := h["Content-Length"]
373 if len(clens) == 0 {
374 return -1, nil
375 }
376
377
378
379 for _, v := range clens[1:] {
380 if clens[0] != v {
381 return -1, &streamError{errH3MessageError, "mismatching Content-Length headers"}
382 }
383 }
384
385
386
387
388
389
390 if (statusCode >= 100 && statusCode < 200) ||
391 statusCode == 204 ||
392 (method == "CONNECT" && statusCode >= 200 && statusCode < 300) {
393
394
395 return -1, nil
396 }
397
398 contentLen, err := strconv.ParseUint(clens[0], 10, 63)
399 if err != nil {
400 return -1, &streamError{errH3MessageError, "invalid Content-Length header"}
401 }
402 return int64(contentLen), nil
403 }
404
405 func (rt *roundTripState) handleHeaders() (statusCode int, h http.Header, err error) {
406 maxHeaderBytes := rt.cc.tr.maxResponseHeaderBytes()
407 haveStatus := false
408 cookie := ""
409
410
411 if rt.st.lim > maxHeaderBytes-rt.headerSize {
412 return 0, nil, errHeadersTooLarge
413 }
414
415
416 err = rt.cc.dec.decode(rt.st, func(_ indexType, name, value string) error {
417 if name == "trailer" {
418 fieldCount := int64(strings.Count(value, ",") + 1)
419 rt.trailerSize += int64(len(value)) - fieldCount + 1 + fieldCount*headerFieldOverhead
420 } else {
421 rt.headerSize += int64(len(name)) + int64(len(value)) + headerFieldOverhead
422 }
423 if rt.headerSize > maxHeaderBytes || rt.trailerSize > maxHeaderBytes {
424 return errHeadersTooLarge
425 }
426 if !httpguts.ValidHeaderFieldValue(value) {
427 return &streamError{errH3MessageError, "invalid field value"}
428 }
429 switch {
430 case name == ":status":
431 if haveStatus {
432 return &streamError{errH3MessageError, "duplicate :status"}
433 }
434 haveStatus = true
435 statusCode, err = strconv.Atoi(value)
436 if err != nil {
437 return &streamError{errH3MessageError, "invalid :status"}
438 }
439 case name[0] == ':':
440
441
442
443
444 return &streamError{errH3MessageError, "undefined pseudo-header"}
445 case name == "cookie":
446
447
448
449
450 if cookie == "" {
451 cookie = value
452 } else {
453 cookie += "; " + value
454 }
455 default:
456 if !validWireHeaderFieldName(name) {
457 return &streamError{errH3MessageError, "invalid field name"}
458 }
459 if h == nil {
460 h = make(http.Header)
461 }
462
463
464
465 cname := httpcommon.CanonicalHeader(name)
466
467
468
469
470
471
472 h[cname] = append(h[cname], value)
473 }
474 return nil
475 })
476 if err != nil {
477 return 0, nil, err
478 }
479 if !haveStatus {
480
481
482 return 0, nil, errH3MessageError
483 }
484 if cookie != "" {
485 if h == nil {
486 h = make(http.Header)
487 }
488 h["Cookie"] = []string{cookie}
489 }
490 if err := rt.st.endFrame(); err != nil {
491 return 0, nil, err
492 }
493
494 if isInfoStatus(statusCode) && rt.trace != nil && rt.trace.Got1xxResponse != nil {
495 rt.headerSize = 0
496 rt.trailerSize = 0
497 }
498 return statusCode, h, nil
499 }
500
501 func (cc *clientConn) handlePushPromise(st *stream) error {
502
503
504
505 return &connectionError{
506 code: errH3IDError,
507 message: "PUSH_PROMISE received when no MAX_PUSH_ID has been sent",
508 }
509 }
510
View as plain text