Skip to content

Commit 4783315

Browse files
neildgopherbot
authored andcommitted
http2: limit 1xx based on size, do not limit when delivered
Replace Transport's limit of 5 1xx responses with a limit based on the maximum header size: The total size of all 1xx response headers must not exceed the limit we use on the size of the final response headers. (This differs slightly from the corresponding HTTP/1 change, which imposes a limit on all 1xx response headers *plus* the final response headers. The difference isn't substantial, and this implementation fits better with the HTTP/2 framer.) When the user is reading 1xx responses using a Got1xxResponse client trace hook, disable the limit: Each 1xx response is individually limited by the header size limit, but there is no limit on the total number of responses. The user is responsible for imposing a limit if they want one. For golang/go#65035 Change-Id: I9c19dbf068e0f580789d952f63113b3d21ad86fc Reviewed-on: https://go-review.googlesource.com/c/net/+/615295 Reviewed-by: Cherry Mui <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Damien Neil <[email protected]> Reviewed-by: Brad Fitzpatrick <[email protected]>
1 parent 5716b98 commit 4783315

File tree

2 files changed

+121
-11
lines changed

2 files changed

+121
-11
lines changed

http2/transport.go

+30-11
Original file line numberDiff line numberDiff line change
@@ -420,12 +420,12 @@ type clientStream struct {
420420
sentHeaders bool
421421

422422
// owned by clientConnReadLoop:
423-
firstByte bool // got the first response byte
424-
pastHeaders bool // got first MetaHeadersFrame (actual headers)
425-
pastTrailers bool // got optional second MetaHeadersFrame (trailers)
426-
num1xx uint8 // number of 1xx responses seen
427-
readClosed bool // peer sent an END_STREAM flag
428-
readAborted bool // read loop reset the stream
423+
firstByte bool // got the first response byte
424+
pastHeaders bool // got first MetaHeadersFrame (actual headers)
425+
pastTrailers bool // got optional second MetaHeadersFrame (trailers)
426+
readClosed bool // peer sent an END_STREAM flag
427+
readAborted bool // read loop reset the stream
428+
totalHeaderSize int64 // total size of 1xx headers seen
429429

430430
trailer http.Header // accumulated trailers
431431
resTrailer *http.Header // client's Response.Trailer
@@ -2494,15 +2494,34 @@ func (rl *clientConnReadLoop) handleResponse(cs *clientStream, f *MetaHeadersFra
24942494
if f.StreamEnded() {
24952495
return nil, errors.New("1xx informational response with END_STREAM flag")
24962496
}
2497-
cs.num1xx++
2498-
const max1xxResponses = 5 // arbitrary bound on number of informational responses, same as net/http
2499-
if cs.num1xx > max1xxResponses {
2500-
return nil, errors.New("http2: too many 1xx informational responses")
2501-
}
25022497
if fn := cs.get1xxTraceFunc(); fn != nil {
2498+
// If the 1xx response is being delivered to the user,
2499+
// then they're responsible for limiting the number
2500+
// of responses.
25032501
if err := fn(statusCode, textproto.MIMEHeader(header)); err != nil {
25042502
return nil, err
25052503
}
2504+
} else {
2505+
// If the user didn't examine the 1xx response, then we
2506+
// limit the size of all 1xx headers.
2507+
//
2508+
// This differs a bit from the HTTP/1 implementation, which
2509+
// limits the size of all 1xx headers plus the final response.
2510+
// Use the larger limit of MaxHeaderListSize and
2511+
// net/http.Transport.MaxResponseHeaderBytes.
2512+
limit := int64(cs.cc.t.maxHeaderListSize())
2513+
if t1 := cs.cc.t.t1; t1 != nil && t1.MaxResponseHeaderBytes > limit {
2514+
limit = t1.MaxResponseHeaderBytes
2515+
}
2516+
for _, h := range f.Fields {
2517+
cs.totalHeaderSize += int64(h.Size())
2518+
}
2519+
if cs.totalHeaderSize > limit {
2520+
if VerboseLogs {
2521+
log.Printf("http2: 1xx informational responses too large")
2522+
}
2523+
return nil, errors.New("header list too large")
2524+
}
25062525
}
25072526
if statusCode == 100 {
25082527
traceGot100Continue(cs.trace)

http2/transport_test.go

+91
Original file line numberDiff line numberDiff line change
@@ -5421,3 +5421,94 @@ func TestIssue67671(t *testing.T) {
54215421
res.Body.Close()
54225422
}
54235423
}
5424+
5425+
func TestTransport1xxLimits(t *testing.T) {
5426+
for _, test := range []struct {
5427+
name string
5428+
opt any
5429+
ctxfn func(context.Context) context.Context
5430+
hcount int
5431+
limited bool
5432+
}{{
5433+
name: "default",
5434+
hcount: 10,
5435+
limited: false,
5436+
}, {
5437+
name: "MaxHeaderListSize",
5438+
opt: func(tr *Transport) {
5439+
tr.MaxHeaderListSize = 10000
5440+
},
5441+
hcount: 10,
5442+
limited: true,
5443+
}, {
5444+
name: "MaxResponseHeaderBytes",
5445+
opt: func(tr *http.Transport) {
5446+
tr.MaxResponseHeaderBytes = 10000
5447+
},
5448+
hcount: 10,
5449+
limited: true,
5450+
}, {
5451+
name: "limit by client trace",
5452+
ctxfn: func(ctx context.Context) context.Context {
5453+
count := 0
5454+
return httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
5455+
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
5456+
count++
5457+
if count >= 10 {
5458+
return errors.New("too many 1xx")
5459+
}
5460+
return nil
5461+
},
5462+
})
5463+
},
5464+
hcount: 10,
5465+
limited: true,
5466+
}, {
5467+
name: "limit disabled by client trace",
5468+
opt: func(tr *Transport) {
5469+
tr.MaxHeaderListSize = 10000
5470+
},
5471+
ctxfn: func(ctx context.Context) context.Context {
5472+
return httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
5473+
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
5474+
return nil
5475+
},
5476+
})
5477+
},
5478+
hcount: 20,
5479+
limited: false,
5480+
}} {
5481+
t.Run(test.name, func(t *testing.T) {
5482+
tc := newTestClientConn(t, test.opt)
5483+
tc.greet()
5484+
5485+
ctx := context.Background()
5486+
if test.ctxfn != nil {
5487+
ctx = test.ctxfn(ctx)
5488+
}
5489+
req, _ := http.NewRequestWithContext(ctx, "GET", "https://dummy.tld/", nil)
5490+
rt := tc.roundTrip(req)
5491+
tc.wantFrameType(FrameHeaders)
5492+
5493+
for i := 0; i < test.hcount; i++ {
5494+
if fr, err := tc.fr.ReadFrame(); err != os.ErrDeadlineExceeded {
5495+
t.Fatalf("after writing %v 1xx headers: read %v, %v; want idle", i, fr, err)
5496+
}
5497+
tc.writeHeaders(HeadersFrameParam{
5498+
StreamID: rt.streamID(),
5499+
EndHeaders: true,
5500+
EndStream: false,
5501+
BlockFragment: tc.makeHeaderBlockFragment(
5502+
":status", "103",
5503+
"x-field", strings.Repeat("a", 1000),
5504+
),
5505+
})
5506+
}
5507+
if test.limited {
5508+
tc.wantFrameType(FrameRSTStream)
5509+
} else {
5510+
tc.wantIdle()
5511+
}
5512+
})
5513+
}
5514+
}

0 commit comments

Comments
 (0)