Skip to content

Commit 1d78a08

Browse files
neildgopherbot
authored andcommitted
http2, internal/httpcommon: factor out server header logic for h2/h3
Move common elements of constructing a http.Request for a server handler into internal/httpcommon. For golang/go#70914 Change-Id: I5dcd902e189a0bb8daf47c0a815045d274346923 Reviewed-on: https://go-review.googlesource.com/c/net/+/652455 Auto-Submit: Damien Neil <dneil@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Jonathan Amsterdam <jba@google.com>
1 parent 0d7dc54 commit 1d78a08

File tree

2 files changed

+115
-83
lines changed

2 files changed

+115
-83
lines changed

http2/server.go

+38-83
Original file line numberDiff line numberDiff line change
@@ -2233,25 +2233,25 @@ func (sc *serverConn) newStream(id, pusherID uint32, state streamState) *stream
22332233
func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*responseWriter, *http.Request, error) {
22342234
sc.serveG.check()
22352235

2236-
rp := requestParam{
2237-
method: f.PseudoValue("method"),
2238-
scheme: f.PseudoValue("scheme"),
2239-
authority: f.PseudoValue("authority"),
2240-
path: f.PseudoValue("path"),
2241-
protocol: f.PseudoValue("protocol"),
2236+
rp := httpcommon.ServerRequestParam{
2237+
Method: f.PseudoValue("method"),
2238+
Scheme: f.PseudoValue("scheme"),
2239+
Authority: f.PseudoValue("authority"),
2240+
Path: f.PseudoValue("path"),
2241+
Protocol: f.PseudoValue("protocol"),
22422242
}
22432243

22442244
// extended connect is disabled, so we should not see :protocol
2245-
if disableExtendedConnectProtocol && rp.protocol != "" {
2245+
if disableExtendedConnectProtocol && rp.Protocol != "" {
22462246
return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
22472247
}
22482248

2249-
isConnect := rp.method == "CONNECT"
2249+
isConnect := rp.Method == "CONNECT"
22502250
if isConnect {
2251-
if rp.protocol == "" && (rp.path != "" || rp.scheme != "" || rp.authority == "") {
2251+
if rp.Protocol == "" && (rp.Path != "" || rp.Scheme != "" || rp.Authority == "") {
22522252
return nil, nil, sc.countError("bad_connect", streamError(f.StreamID, ErrCodeProtocol))
22532253
}
2254-
} else if rp.method == "" || rp.path == "" || (rp.scheme != "https" && rp.scheme != "http") {
2254+
} else if rp.Method == "" || rp.Path == "" || (rp.Scheme != "https" && rp.Scheme != "http") {
22552255
// See 8.1.2.6 Malformed Requests and Responses:
22562256
//
22572257
// Malformed requests or responses that are detected
@@ -2265,15 +2265,16 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
22652265
return nil, nil, sc.countError("bad_path_method", streamError(f.StreamID, ErrCodeProtocol))
22662266
}
22672267

2268-
rp.header = make(http.Header)
2268+
header := make(http.Header)
2269+
rp.Header = header
22692270
for _, hf := range f.RegularFields() {
2270-
rp.header.Add(sc.canonicalHeader(hf.Name), hf.Value)
2271+
header.Add(sc.canonicalHeader(hf.Name), hf.Value)
22712272
}
2272-
if rp.authority == "" {
2273-
rp.authority = rp.header.Get("Host")
2273+
if rp.Authority == "" {
2274+
rp.Authority = header.Get("Host")
22742275
}
2275-
if rp.protocol != "" {
2276-
rp.header.Set(":protocol", rp.protocol)
2276+
if rp.Protocol != "" {
2277+
header.Set(":protocol", rp.Protocol)
22772278
}
22782279

22792280
rw, req, err := sc.newWriterAndRequestNoBody(st, rp)
@@ -2282,7 +2283,7 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
22822283
}
22832284
bodyOpen := !f.StreamEnded()
22842285
if bodyOpen {
2285-
if vv, ok := rp.header["Content-Length"]; ok {
2286+
if vv, ok := rp.Header["Content-Length"]; ok {
22862287
if cl, err := strconv.ParseUint(vv[0], 10, 63); err == nil {
22872288
req.ContentLength = int64(cl)
22882289
} else {
@@ -2298,84 +2299,38 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
22982299
return rw, req, nil
22992300
}
23002301

2301-
type requestParam struct {
2302-
method string
2303-
scheme, authority, path string
2304-
protocol string
2305-
header http.Header
2306-
}
2307-
2308-
func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*responseWriter, *http.Request, error) {
2302+
func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp httpcommon.ServerRequestParam) (*responseWriter, *http.Request, error) {
23092303
sc.serveG.check()
23102304

23112305
var tlsState *tls.ConnectionState // nil if not scheme https
2312-
if rp.scheme == "https" {
2306+
if rp.Scheme == "https" {
23132307
tlsState = sc.tlsState
23142308
}
23152309

2316-
needsContinue := httpguts.HeaderValuesContainsToken(rp.header["Expect"], "100-continue")
2317-
if needsContinue {
2318-
rp.header.Del("Expect")
2319-
}
2320-
// Merge Cookie headers into one "; "-delimited value.
2321-
if cookies := rp.header["Cookie"]; len(cookies) > 1 {
2322-
rp.header.Set("Cookie", strings.Join(cookies, "; "))
2323-
}
2324-
2325-
// Setup Trailers
2326-
var trailer http.Header
2327-
for _, v := range rp.header["Trailer"] {
2328-
for _, key := range strings.Split(v, ",") {
2329-
key = http.CanonicalHeaderKey(textproto.TrimString(key))
2330-
switch key {
2331-
case "Transfer-Encoding", "Trailer", "Content-Length":
2332-
// Bogus. (copy of http1 rules)
2333-
// Ignore.
2334-
default:
2335-
if trailer == nil {
2336-
trailer = make(http.Header)
2337-
}
2338-
trailer[key] = nil
2339-
}
2340-
}
2341-
}
2342-
delete(rp.header, "Trailer")
2343-
2344-
var url_ *url.URL
2345-
var requestURI string
2346-
if rp.method == "CONNECT" && rp.protocol == "" {
2347-
url_ = &url.URL{Host: rp.authority}
2348-
requestURI = rp.authority // mimic HTTP/1 server behavior
2349-
} else {
2350-
var err error
2351-
url_, err = url.ParseRequestURI(rp.path)
2352-
if err != nil {
2353-
return nil, nil, sc.countError("bad_path", streamError(st.id, ErrCodeProtocol))
2354-
}
2355-
requestURI = rp.path
2310+
res := httpcommon.NewServerRequest(rp)
2311+
if res.InvalidReason != "" {
2312+
return nil, nil, sc.countError(res.InvalidReason, streamError(st.id, ErrCodeProtocol))
23562313
}
23572314

23582315
body := &requestBody{
23592316
conn: sc,
23602317
stream: st,
2361-
needsContinue: needsContinue,
2318+
needsContinue: res.NeedsContinue,
23622319
}
2363-
req := &http.Request{
2364-
Method: rp.method,
2365-
URL: url_,
2320+
req := (&http.Request{
2321+
Method: rp.Method,
2322+
URL: res.URL,
23662323
RemoteAddr: sc.remoteAddrStr,
2367-
Header: rp.header,
2368-
RequestURI: requestURI,
2324+
Header: rp.Header,
2325+
RequestURI: res.RequestURI,
23692326
Proto: "HTTP/2.0",
23702327
ProtoMajor: 2,
23712328
ProtoMinor: 0,
23722329
TLS: tlsState,
2373-
Host: rp.authority,
2330+
Host: rp.Authority,
23742331
Body: body,
2375-
Trailer: trailer,
2376-
}
2377-
req = req.WithContext(st.ctx)
2378-
2332+
Trailer: res.Trailer,
2333+
}).WithContext(st.ctx)
23792334
rw := sc.newResponseWriter(st, req)
23802335
return rw, req, nil
23812336
}
@@ -3270,12 +3225,12 @@ func (sc *serverConn) startPush(msg *startPushRequest) {
32703225
// we start in "half closed (remote)" for simplicity.
32713226
// See further comments at the definition of stateHalfClosedRemote.
32723227
promised := sc.newStream(promisedID, msg.parent.id, stateHalfClosedRemote)
3273-
rw, req, err := sc.newWriterAndRequestNoBody(promised, requestParam{
3274-
method: msg.method,
3275-
scheme: msg.url.Scheme,
3276-
authority: msg.url.Host,
3277-
path: msg.url.RequestURI(),
3278-
header: cloneHeader(msg.header), // clone since handler runs concurrently with writing the PUSH_PROMISE
3228+
rw, req, err := sc.newWriterAndRequestNoBody(promised, httpcommon.ServerRequestParam{
3229+
Method: msg.method,
3230+
Scheme: msg.url.Scheme,
3231+
Authority: msg.url.Host,
3232+
Path: msg.url.RequestURI(),
3233+
Header: cloneHeader(msg.header), // clone since handler runs concurrently with writing the PUSH_PROMISE
32793234
})
32803235
if err != nil {
32813236
// Should not happen, since we've already validated msg.url.

internal/httpcommon/request.go

+77
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"errors"
1010
"fmt"
1111
"net/http/httptrace"
12+
"net/textproto"
1213
"net/url"
1314
"sort"
1415
"strconv"
@@ -378,3 +379,79 @@ func shouldSendReqContentLength(method string, contentLength int64) bool {
378379
return false
379380
}
380381
}
382+
383+
// ServerRequestParam is parameters to NewServerRequest.
384+
type ServerRequestParam struct {
385+
Method string
386+
Scheme, Authority, Path string
387+
Protocol string
388+
Header map[string][]string
389+
}
390+
391+
// ServerRequestResult is the result of NewServerRequest.
392+
type ServerRequestResult struct {
393+
// Various http.Request fields.
394+
URL *url.URL
395+
RequestURI string
396+
Trailer map[string][]string
397+
398+
NeedsContinue bool // client provided an "Expect: 100-continue" header
399+
400+
// If the request should be rejected, this is a short string suitable for passing
401+
// to the http2 package's CountError function.
402+
// It might be a bit odd to return errors this way rather than returing an error,
403+
// but this ensures we don't forget to include a CountError reason.
404+
InvalidReason string
405+
}
406+
407+
func NewServerRequest(rp ServerRequestParam) ServerRequestResult {
408+
needsContinue := httpguts.HeaderValuesContainsToken(rp.Header["Expect"], "100-continue")
409+
if needsContinue {
410+
delete(rp.Header, "Expect")
411+
}
412+
// Merge Cookie headers into one "; "-delimited value.
413+
if cookies := rp.Header["Cookie"]; len(cookies) > 1 {
414+
rp.Header["Cookie"] = []string{strings.Join(cookies, "; ")}
415+
}
416+
417+
// Setup Trailers
418+
var trailer map[string][]string
419+
for _, v := range rp.Header["Trailer"] {
420+
for _, key := range strings.Split(v, ",") {
421+
key = textproto.CanonicalMIMEHeaderKey(textproto.TrimString(key))
422+
switch key {
423+
case "Transfer-Encoding", "Trailer", "Content-Length":
424+
// Bogus. (copy of http1 rules)
425+
// Ignore.
426+
default:
427+
if trailer == nil {
428+
trailer = make(map[string][]string)
429+
}
430+
trailer[key] = nil
431+
}
432+
}
433+
}
434+
delete(rp.Header, "Trailer")
435+
var url_ *url.URL
436+
var requestURI string
437+
if rp.Method == "CONNECT" && rp.Protocol == "" {
438+
url_ = &url.URL{Host: rp.Authority}
439+
requestURI = rp.Authority // mimic HTTP/1 server behavior
440+
} else {
441+
var err error
442+
url_, err = url.ParseRequestURI(rp.Path)
443+
if err != nil {
444+
return ServerRequestResult{
445+
InvalidReason: "bad_path",
446+
}
447+
}
448+
requestURI = rp.Path
449+
}
450+
451+
return ServerRequestResult{
452+
URL: url_,
453+
NeedsContinue: needsContinue,
454+
RequestURI: requestURI,
455+
Trailer: trailer,
456+
}
457+
}

0 commit comments

Comments
 (0)