Skip to content

Commit 73fe701

Browse files
committed
internal/http3: add Expect: 100-continue support to Server
When serving a request containing the "Expect: 100-continue" header, Server will now send an HTTP 100 status automatically if the request body is read from within the server handler. For golang/go#70914 Change-Id: Ib8185170deabf777a02487a1ded6671db720df51 Reviewed-on: https://go-review.googlesource.com/c/net/+/742520 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Damien Neil <dneil@google.com> Reviewed-by: Nicholas Husin <husin@google.com>
1 parent af0c9df commit 73fe701

File tree

4 files changed

+211
-46
lines changed

4 files changed

+211
-46
lines changed

internal/http3/body.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ type bodyReader struct {
5757
mu sync.Mutex
5858
remain int64
5959
err error
60+
// If not nil, the body contains an "Expect: 100-continue" header, and
61+
// send100Continue should be called when Read is invoked for the first
62+
// time.
63+
send100Continue func()
6064
}
6165

6266
func (r *bodyReader) Read(p []byte) (n int, err error) {
@@ -65,6 +69,10 @@ func (r *bodyReader) Read(p []byte) (n int, err error) {
6569
// Use a mutex here to provide the same behavior.
6670
r.mu.Lock()
6771
defer r.mu.Unlock()
72+
if r.send100Continue != nil {
73+
r.send100Continue()
74+
r.send100Continue = nil
75+
}
6876
if r.err != nil {
6977
return 0, r.err
7078
}

internal/http3/server.go

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ package http3
77
import (
88
"context"
99
"net/http"
10-
"net/url"
1110
"strconv"
1211
"sync"
1312

1413
"golang.org/x/net/http/httpguts"
14+
"golang.org/x/net/internal/httpcommon"
1515
"golang.org/x/net/quic"
1616
)
1717

@@ -157,54 +157,81 @@ func (sc *serverConn) handlePushStream(*stream) error {
157157
}
158158
}
159159

160-
func (sc *serverConn) parseRequest(st *stream) (*http.Request, error) {
161-
req := &http.Request{
162-
URL: &url.URL{},
163-
Proto: "HTTP/3.0",
164-
ProtoMajor: 3,
165-
RemoteAddr: sc.qconn.RemoteAddr().String(),
166-
}
160+
type pseudoHeader struct {
161+
method string
162+
scheme string
163+
path string
164+
authority string
165+
}
166+
167+
func (sc *serverConn) parseHeader(st *stream) (http.Header, pseudoHeader, error) {
167168
ftype, err := st.readFrameHeader()
168169
if err != nil {
169-
return nil, err
170+
return nil, pseudoHeader{}, err
170171
}
171172
if ftype != frameTypeHeaders {
172-
return nil, err
173+
return nil, pseudoHeader{}, err
173174
}
174-
req.Header = make(http.Header)
175+
header := make(http.Header)
176+
var pHeader pseudoHeader
175177
var dec qpackDecoder
176178
if err := dec.decode(st, func(_ indexType, name, value string) error {
177179
switch name {
178180
case ":method":
179-
req.Method = value
181+
pHeader.method = value
180182
case ":scheme":
181-
req.URL.Scheme = value
183+
pHeader.scheme = value
182184
case ":path":
183-
req.URL.Path = value
185+
pHeader.path = value
184186
case ":authority":
185-
req.URL.Host = value
187+
pHeader.authority = value
186188
default:
187-
req.Header.Add(name, value)
189+
header.Add(name, value)
188190
}
189191
return nil
190192
}); err != nil {
191-
return nil, err
193+
return nil, pseudoHeader{}, err
192194
}
193195
if err := st.endFrame(); err != nil {
194-
return nil, err
195-
}
196-
req.Body = &bodyReader{
197-
st: st,
198-
remain: -1,
196+
return nil, pseudoHeader{}, err
199197
}
200-
return req, nil
198+
return header, pHeader, nil
201199
}
202200

203201
func (sc *serverConn) handleRequestStream(st *stream) error {
204-
req, err := sc.parseRequest(st)
202+
header, pHeader, err := sc.parseHeader(st)
205203
if err != nil {
206204
return err
207205
}
206+
207+
reqInfo := httpcommon.NewServerRequest(httpcommon.ServerRequestParam{
208+
Method: pHeader.method,
209+
Scheme: pHeader.scheme,
210+
Authority: pHeader.authority,
211+
Path: pHeader.path,
212+
Header: header,
213+
})
214+
if reqInfo.InvalidReason != "" {
215+
return &streamError{
216+
code: errH3MessageError,
217+
message: reqInfo.InvalidReason,
218+
}
219+
}
220+
req := &http.Request{
221+
Proto: "HTTP/3.0",
222+
Method: pHeader.method,
223+
Host: pHeader.authority,
224+
URL: reqInfo.URL,
225+
RequestURI: reqInfo.RequestURI,
226+
Trailer: reqInfo.Trailer,
227+
ProtoMajor: 3,
228+
RemoteAddr: sc.qconn.RemoteAddr().String(),
229+
Body: &bodyReader{
230+
st: st,
231+
remain: -1,
232+
},
233+
Header: header,
234+
}
208235
defer req.Body.Close()
209236

210237
rw := &responseWriter{
@@ -219,6 +246,12 @@ func (sc *serverConn) handleRequestStream(st *stream) error {
219246
},
220247
}
221248
defer rw.close()
249+
if reqInfo.NeedsContinue {
250+
req.Body.(*bodyReader).send100Continue = func() {
251+
rw.WriteHeader(http.StatusContinue)
252+
rw.Flush()
253+
}
254+
}
222255

223256
// TODO: handle panic coming from the HTTP handler.
224257
sc.handler.ServeHTTP(rw, req)
@@ -238,11 +271,10 @@ func (sc *serverConn) abort(err error) {
238271
}
239272

240273
type responseWriter struct {
241-
st *stream
242-
bw *bodyWriter
243-
mu sync.Mutex
244-
headers http.Header
245-
// TODO: support 1xx status
274+
st *stream
275+
bw *bodyWriter
276+
mu sync.Mutex
277+
headers http.Header
246278
wroteHeader bool // Non-1xx header has been (logically) written.
247279
isHeadResp bool // response is for a HEAD request.
248280
}
@@ -278,7 +310,9 @@ func (rw *responseWriter) writeHeaderLockedOnce(statusCode int) {
278310
rw.st.writeVarint(int64(frameTypeHeaders))
279311
rw.st.writeVarint(int64(len(encHeaders)))
280312
rw.st.Write(encHeaders)
281-
rw.wroteHeader = true
313+
if statusCode >= http.StatusOK {
314+
rw.wroteHeader = true
315+
}
282316
}
283317

284318
func (rw *responseWriter) WriteHeader(statusCode int) {

0 commit comments

Comments
 (0)