Process more settings. Several aren't used yet.
Also, move settings code from frame.go to http2.go.
diff --git a/frame.go b/frame.go
index 12dd565..7bc93e1 100644
--- a/frame.go
+++ b/frame.go
@@ -108,35 +108,6 @@
},
}
-// A SettingID is an HTTP/2 setting as defined in
-// http://http2.github.io/http2-spec/#iana-settings
-type SettingID uint16
-
-const (
- SettingHeaderTableSize SettingID = 0x1
- SettingEnablePush SettingID = 0x2
- SettingMaxConcurrentStreams SettingID = 0x3
- SettingInitialWindowSize SettingID = 0x4
- SettingMaxFrameSize SettingID = 0x5
- SettingMaxHeaderListSize SettingID = 0x6
-)
-
-var settingName = map[SettingID]string{
- SettingHeaderTableSize: "HEADER_TABLE_SIZE",
- SettingEnablePush: "ENABLE_PUSH",
- SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
- SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
- SettingMaxFrameSize: "MAX_FRAME_SIZE",
- SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
-}
-
-func (s SettingID) String() string {
- if v, ok := settingName[s]; ok {
- return v
- }
- return fmt.Sprintf("UNKNOWN_SETTING_%d", uint8(s))
-}
-
// a frameParser parses a frame given its FrameHeader and payload
// bytes. The length of payload will always equal fh.Length (which
// might be 0).
@@ -518,16 +489,6 @@
return nil
}
-// Setting is a setting parameter: which setting it is, and its value.
-type Setting struct {
- // ID is which setting is being set.
- // See http://http2.github.io/http2-spec/#SettingValues
- ID SettingID
-
- // Val is the value.
- Val uint32
-}
-
// WriteSettings writes a SETTINGS frame with zero or more settings
// specified and the ACK bit not set.
//
diff --git a/http2.go b/http2.go
index b17a9c6..708f00d 100644
--- a/http2.go
+++ b/http2.go
@@ -17,6 +17,7 @@
package http2
import (
+ "fmt"
"net/http"
"strconv"
)
@@ -70,6 +71,69 @@
return stateName[st]
}
+// Setting is a setting parameter: which setting it is, and its value.
+type Setting struct {
+ // ID is which setting is being set.
+ // See http://http2.github.io/http2-spec/#SettingValues
+ ID SettingID
+
+ // Val is the value.
+ Val uint32
+}
+
+func (s Setting) String() string {
+ return fmt.Sprintf("[%v = %d]", s.ID, s.Val)
+}
+
+// Valid reports whether the setting is valid.
+func (s Setting) Valid() error {
+ // Limits and error codes from 6.5.2 Defined SETTINGS Parameters
+ switch s.ID {
+ case SettingEnablePush:
+ if s.Val != 1 && s.Val != 0 {
+ return ConnectionError(ErrCodeProtocol)
+ }
+ case SettingInitialWindowSize:
+ if s.Val > 1<<31-1 {
+ return ConnectionError(ErrCodeFlowControl)
+ }
+ case SettingMaxFrameSize:
+ if s.Val < 16384 || s.Val > 1<<24-1 {
+ return ConnectionError(ErrCodeProtocol)
+ }
+ }
+ return nil
+}
+
+// A SettingID is an HTTP/2 setting as defined in
+// http://http2.github.io/http2-spec/#iana-settings
+type SettingID uint16
+
+const (
+ SettingHeaderTableSize SettingID = 0x1
+ SettingEnablePush SettingID = 0x2
+ SettingMaxConcurrentStreams SettingID = 0x3
+ SettingInitialWindowSize SettingID = 0x4
+ SettingMaxFrameSize SettingID = 0x5
+ SettingMaxHeaderListSize SettingID = 0x6
+)
+
+var settingName = map[SettingID]string{
+ SettingHeaderTableSize: "HEADER_TABLE_SIZE",
+ SettingEnablePush: "ENABLE_PUSH",
+ SettingMaxConcurrentStreams: "MAX_CONCURRENT_STREAMS",
+ SettingInitialWindowSize: "INITIAL_WINDOW_SIZE",
+ SettingMaxFrameSize: "MAX_FRAME_SIZE",
+ SettingMaxHeaderListSize: "MAX_HEADER_LIST_SIZE",
+}
+
+func (s SettingID) String() string {
+ if v, ok := settingName[s]; ok {
+ return v
+ }
+ return fmt.Sprintf("UNKNOWN_SETTING_%d", uint8(s))
+}
+
func validHeader(v string) bool {
if len(v) == 0 {
return false
diff --git a/http2_test.go b/http2_test.go
index fa11422..8007913 100644
--- a/http2_test.go
+++ b/http2_test.go
@@ -26,6 +26,21 @@
flag.BoolVar(&VerboseLogs, "verboseh2", false, "Verbose HTTP/2 debug logging")
}
+func TestSettingString(t *testing.T) {
+ tests := []struct {
+ s Setting
+ want string
+ }{
+ {Setting{SettingMaxFrameSize, 123}, "[MAX_FRAME_SIZE = 123]"},
+ }
+ for i, tt := range tests {
+ got := fmt.Sprint(tt.s)
+ if got != tt.want {
+ t.Errorf("%d. for %#v, string = %q; want %q", i, tt.s, got, tt.want)
+ }
+ }
+}
+
type twriter struct {
t testing.TB
}
diff --git a/server.go b/server.go
index 4f30990..a84f0f0 100644
--- a/server.go
+++ b/server.go
@@ -88,21 +88,24 @@
func (srv *Server) handleConn(hs *http.Server, c net.Conn, h http.Handler) {
sc := &serverConn{
- hs: hs,
- conn: c,
- handler: h,
- framer: NewFramer(c, c), // TODO: write to a (custom?) buffered writer that can alternate when it's in buffered mode.
- streams: make(map[uint32]*stream),
- readFrameCh: make(chan frameAndGate),
- readFrameErrCh: make(chan error, 1), // must be buffered for 1
- wantWriteFrameCh: make(chan frameWriteMsg, 8),
- writeFrameCh: make(chan frameWriteMsg, 1), // may be 0 or 1, but more is useless. (max 1 in flight)
- wroteFrameCh: make(chan struct{}, 1),
- flow: newFlow(initialWindowSize),
- doneServing: make(chan struct{}),
- maxWriteFrameSize: initialMaxFrameSize,
- initialWindowSize: initialWindowSize,
- serveG: newGoroutineLock(),
+ hs: hs,
+ conn: c,
+ handler: h,
+ framer: NewFramer(c, c), // TODO: write to a (custom?) buffered writer that can alternate when it's in buffered mode.
+ streams: make(map[uint32]*stream),
+ readFrameCh: make(chan frameAndGate),
+ readFrameErrCh: make(chan error, 1), // must be buffered for 1
+ wantWriteFrameCh: make(chan frameWriteMsg, 8),
+ writeFrameCh: make(chan frameWriteMsg, 1), // may be 0 or 1, but more is useless. (max 1 in flight)
+ wroteFrameCh: make(chan struct{}, 1),
+ flow: newFlow(initialWindowSize),
+ doneServing: make(chan struct{}),
+ maxWriteFrameSize: initialMaxFrameSize,
+ initialWindowSize: initialWindowSize,
+ headerTableSize: initialHeaderTableSize,
+ maxConcurrentStreams: -1, // no limit
+ serveG: newGoroutineLock(),
+ pushEnabled: true,
}
sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf)
sc.hpackDecoder = hpack.NewDecoder(initialHeaderTableSize, sc.onNewHeaderField)
@@ -142,12 +145,16 @@
flow *flow // connection-wide (not stream-specific) flow control
// Everything following is owned by the serve loop; use serveG.check():
+ pushEnabled bool
sawFirstSettings bool // got the initial SETTINGS frame after the preface
needToSendSettingsAck bool
maxStreamID uint32 // max ever seen
streams map[uint32]*stream
- maxWriteFrameSize uint32 // TODO: update this when settings come in
+ maxWriteFrameSize uint32
initialWindowSize int32
+ headerTableSize uint32
+ maxHeaderListSize uint32 // zero means unknown (default)
+ maxConcurrentStreams int64 // negative means no limit.
canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case
sentGoAway bool
req requestParam // non-zero while reading request headers
@@ -723,24 +730,39 @@
func (sc *serverConn) processSetting(s Setting) error {
sc.serveG.check()
+ if err := s.Valid(); err != nil {
+ return err
+ }
sc.vlogf("processing setting %v", s)
switch s.ID {
+ case SettingHeaderTableSize:
+ sc.headerTableSize = s.Val
+ return nil
+ case SettingEnablePush:
+ sc.pushEnabled = s.Val != 0
+ return nil
+ case SettingMaxConcurrentStreams:
+ sc.maxConcurrentStreams = int64(s.Val)
+ return nil
case SettingInitialWindowSize:
return sc.processSettingInitialWindowSize(s.Val)
+ case SettingMaxFrameSize:
+ sc.maxWriteFrameSize = s.Val
+ return nil
+ case SettingMaxHeaderListSize:
+ sc.maxHeaderListSize = s.Val
+ return nil
}
- log.Printf("TODO: handle %v", s)
+ // Unknown setting: "An endpoint that receives a SETTINGS
+ // frame with any unknown or unsupported identifier MUST
+ // ignore that setting."
return nil
}
func (sc *serverConn) processSettingInitialWindowSize(val uint32) error {
sc.serveG.check()
- if val > (1<<31 - 1) {
- // 6.5.2 Defined SETTINGS Parameters
- // "Values above the maximum flow control window size of
- // 231-1 MUST be treated as a connection error (Section
- // 5.4.1) of type FLOW_CONTROL_ERROR."
- return ConnectionError(ErrCodeFlowControl)
- }
+ // Note: val already validated to be within range by
+ // processSetting's Valid call.
// "A SETTINGS frame can alter the initial flow control window
// size for all current streams. When the value of