|
@@ -98,6 +98,19 @@ type Server struct {
|
|
|
// the HTTP/2 spec's recommendations.
|
|
|
MaxConcurrentStreams uint32
|
|
|
|
|
|
+ // MaxDecoderHeaderTableSize optionally specifies the http2
|
|
|
+ // SETTINGS_HEADER_TABLE_SIZE to send in the initial settings frame. It
|
|
|
+ // informs the remote endpoint of the maximum size of the header compression
|
|
|
+ // table used to decode header blocks, in octets. If zero, the default value
|
|
|
+ // of 4096 is used.
|
|
|
+ MaxDecoderHeaderTableSize uint32
|
|
|
+
|
|
|
+ // MaxEncoderHeaderTableSize optionally specifies an upper limit for the
|
|
|
+ // header compression table used for encoding request headers. Received
|
|
|
+ // SETTINGS_HEADER_TABLE_SIZE settings are capped at this limit. If zero,
|
|
|
+ // the default value of 4096 is used.
|
|
|
+ MaxEncoderHeaderTableSize uint32
|
|
|
+
|
|
|
// MaxReadFrameSize optionally specifies the largest frame
|
|
|
// this server is willing to read. A valid value is between
|
|
|
// 16k and 16M, inclusive. If zero or otherwise invalid, a
|
|
@@ -170,6 +183,20 @@ func (s *Server) maxConcurrentStreams() uint32 {
|
|
|
return defaultMaxStreams
|
|
|
}
|
|
|
|
|
|
+func (s *Server) maxDecoderHeaderTableSize() uint32 {
|
|
|
+ if v := s.MaxDecoderHeaderTableSize; v > 0 {
|
|
|
+ return v
|
|
|
+ }
|
|
|
+ return initialHeaderTableSize
|
|
|
+}
|
|
|
+
|
|
|
+func (s *Server) maxEncoderHeaderTableSize() uint32 {
|
|
|
+ if v := s.MaxEncoderHeaderTableSize; v > 0 {
|
|
|
+ return v
|
|
|
+ }
|
|
|
+ return initialHeaderTableSize
|
|
|
+}
|
|
|
+
|
|
|
// maxQueuedControlFrames is the maximum number of control frames like
|
|
|
// SETTINGS, PING and RST_STREAM that will be queued for writing before
|
|
|
// the connection is closed to prevent memory exhaustion attacks.
|
|
@@ -394,7 +421,6 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
|
|
|
advMaxStreams: s.maxConcurrentStreams(),
|
|
|
initialStreamSendWindowSize: initialWindowSize,
|
|
|
maxFrameSize: initialMaxFrameSize,
|
|
|
- headerTableSize: initialHeaderTableSize,
|
|
|
serveG: newGoroutineLock(),
|
|
|
pushEnabled: true,
|
|
|
sawClientPreface: opts.SawClientPreface,
|
|
@@ -424,12 +450,13 @@ func (s *Server) ServeConn(c net.Conn, opts *ServeConnOpts) {
|
|
|
sc.flow.add(initialWindowSize)
|
|
|
sc.inflow.add(initialWindowSize)
|
|
|
sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf)
|
|
|
+ sc.hpackEncoder.SetMaxDynamicTableSizeLimit(s.maxEncoderHeaderTableSize())
|
|
|
|
|
|
fr := NewFramer(sc.bw, c)
|
|
|
if s.CountError != nil {
|
|
|
fr.countError = s.CountError
|
|
|
}
|
|
|
- fr.ReadMetaHeaders = hpack.NewDecoder(initialHeaderTableSize, nil)
|
|
|
+ fr.ReadMetaHeaders = hpack.NewDecoder(s.maxDecoderHeaderTableSize(), nil)
|
|
|
fr.MaxHeaderListSize = sc.maxHeaderListSize()
|
|
|
fr.SetMaxReadFrameSize(s.maxReadFrameSize())
|
|
|
sc.framer = fr
|
|
@@ -559,9 +586,9 @@ type serverConn struct {
|
|
|
streams map[uint32]*stream
|
|
|
initialStreamSendWindowSize int32
|
|
|
maxFrameSize int32
|
|
|
- headerTableSize uint32
|
|
|
peerMaxHeaderListSize uint32 // zero means unknown (default)
|
|
|
canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case
|
|
|
+ canonHeaderKeysSize int // canonHeader keys size in bytes
|
|
|
writingFrame bool // started writing a frame (on serve goroutine or separate)
|
|
|
writingFrameAsync bool // started a frame on its own goroutine but haven't heard back on wroteFrameCh
|
|
|
needsFrameFlush bool // last frame write wasn't a flush
|
|
@@ -740,6 +767,13 @@ func (sc *serverConn) condlogf(err error, format string, args ...interface{}) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// maxCachedCanonicalHeadersKeysSize is an arbitrarily-chosen limit on the size
|
|
|
+// of the entries in the canonHeader cache.
|
|
|
+// This should be larger than the size of unique, uncommon header keys likely to
|
|
|
+// be sent by the peer, while not so high as to permit unreasonable memory usage
|
|
|
+// if the peer sends an unbounded number of unique header keys.
|
|
|
+const maxCachedCanonicalHeadersKeysSize = 2048
|
|
|
+
|
|
|
func (sc *serverConn) canonicalHeader(v string) string {
|
|
|
sc.serveG.check()
|
|
|
buildCommonHeaderMapsOnce()
|
|
@@ -755,14 +789,10 @@ func (sc *serverConn) canonicalHeader(v string) string {
|
|
|
sc.canonHeader = make(map[string]string)
|
|
|
}
|
|
|
cv = http.CanonicalHeaderKey(v)
|
|
|
- // maxCachedCanonicalHeaders is an arbitrarily-chosen limit on the number of
|
|
|
- // entries in the canonHeader cache. This should be larger than the number
|
|
|
- // of unique, uncommon header keys likely to be sent by the peer, while not
|
|
|
- // so high as to permit unreasonable memory usage if the peer sends an unbounded
|
|
|
- // number of unique header keys.
|
|
|
- const maxCachedCanonicalHeaders = 32
|
|
|
- if len(sc.canonHeader) < maxCachedCanonicalHeaders {
|
|
|
+ size := 100 + len(v)*2 // 100 bytes of map overhead + key + value
|
|
|
+ if sc.canonHeaderKeysSize+size <= maxCachedCanonicalHeadersKeysSize {
|
|
|
sc.canonHeader[v] = cv
|
|
|
+ sc.canonHeaderKeysSize += size
|
|
|
}
|
|
|
return cv
|
|
|
}
|
|
@@ -864,6 +894,7 @@ func (sc *serverConn) serve() {
|
|
|
{SettingMaxFrameSize, sc.srv.maxReadFrameSize()},
|
|
|
{SettingMaxConcurrentStreams, sc.advMaxStreams},
|
|
|
{SettingMaxHeaderListSize, sc.maxHeaderListSize()},
|
|
|
+ {SettingHeaderTableSize, sc.srv.maxDecoderHeaderTableSize()},
|
|
|
{SettingInitialWindowSize, uint32(sc.srv.initialStreamRecvWindowSize())},
|
|
|
},
|
|
|
})
|
|
@@ -1661,7 +1692,6 @@ func (sc *serverConn) processSetting(s Setting) error {
|
|
|
}
|
|
|
switch s.ID {
|
|
|
case SettingHeaderTableSize:
|
|
|
- sc.headerTableSize = s.Val
|
|
|
sc.hpackEncoder.SetMaxDynamicTableSize(s.Val)
|
|
|
case SettingEnablePush:
|
|
|
sc.pushEnabled = s.Val != 0
|