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