7
7
package http3
8
8
9
9
import (
10
+ "errors"
10
11
"io"
11
12
"net/http"
12
13
"strconv"
14
+ "sync"
13
15
14
16
"golang.org/x/net/internal/httpcommon"
15
17
)
16
18
19
+ type roundTripState struct {
20
+ cc * ClientConn
21
+ st * stream
22
+
23
+ // Request body, provided by the caller.
24
+ onceCloseReqBody sync.Once
25
+ reqBody io.ReadCloser
26
+
27
+ reqBodyWriter bodyWriter
28
+
29
+ // Response.Body, provided to the caller.
30
+ respBody bodyReader
31
+
32
+ errOnce sync.Once
33
+ err error
34
+ }
35
+
36
+ // abort terminates the RoundTrip.
37
+ // It returns the first fatal error encountered by the RoundTrip call.
38
+ func (rt * roundTripState ) abort (err error ) error {
39
+ rt .errOnce .Do (func () {
40
+ rt .err = err
41
+ switch e := err .(type ) {
42
+ case * connectionError :
43
+ rt .cc .abort (e )
44
+ case * streamError :
45
+ rt .st .stream .CloseRead ()
46
+ rt .st .stream .Reset (uint64 (e .code ))
47
+ default :
48
+ rt .st .stream .CloseRead ()
49
+ rt .st .stream .Reset (uint64 (errH3NoError ))
50
+ }
51
+ })
52
+ return rt .err
53
+ }
54
+
55
+ // closeReqBody closes the Request.Body, at most once.
56
+ func (rt * roundTripState ) closeReqBody () {
57
+ if rt .reqBody != nil {
58
+ rt .onceCloseReqBody .Do (func () {
59
+ rt .reqBody .Close ()
60
+ })
61
+ }
62
+ }
63
+
17
64
// RoundTrip sends a request on the connection.
18
65
func (cc * ClientConn ) RoundTrip (req * http.Request ) (_ * http.Response , err error ) {
19
66
// Each request gets its own QUIC stream.
20
67
st , err := newConnStream (req .Context (), cc .qconn , streamTypeRequest )
21
68
if err != nil {
22
69
return nil , err
23
70
}
71
+ rt := & roundTripState {
72
+ cc : cc ,
73
+ st : st ,
74
+ }
24
75
defer func () {
25
- switch e := err .(type ) {
26
- case nil :
27
- case * connectionError :
28
- cc .abort (e )
29
- case * streamError :
30
- st .stream .CloseRead ()
31
- st .stream .Reset (uint64 (e .code ))
32
- default :
33
- st .stream .CloseRead ()
34
- st .stream .Reset (uint64 (errH3NoError ))
76
+ if err != nil {
77
+ err = rt .abort (err )
35
78
}
36
79
}()
37
80
@@ -64,7 +107,13 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (_ *http.Response, err error)
64
107
}
65
108
66
109
if encr .HasBody {
67
- // TODO: Send the request body.
110
+ // TODO: Defer sending the request body when "Expect: 100-continue" is set.
111
+ rt .reqBody = req .Body
112
+ rt .reqBodyWriter .st = st
113
+ rt .reqBodyWriter .remain = httpcommon .ActualContentLength (req )
114
+ rt .reqBodyWriter .flush = true
115
+ rt .reqBodyWriter .name = "request"
116
+ go copyRequestBody (rt )
68
117
}
69
118
70
119
// Read the response headers.
@@ -91,14 +140,16 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (_ *http.Response, err error)
91
140
if err != nil {
92
141
return nil , err
93
142
}
143
+ rt .respBody .st = st
144
+ rt .respBody .remain = contentLength
94
145
resp := & http.Response {
95
146
Proto : "HTTP/3.0" ,
96
147
ProtoMajor : 3 ,
97
148
Header : h ,
98
149
StatusCode : statusCode ,
99
150
Status : strconv .Itoa (statusCode ) + " " + http .StatusText (statusCode ),
100
151
ContentLength : contentLength ,
101
- Body : io . NopCloser ( nil ), // TODO: read the response body
152
+ Body : ( * transportResponseBody )( rt ),
102
153
}
103
154
// TODO: Automatic Content-Type: gzip decoding.
104
155
return resp , nil
@@ -114,6 +165,55 @@ func (cc *ClientConn) RoundTrip(req *http.Request) (_ *http.Response, err error)
114
165
}
115
166
}
116
167
168
+ func copyRequestBody (rt * roundTripState ) {
169
+ defer rt .closeReqBody ()
170
+ _ , err := io .Copy (& rt .reqBodyWriter , rt .reqBody )
171
+ if closeErr := rt .reqBodyWriter .Close (); err == nil {
172
+ err = closeErr
173
+ }
174
+ if err != nil {
175
+ // Something went wrong writing the body.
176
+ rt .abort (err )
177
+ } else {
178
+ // We wrote the whole body.
179
+ rt .st .stream .CloseWrite ()
180
+ }
181
+ }
182
+
183
+ // transportResponseBody is the Response.Body returned by RoundTrip.
184
+ type transportResponseBody roundTripState
185
+
186
+ // Read is Response.Body.Read.
187
+ func (b * transportResponseBody ) Read (p []byte ) (n int , err error ) {
188
+ return b .respBody .Read (p )
189
+ }
190
+
191
+ var errRespBodyClosed = errors .New ("response body closed" )
192
+
193
+ // Close is Response.Body.Close.
194
+ // Closing the response body is how the caller signals that they're done with a request.
195
+ func (b * transportResponseBody ) Close () error {
196
+ rt := (* roundTripState )(b )
197
+ // Close the request body, which should wake up copyRequestBody if it's
198
+ // currently blocked reading the body.
199
+ rt .closeReqBody ()
200
+ // Close the request stream, since we're done with the request.
201
+ // Reset closes the sending half of the stream.
202
+ rt .st .stream .Reset (uint64 (errH3NoError ))
203
+ // respBody.Close is responsible for closing the receiving half.
204
+ err := rt .respBody .Close ()
205
+ if err == nil {
206
+ err = errRespBodyClosed
207
+ }
208
+ err = rt .abort (err )
209
+ if err == errRespBodyClosed {
210
+ // No other errors occurred before closing Response.Body,
211
+ // so consider this a successful request.
212
+ return nil
213
+ }
214
+ return err
215
+ }
216
+
117
217
func parseResponseContentLength (method string , statusCode int , h http.Header ) (int64 , error ) {
118
218
clens := h ["Content-Length" ]
119
219
if len (clens ) == 0 {
0 commit comments