http2: merge github.com/bradfitz/http2 (branch: golang.org/x/net)
This adds the http2 directory from github.com/bradfitz/http2
diff --git a/http2/.gitignore b/http2/.gitignore
new file mode 100644
index 0000000..190f122
--- /dev/null
+++ b/http2/.gitignore
@@ -0,0 +1,2 @@
+*~
+h2i/h2i
diff --git a/http2/AUTHORS b/http2/AUTHORS
new file mode 100644
index 0000000..973453e
--- /dev/null
+++ b/http2/AUTHORS
@@ -0,0 +1,20 @@
+# This file is like Go's AUTHORS file: it lists Copyright holders.
+# The list of humans who have contributd is in the CONTRIBUTORS file.
+#
+# To contribute to this project, because it will eventually be folded
+# back in to Go itself, you need to submit a CLA:
+#
+# http://golang.org/doc/contribute.html#copyright
+#
+# Then you get added to CONTRIBUTORS and you or your company get added
+# to the AUTHORS file.
+
+Blake Mizerany <blake.mizerany@gmail.com> github=bmizerany
+Daniel Morsing <daniel.morsing@gmail.com> github=DanielMorsing
+Gabriel Aszalos <gabriel.aszalos@gmail.com> github=gbbr
+Google, Inc.
+Keith Rarick <kr@xph.us> github=kr
+Matthew Keenan <tank.en.mate@gmail.com> <github@mattkeenan.net> github=mattkeenan
+Matt Layher <mdlayher@gmail.com> github=mdlayher
+Perry Abbott <perry.j.abbott@gmail.com> github=pabbott0
+Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> github=tatsuhiro-t
diff --git a/http2/CONTRIBUTORS b/http2/CONTRIBUTORS
new file mode 100644
index 0000000..f0128c1
--- /dev/null
+++ b/http2/CONTRIBUTORS
@@ -0,0 +1,20 @@
+# This file is like Go's CONTRIBUTORS file: it lists humans.
+# The list of copyright holders (which may be companies) are in the AUTHORS file.
+#
+# To contribute to this project, because it will eventually be folded
+# back in to Go itself, you need to submit a CLA:
+#
+# http://golang.org/doc/contribute.html#copyright
+#
+# Then you get added to CONTRIBUTORS and you or your company get added
+# to the AUTHORS file.
+
+Blake Mizerany <blake.mizerany@gmail.com> github=bmizerany
+Brad Fitzpatrick <bradfitz@golang.org> github=bradfitz
+Daniel Morsing <daniel.morsing@gmail.com> github=DanielMorsing
+Gabriel Aszalos <gabriel.aszalos@gmail.com> github=gbbr
+Keith Rarick <kr@xph.us> github=kr
+Matthew Keenan <tank.en.mate@gmail.com> <github@mattkeenan.net> github=mattkeenan
+Matt Layher <mdlayher@gmail.com> github=mdlayher
+Perry Abbott <perry.j.abbott@gmail.com> github=pabbott0
+Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> github=tatsuhiro-t
diff --git a/http2/Dockerfile b/http2/Dockerfile
new file mode 100644
index 0000000..b4e14d5
--- /dev/null
+++ b/http2/Dockerfile
@@ -0,0 +1,44 @@
+#
+# This Dockerfile builds a recent curl with HTTP/2 client support, using
+# a recent nghttp2 build.
+#
+# See the Makefile for how to tag it. If Docker and that image is found, the
+# Go tests use this curl binary for integration tests.
+#
+
+FROM ubuntu:trusty
+
+RUN apt-get update && \
+ apt-get upgrade -y && \
+ apt-get install -y git-core build-essential wget
+
+RUN apt-get install -y --no-install-recommends \
+ autotools-dev libtool pkg-config zlib1g-dev \
+ libcunit1-dev libssl-dev libxml2-dev libevent-dev \
+ automake autoconf
+
+# Note: setting NGHTTP2_VER before the git clone, so an old git clone isn't cached:
+ENV NGHTTP2_VER af24f8394e43f4
+RUN cd /root && git clone https://github.com/tatsuhiro-t/nghttp2.git
+
+WORKDIR /root/nghttp2
+RUN git reset --hard $NGHTTP2_VER
+RUN autoreconf -i
+RUN automake
+RUN autoconf
+RUN ./configure
+RUN make
+RUN make install
+
+WORKDIR /root
+RUN wget http://curl.haxx.se/download/curl-7.40.0.tar.gz
+RUN tar -zxvf curl-7.40.0.tar.gz
+WORKDIR /root/curl-7.40.0
+RUN ./configure --with-ssl --with-nghttp2=/usr/local
+RUN make
+RUN make install
+RUN ldconfig
+
+CMD ["-h"]
+ENTRYPOINT ["/usr/local/bin/curl"]
+
diff --git a/http2/HACKING b/http2/HACKING
new file mode 100644
index 0000000..69aafe4
--- /dev/null
+++ b/http2/HACKING
@@ -0,0 +1,5 @@
+We only accept contributions from users who have gone through Go's
+contribution process (signed a CLA).
+
+Please acknowledge whether you have (and use the same email) if
+sending a pull request.
diff --git a/http2/LICENSE b/http2/LICENSE
new file mode 100644
index 0000000..2dc6853
--- /dev/null
+++ b/http2/LICENSE
@@ -0,0 +1,7 @@
+Copyright 2014 Google & the Go AUTHORS
+
+Go AUTHORS are:
+See https://code.google.com/p/go/source/browse/AUTHORS
+
+Licensed under the terms of Go itself:
+https://code.google.com/p/go/source/browse/LICENSE
diff --git a/http2/Makefile b/http2/Makefile
new file mode 100644
index 0000000..55fd826
--- /dev/null
+++ b/http2/Makefile
@@ -0,0 +1,3 @@
+curlimage:
+ docker build -t gohttp2/curl .
+
diff --git a/http2/README b/http2/README
new file mode 100644
index 0000000..3de57de
--- /dev/null
+++ b/http2/README
@@ -0,0 +1,17 @@
+This is a work-in-progress HTTP/2 implementation for Go.
+
+It will eventually live in the Go standard library and won't require
+any changes to your code to use. It will just be automatic.
+
+Status:
+
+* The server support is pretty good. A few things are missing
+ but are being worked on.
+* The client work has just started but shares a lot of code
+ is coming along much quicker.
+
+Docs are at https://godoc.org/golang.org/x/net/http2
+
+Demo test server at https://http2.golang.org/
+
+Help & bug reports welcome.
diff --git a/http2/buffer.go b/http2/buffer.go
new file mode 100644
index 0000000..c43954c
--- /dev/null
+++ b/http2/buffer.go
@@ -0,0 +1,76 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "errors"
+)
+
+// buffer is an io.ReadWriteCloser backed by a fixed size buffer.
+// It never allocates, but moves old data as new data is written.
+type buffer struct {
+ buf []byte
+ r, w int
+ closed bool
+ err error // err to return to reader
+}
+
+var (
+ errReadEmpty = errors.New("read from empty buffer")
+ errWriteClosed = errors.New("write on closed buffer")
+ errWriteFull = errors.New("write on full buffer")
+)
+
+// Read copies bytes from the buffer into p.
+// It is an error to read when no data is available.
+func (b *buffer) Read(p []byte) (n int, err error) {
+ n = copy(p, b.buf[b.r:b.w])
+ b.r += n
+ if b.closed && b.r == b.w {
+ err = b.err
+ } else if b.r == b.w && n == 0 {
+ err = errReadEmpty
+ }
+ return n, err
+}
+
+// Len returns the number of bytes of the unread portion of the buffer.
+func (b *buffer) Len() int {
+ return b.w - b.r
+}
+
+// Write copies bytes from p into the buffer.
+// It is an error to write more data than the buffer can hold.
+func (b *buffer) Write(p []byte) (n int, err error) {
+ if b.closed {
+ return 0, errWriteClosed
+ }
+
+ // Slide existing data to beginning.
+ if b.r > 0 && len(p) > len(b.buf)-b.w {
+ copy(b.buf, b.buf[b.r:b.w])
+ b.w -= b.r
+ b.r = 0
+ }
+
+ // Write new data.
+ n = copy(b.buf[b.w:], p)
+ b.w += n
+ if n < len(p) {
+ err = errWriteFull
+ }
+ return n, err
+}
+
+// Close marks the buffer as closed. Future calls to Write will
+// return an error. Future calls to Read, once the buffer is
+// empty, will return err.
+func (b *buffer) Close(err error) {
+ if !b.closed {
+ b.closed = true
+ b.err = err
+ }
+}
diff --git a/http2/buffer_test.go b/http2/buffer_test.go
new file mode 100644
index 0000000..ea76905
--- /dev/null
+++ b/http2/buffer_test.go
@@ -0,0 +1,154 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "io"
+ "reflect"
+ "testing"
+)
+
+var bufferReadTests = []struct {
+ buf buffer
+ read, wn int
+ werr error
+ wp []byte
+ wbuf buffer
+}{
+ {
+ buffer{[]byte{'a', 0}, 0, 1, false, nil},
+ 5, 1, nil, []byte{'a'},
+ buffer{[]byte{'a', 0}, 1, 1, false, nil},
+ },
+ {
+ buffer{[]byte{'a', 0}, 0, 1, true, io.EOF},
+ 5, 1, io.EOF, []byte{'a'},
+ buffer{[]byte{'a', 0}, 1, 1, true, io.EOF},
+ },
+ {
+ buffer{[]byte{0, 'a'}, 1, 2, false, nil},
+ 5, 1, nil, []byte{'a'},
+ buffer{[]byte{0, 'a'}, 2, 2, false, nil},
+ },
+ {
+ buffer{[]byte{0, 'a'}, 1, 2, true, io.EOF},
+ 5, 1, io.EOF, []byte{'a'},
+ buffer{[]byte{0, 'a'}, 2, 2, true, io.EOF},
+ },
+ {
+ buffer{[]byte{}, 0, 0, false, nil},
+ 5, 0, errReadEmpty, []byte{},
+ buffer{[]byte{}, 0, 0, false, nil},
+ },
+ {
+ buffer{[]byte{}, 0, 0, true, io.EOF},
+ 5, 0, io.EOF, []byte{},
+ buffer{[]byte{}, 0, 0, true, io.EOF},
+ },
+}
+
+func TestBufferRead(t *testing.T) {
+ for i, tt := range bufferReadTests {
+ read := make([]byte, tt.read)
+ n, err := tt.buf.Read(read)
+ if n != tt.wn {
+ t.Errorf("#%d: wn = %d want %d", i, n, tt.wn)
+ continue
+ }
+ if err != tt.werr {
+ t.Errorf("#%d: werr = %v want %v", i, err, tt.werr)
+ continue
+ }
+ read = read[:n]
+ if !reflect.DeepEqual(read, tt.wp) {
+ t.Errorf("#%d: read = %+v want %+v", i, read, tt.wp)
+ }
+ if !reflect.DeepEqual(tt.buf, tt.wbuf) {
+ t.Errorf("#%d: buf = %+v want %+v", i, tt.buf, tt.wbuf)
+ }
+ }
+}
+
+var bufferWriteTests = []struct {
+ buf buffer
+ write, wn int
+ werr error
+ wbuf buffer
+}{
+ {
+ buf: buffer{
+ buf: []byte{},
+ },
+ wbuf: buffer{
+ buf: []byte{},
+ },
+ },
+ {
+ buf: buffer{
+ buf: []byte{1, 'a'},
+ },
+ write: 1,
+ wn: 1,
+ wbuf: buffer{
+ buf: []byte{0, 'a'},
+ w: 1,
+ },
+ },
+ {
+ buf: buffer{
+ buf: []byte{'a', 1},
+ r: 1,
+ w: 1,
+ },
+ write: 2,
+ wn: 2,
+ wbuf: buffer{
+ buf: []byte{0, 0},
+ w: 2,
+ },
+ },
+ {
+ buf: buffer{
+ buf: []byte{},
+ r: 1,
+ closed: true,
+ },
+ write: 5,
+ werr: errWriteClosed,
+ wbuf: buffer{
+ buf: []byte{},
+ r: 1,
+ closed: true,
+ },
+ },
+ {
+ buf: buffer{
+ buf: []byte{},
+ },
+ write: 5,
+ werr: errWriteFull,
+ wbuf: buffer{
+ buf: []byte{},
+ },
+ },
+}
+
+func TestBufferWrite(t *testing.T) {
+ for i, tt := range bufferWriteTests {
+ n, err := tt.buf.Write(make([]byte, tt.write))
+ if n != tt.wn {
+ t.Errorf("#%d: wrote %d bytes; want %d", i, n, tt.wn)
+ continue
+ }
+ if err != tt.werr {
+ t.Errorf("#%d: error = %v; want %v", i, err, tt.werr)
+ continue
+ }
+ if !reflect.DeepEqual(tt.buf, tt.wbuf) {
+ t.Errorf("#%d: buf = %+v; want %+v", i, tt.buf, tt.wbuf)
+ }
+ }
+}
diff --git a/http2/errors.go b/http2/errors.go
new file mode 100644
index 0000000..c885328
--- /dev/null
+++ b/http2/errors.go
@@ -0,0 +1,78 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import "fmt"
+
+// An ErrCode is an unsigned 32-bit error code as defined in the HTTP/2 spec.
+type ErrCode uint32
+
+const (
+ ErrCodeNo ErrCode = 0x0
+ ErrCodeProtocol ErrCode = 0x1
+ ErrCodeInternal ErrCode = 0x2
+ ErrCodeFlowControl ErrCode = 0x3
+ ErrCodeSettingsTimeout ErrCode = 0x4
+ ErrCodeStreamClosed ErrCode = 0x5
+ ErrCodeFrameSize ErrCode = 0x6
+ ErrCodeRefusedStream ErrCode = 0x7
+ ErrCodeCancel ErrCode = 0x8
+ ErrCodeCompression ErrCode = 0x9
+ ErrCodeConnect ErrCode = 0xa
+ ErrCodeEnhanceYourCalm ErrCode = 0xb
+ ErrCodeInadequateSecurity ErrCode = 0xc
+ ErrCodeHTTP11Required ErrCode = 0xd
+)
+
+var errCodeName = map[ErrCode]string{
+ ErrCodeNo: "NO_ERROR",
+ ErrCodeProtocol: "PROTOCOL_ERROR",
+ ErrCodeInternal: "INTERNAL_ERROR",
+ ErrCodeFlowControl: "FLOW_CONTROL_ERROR",
+ ErrCodeSettingsTimeout: "SETTINGS_TIMEOUT",
+ ErrCodeStreamClosed: "STREAM_CLOSED",
+ ErrCodeFrameSize: "FRAME_SIZE_ERROR",
+ ErrCodeRefusedStream: "REFUSED_STREAM",
+ ErrCodeCancel: "CANCEL",
+ ErrCodeCompression: "COMPRESSION_ERROR",
+ ErrCodeConnect: "CONNECT_ERROR",
+ ErrCodeEnhanceYourCalm: "ENHANCE_YOUR_CALM",
+ ErrCodeInadequateSecurity: "INADEQUATE_SECURITY",
+ ErrCodeHTTP11Required: "HTTP_1_1_REQUIRED",
+}
+
+func (e ErrCode) String() string {
+ if s, ok := errCodeName[e]; ok {
+ return s
+ }
+ return fmt.Sprintf("unknown error code 0x%x", uint32(e))
+}
+
+// ConnectionError is an error that results in the termination of the
+// entire connection.
+type ConnectionError ErrCode
+
+func (e ConnectionError) Error() string { return fmt.Sprintf("connection error: %s", ErrCode(e)) }
+
+// StreamError is an error that only affects one stream within an
+// HTTP/2 connection.
+type StreamError struct {
+ StreamID uint32
+ Code ErrCode
+}
+
+func (e StreamError) Error() string {
+ return fmt.Sprintf("stream error: stream ID %d; %v", e.StreamID, e.Code)
+}
+
+// 6.9.1 The Flow Control Window
+// "If a sender receives a WINDOW_UPDATE that causes a flow control
+// window to exceed this maximum it MUST terminate either the stream
+// or the connection, as appropriate. For streams, [...]; for the
+// connection, a GOAWAY frame with a FLOW_CONTROL_ERROR code."
+type goAwayFlowError struct{}
+
+func (goAwayFlowError) Error() string { return "connection exceeded flow control window size" }
diff --git a/http2/errors_test.go b/http2/errors_test.go
new file mode 100644
index 0000000..86d52ee
--- /dev/null
+++ b/http2/errors_test.go
@@ -0,0 +1,27 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import "testing"
+
+func TestErrCodeString(t *testing.T) {
+ tests := []struct {
+ err ErrCode
+ want string
+ }{
+ {ErrCodeProtocol, "PROTOCOL_ERROR"},
+ {0xd, "HTTP_1_1_REQUIRED"},
+ {0xf, "unknown error code 0xf"},
+ }
+ for i, tt := range tests {
+ got := tt.err.String()
+ if got != tt.want {
+ t.Errorf("%d. Error = %q; want %q", i, got, tt.want)
+ }
+ }
+}
diff --git a/http2/flow.go b/http2/flow.go
new file mode 100644
index 0000000..540fc42
--- /dev/null
+++ b/http2/flow.go
@@ -0,0 +1,51 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+// Flow control
+
+package http2
+
+// flow is the flow control window's size.
+type flow struct {
+ // n is the number of DATA bytes we're allowed to send.
+ // A flow is kept both on a conn and a per-stream.
+ n int32
+
+ // conn points to the shared connection-level flow that is
+ // shared by all streams on that conn. It is nil for the flow
+ // that's on the conn directly.
+ conn *flow
+}
+
+func (f *flow) setConnFlow(cf *flow) { f.conn = cf }
+
+func (f *flow) available() int32 {
+ n := f.n
+ if f.conn != nil && f.conn.n < n {
+ n = f.conn.n
+ }
+ return n
+}
+
+func (f *flow) take(n int32) {
+ if n > f.available() {
+ panic("internal error: took too much")
+ }
+ f.n -= n
+ if f.conn != nil {
+ f.conn.n -= n
+ }
+}
+
+// add adds n bytes (positive or negative) to the flow control window.
+// It returns false if the sum would exceed 2^31-1.
+func (f *flow) add(n int32) bool {
+ remain := (1<<31 - 1) - f.n
+ if n > remain {
+ return false
+ }
+ f.n += n
+ return true
+}
diff --git a/http2/flow_test.go b/http2/flow_test.go
new file mode 100644
index 0000000..dbb656f
--- /dev/null
+++ b/http2/flow_test.go
@@ -0,0 +1,54 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import "testing"
+
+func TestFlow(t *testing.T) {
+ var st flow
+ var conn flow
+ st.add(3)
+ conn.add(2)
+
+ if got, want := st.available(), int32(3); got != want {
+ t.Errorf("available = %d; want %d", got, want)
+ }
+ st.setConnFlow(&conn)
+ if got, want := st.available(), int32(2); got != want {
+ t.Errorf("after parent setup, available = %d; want %d", got, want)
+ }
+
+ st.take(2)
+ if got, want := conn.available(), int32(0); got != want {
+ t.Errorf("after taking 2, conn = %d; want %d", got, want)
+ }
+ if got, want := st.available(), int32(0); got != want {
+ t.Errorf("after taking 2, stream = %d; want %d", got, want)
+ }
+}
+
+func TestFlowAdd(t *testing.T) {
+ var f flow
+ if !f.add(1) {
+ t.Fatal("failed to add 1")
+ }
+ if !f.add(-1) {
+ t.Fatal("failed to add -1")
+ }
+ if got, want := f.available(), int32(0); got != want {
+ t.Fatalf("size = %d; want %d", got, want)
+ }
+ if !f.add(1<<31 - 1) {
+ t.Fatal("failed to add 2^31-1")
+ }
+ if got, want := f.available(), int32(1<<31-1); got != want {
+ t.Fatalf("size = %d; want %d", got, want)
+ }
+ if f.add(1) {
+ t.Fatal("adding 1 to max shouldn't be allowed")
+ }
+
+}
diff --git a/http2/frame.go b/http2/frame.go
new file mode 100644
index 0000000..e8b872a
--- /dev/null
+++ b/http2/frame.go
@@ -0,0 +1,1113 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "bytes"
+ "encoding/binary"
+ "errors"
+ "fmt"
+ "io"
+ "sync"
+)
+
+const frameHeaderLen = 9
+
+var padZeros = make([]byte, 255) // zeros for padding
+
+// A FrameType is a registered frame type as defined in
+// http://http2.github.io/http2-spec/#rfc.section.11.2
+type FrameType uint8
+
+const (
+ FrameData FrameType = 0x0
+ FrameHeaders FrameType = 0x1
+ FramePriority FrameType = 0x2
+ FrameRSTStream FrameType = 0x3
+ FrameSettings FrameType = 0x4
+ FramePushPromise FrameType = 0x5
+ FramePing FrameType = 0x6
+ FrameGoAway FrameType = 0x7
+ FrameWindowUpdate FrameType = 0x8
+ FrameContinuation FrameType = 0x9
+)
+
+var frameName = map[FrameType]string{
+ FrameData: "DATA",
+ FrameHeaders: "HEADERS",
+ FramePriority: "PRIORITY",
+ FrameRSTStream: "RST_STREAM",
+ FrameSettings: "SETTINGS",
+ FramePushPromise: "PUSH_PROMISE",
+ FramePing: "PING",
+ FrameGoAway: "GOAWAY",
+ FrameWindowUpdate: "WINDOW_UPDATE",
+ FrameContinuation: "CONTINUATION",
+}
+
+func (t FrameType) String() string {
+ if s, ok := frameName[t]; ok {
+ return s
+ }
+ return fmt.Sprintf("UNKNOWN_FRAME_TYPE_%d", uint8(t))
+}
+
+// Flags is a bitmask of HTTP/2 flags.
+// The meaning of flags varies depending on the frame type.
+type Flags uint8
+
+// Has reports whether f contains all (0 or more) flags in v.
+func (f Flags) Has(v Flags) bool {
+ return (f & v) == v
+}
+
+// Frame-specific FrameHeader flag bits.
+const (
+ // Data Frame
+ FlagDataEndStream Flags = 0x1
+ FlagDataPadded Flags = 0x8
+
+ // Headers Frame
+ FlagHeadersEndStream Flags = 0x1
+ FlagHeadersEndHeaders Flags = 0x4
+ FlagHeadersPadded Flags = 0x8
+ FlagHeadersPriority Flags = 0x20
+
+ // Settings Frame
+ FlagSettingsAck Flags = 0x1
+
+ // Ping Frame
+ FlagPingAck Flags = 0x1
+
+ // Continuation Frame
+ FlagContinuationEndHeaders Flags = 0x4
+
+ FlagPushPromiseEndHeaders Flags = 0x4
+ FlagPushPromisePadded Flags = 0x8
+)
+
+var flagName = map[FrameType]map[Flags]string{
+ FrameData: {
+ FlagDataEndStream: "END_STREAM",
+ FlagDataPadded: "PADDED",
+ },
+ FrameHeaders: {
+ FlagHeadersEndStream: "END_STREAM",
+ FlagHeadersEndHeaders: "END_HEADERS",
+ FlagHeadersPadded: "PADDED",
+ FlagHeadersPriority: "PRIORITY",
+ },
+ FrameSettings: {
+ FlagSettingsAck: "ACK",
+ },
+ FramePing: {
+ FlagPingAck: "ACK",
+ },
+ FrameContinuation: {
+ FlagContinuationEndHeaders: "END_HEADERS",
+ },
+ FramePushPromise: {
+ FlagPushPromiseEndHeaders: "END_HEADERS",
+ FlagPushPromisePadded: "PADDED",
+ },
+}
+
+// a frameParser parses a frame given its FrameHeader and payload
+// bytes. The length of payload will always equal fh.Length (which
+// might be 0).
+type frameParser func(fh FrameHeader, payload []byte) (Frame, error)
+
+var frameParsers = map[FrameType]frameParser{
+ FrameData: parseDataFrame,
+ FrameHeaders: parseHeadersFrame,
+ FramePriority: parsePriorityFrame,
+ FrameRSTStream: parseRSTStreamFrame,
+ FrameSettings: parseSettingsFrame,
+ FramePushPromise: parsePushPromise,
+ FramePing: parsePingFrame,
+ FrameGoAway: parseGoAwayFrame,
+ FrameWindowUpdate: parseWindowUpdateFrame,
+ FrameContinuation: parseContinuationFrame,
+}
+
+func typeFrameParser(t FrameType) frameParser {
+ if f := frameParsers[t]; f != nil {
+ return f
+ }
+ return parseUnknownFrame
+}
+
+// A FrameHeader is the 9 byte header of all HTTP/2 frames.
+//
+// See http://http2.github.io/http2-spec/#FrameHeader
+type FrameHeader struct {
+ valid bool // caller can access []byte fields in the Frame
+
+ // Type is the 1 byte frame type. There are ten standard frame
+ // types, but extension frame types may be written by WriteRawFrame
+ // and will be returned by ReadFrame (as UnknownFrame).
+ Type FrameType
+
+ // Flags are the 1 byte of 8 potential bit flags per frame.
+ // They are specific to the frame type.
+ Flags Flags
+
+ // Length is the length of the frame, not including the 9 byte header.
+ // The maximum size is one byte less than 16MB (uint24), but only
+ // frames up to 16KB are allowed without peer agreement.
+ Length uint32
+
+ // StreamID is which stream this frame is for. Certain frames
+ // are not stream-specific, in which case this field is 0.
+ StreamID uint32
+}
+
+// Header returns h. It exists so FrameHeaders can be embedded in other
+// specific frame types and implement the Frame interface.
+func (h FrameHeader) Header() FrameHeader { return h }
+
+func (h FrameHeader) String() string {
+ var buf bytes.Buffer
+ buf.WriteString("[FrameHeader ")
+ buf.WriteString(h.Type.String())
+ if h.Flags != 0 {
+ buf.WriteString(" flags=")
+ set := 0
+ for i := uint8(0); i < 8; i++ {
+ if h.Flags&(1<<i) == 0 {
+ continue
+ }
+ set++
+ if set > 1 {
+ buf.WriteByte('|')
+ }
+ name := flagName[h.Type][Flags(1<<i)]
+ if name != "" {
+ buf.WriteString(name)
+ } else {
+ fmt.Fprintf(&buf, "0x%x", 1<<i)
+ }
+ }
+ }
+ if h.StreamID != 0 {
+ fmt.Fprintf(&buf, " stream=%d", h.StreamID)
+ }
+ fmt.Fprintf(&buf, " len=%d]", h.Length)
+ return buf.String()
+}
+
+func (h *FrameHeader) checkValid() {
+ if !h.valid {
+ panic("Frame accessor called on non-owned Frame")
+ }
+}
+
+func (h *FrameHeader) invalidate() { h.valid = false }
+
+// frame header bytes.
+// Used only by ReadFrameHeader.
+var fhBytes = sync.Pool{
+ New: func() interface{} {
+ buf := make([]byte, frameHeaderLen)
+ return &buf
+ },
+}
+
+// ReadFrameHeader reads 9 bytes from r and returns a FrameHeader.
+// Most users should use Framer.ReadFrame instead.
+func ReadFrameHeader(r io.Reader) (FrameHeader, error) {
+ bufp := fhBytes.Get().(*[]byte)
+ defer fhBytes.Put(bufp)
+ return readFrameHeader(*bufp, r)
+}
+
+func readFrameHeader(buf []byte, r io.Reader) (FrameHeader, error) {
+ _, err := io.ReadFull(r, buf[:frameHeaderLen])
+ if err != nil {
+ return FrameHeader{}, err
+ }
+ return FrameHeader{
+ Length: (uint32(buf[0])<<16 | uint32(buf[1])<<8 | uint32(buf[2])),
+ Type: FrameType(buf[3]),
+ Flags: Flags(buf[4]),
+ StreamID: binary.BigEndian.Uint32(buf[5:]) & (1<<31 - 1),
+ valid: true,
+ }, nil
+}
+
+// A Frame is the base interface implemented by all frame types.
+// Callers will generally type-assert the specific frame type:
+// *HeadersFrame, *SettingsFrame, *WindowUpdateFrame, etc.
+//
+// Frames are only valid until the next call to Framer.ReadFrame.
+type Frame interface {
+ Header() FrameHeader
+
+ // invalidate is called by Framer.ReadFrame to make this
+ // frame's buffers as being invalid, since the subsequent
+ // frame will reuse them.
+ invalidate()
+}
+
+// A Framer reads and writes Frames.
+type Framer struct {
+ r io.Reader
+ lastFrame Frame
+
+ maxReadSize uint32
+ headerBuf [frameHeaderLen]byte
+
+ // TODO: let getReadBuf be configurable, and use a less memory-pinning
+ // allocator in server.go to minimize memory pinned for many idle conns.
+ // Will probably also need to make frame invalidation have a hook too.
+ getReadBuf func(size uint32) []byte
+ readBuf []byte // cache for default getReadBuf
+
+ maxWriteSize uint32 // zero means unlimited; TODO: implement
+
+ w io.Writer
+ wbuf []byte
+
+ // AllowIllegalWrites permits the Framer's Write methods to
+ // write frames that do not conform to the HTTP/2 spec. This
+ // permits using the Framer to test other HTTP/2
+ // implementations' conformance to the spec.
+ // If false, the Write methods will prefer to return an error
+ // rather than comply.
+ AllowIllegalWrites bool
+
+ // TODO: track which type of frame & with which flags was sent
+ // last. Then return an error (unless AllowIllegalWrites) if
+ // we're in the middle of a header block and a
+ // non-Continuation or Continuation on a different stream is
+ // attempted to be written.
+}
+
+func (f *Framer) startWrite(ftype FrameType, flags Flags, streamID uint32) {
+ // Write the FrameHeader.
+ f.wbuf = append(f.wbuf[:0],
+ 0, // 3 bytes of length, filled in in endWrite
+ 0,
+ 0,
+ byte(ftype),
+ byte(flags),
+ byte(streamID>>24),
+ byte(streamID>>16),
+ byte(streamID>>8),
+ byte(streamID))
+}
+
+func (f *Framer) endWrite() error {
+ // Now that we know the final size, fill in the FrameHeader in
+ // the space previously reserved for it. Abuse append.
+ length := len(f.wbuf) - frameHeaderLen
+ if length >= (1 << 24) {
+ return ErrFrameTooLarge
+ }
+ _ = append(f.wbuf[:0],
+ byte(length>>16),
+ byte(length>>8),
+ byte(length))
+ n, err := f.w.Write(f.wbuf)
+ if err == nil && n != len(f.wbuf) {
+ err = io.ErrShortWrite
+ }
+ return err
+}
+
+func (f *Framer) writeByte(v byte) { f.wbuf = append(f.wbuf, v) }
+func (f *Framer) writeBytes(v []byte) { f.wbuf = append(f.wbuf, v...) }
+func (f *Framer) writeUint16(v uint16) { f.wbuf = append(f.wbuf, byte(v>>8), byte(v)) }
+func (f *Framer) writeUint32(v uint32) {
+ f.wbuf = append(f.wbuf, byte(v>>24), byte(v>>16), byte(v>>8), byte(v))
+}
+
+const (
+ minMaxFrameSize = 1 << 14
+ maxFrameSize = 1<<24 - 1
+)
+
+// NewFramer returns a Framer that writes frames to w and reads them from r.
+func NewFramer(w io.Writer, r io.Reader) *Framer {
+ fr := &Framer{
+ w: w,
+ r: r,
+ }
+ fr.getReadBuf = func(size uint32) []byte {
+ if cap(fr.readBuf) >= int(size) {
+ return fr.readBuf[:size]
+ }
+ fr.readBuf = make([]byte, size)
+ return fr.readBuf
+ }
+ fr.SetMaxReadFrameSize(maxFrameSize)
+ return fr
+}
+
+// SetMaxReadFrameSize sets the maximum size of a frame
+// that will be read by a subsequent call to ReadFrame.
+// It is the caller's responsibility to advertise this
+// limit with a SETTINGS frame.
+func (fr *Framer) SetMaxReadFrameSize(v uint32) {
+ if v > maxFrameSize {
+ v = maxFrameSize
+ }
+ fr.maxReadSize = v
+}
+
+// ErrFrameTooLarge is returned from Framer.ReadFrame when the peer
+// sends a frame that is larger than declared with SetMaxReadFrameSize.
+var ErrFrameTooLarge = errors.New("http2: frame too large")
+
+// ReadFrame reads a single frame. The returned Frame is only valid
+// until the next call to ReadFrame.
+// If the frame is larger than previously set with SetMaxReadFrameSize,
+// the returned error is ErrFrameTooLarge.
+func (fr *Framer) ReadFrame() (Frame, error) {
+ if fr.lastFrame != nil {
+ fr.lastFrame.invalidate()
+ }
+ fh, err := readFrameHeader(fr.headerBuf[:], fr.r)
+ if err != nil {
+ return nil, err
+ }
+ if fh.Length > fr.maxReadSize {
+ return nil, ErrFrameTooLarge
+ }
+ payload := fr.getReadBuf(fh.Length)
+ if _, err := io.ReadFull(fr.r, payload); err != nil {
+ return nil, err
+ }
+ f, err := typeFrameParser(fh.Type)(fh, payload)
+ if err != nil {
+ return nil, err
+ }
+ fr.lastFrame = f
+ return f, nil
+}
+
+// A DataFrame conveys arbitrary, variable-length sequences of octets
+// associated with a stream.
+// See http://http2.github.io/http2-spec/#rfc.section.6.1
+type DataFrame struct {
+ FrameHeader
+ data []byte
+}
+
+func (f *DataFrame) StreamEnded() bool {
+ return f.FrameHeader.Flags.Has(FlagDataEndStream)
+}
+
+// Data returns the frame's data octets, not including any padding
+// size byte or padding suffix bytes.
+// The caller must not retain the returned memory past the next
+// call to ReadFrame.
+func (f *DataFrame) Data() []byte {
+ f.checkValid()
+ return f.data
+}
+
+func parseDataFrame(fh FrameHeader, payload []byte) (Frame, error) {
+ if fh.StreamID == 0 {
+ // DATA frames MUST be associated with a stream. If a
+ // DATA frame is received whose stream identifier
+ // field is 0x0, the recipient MUST respond with a
+ // connection error (Section 5.4.1) of type
+ // PROTOCOL_ERROR.
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+ f := &DataFrame{
+ FrameHeader: fh,
+ }
+ var padSize byte
+ if fh.Flags.Has(FlagDataPadded) {
+ var err error
+ payload, padSize, err = readByte(payload)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if int(padSize) > len(payload) {
+ // If the length of the padding is greater than the
+ // length of the frame payload, the recipient MUST
+ // treat this as a connection error.
+ // Filed: https://github.com/http2/http2-spec/issues/610
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+ f.data = payload[:len(payload)-int(padSize)]
+ return f, nil
+}
+
+var errStreamID = errors.New("invalid streamid")
+
+func validStreamID(streamID uint32) bool {
+ return streamID != 0 && streamID&(1<<31) == 0
+}
+
+// WriteData writes a DATA frame.
+//
+// It will perform exactly one Write to the underlying Writer.
+// It is the caller's responsibility to not call other Write methods concurrently.
+func (f *Framer) WriteData(streamID uint32, endStream bool, data []byte) error {
+ // TODO: ignoring padding for now. will add when somebody cares.
+ if !validStreamID(streamID) && !f.AllowIllegalWrites {
+ return errStreamID
+ }
+ var flags Flags
+ if endStream {
+ flags |= FlagDataEndStream
+ }
+ f.startWrite(FrameData, flags, streamID)
+ f.wbuf = append(f.wbuf, data...)
+ return f.endWrite()
+}
+
+// A SettingsFrame conveys configuration parameters that affect how
+// endpoints communicate, such as preferences and constraints on peer
+// behavior.
+//
+// See http://http2.github.io/http2-spec/#SETTINGS
+type SettingsFrame struct {
+ FrameHeader
+ p []byte
+}
+
+func parseSettingsFrame(fh FrameHeader, p []byte) (Frame, error) {
+ if fh.Flags.Has(FlagSettingsAck) && fh.Length > 0 {
+ // When this (ACK 0x1) bit is set, the payload of the
+ // SETTINGS frame MUST be empty. Receipt of a
+ // SETTINGS frame with the ACK flag set and a length
+ // field value other than 0 MUST be treated as a
+ // connection error (Section 5.4.1) of type
+ // FRAME_SIZE_ERROR.
+ return nil, ConnectionError(ErrCodeFrameSize)
+ }
+ if fh.StreamID != 0 {
+ // SETTINGS frames always apply to a connection,
+ // never a single stream. The stream identifier for a
+ // SETTINGS frame MUST be zero (0x0). If an endpoint
+ // receives a SETTINGS frame whose stream identifier
+ // field is anything other than 0x0, the endpoint MUST
+ // respond with a connection error (Section 5.4.1) of
+ // type PROTOCOL_ERROR.
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+ if len(p)%6 != 0 {
+ // Expecting even number of 6 byte settings.
+ return nil, ConnectionError(ErrCodeFrameSize)
+ }
+ f := &SettingsFrame{FrameHeader: fh, p: p}
+ if v, ok := f.Value(SettingInitialWindowSize); ok && v > (1<<31)-1 {
+ // Values above the maximum flow control window size of 2^31 - 1 MUST
+ // be treated as a connection error (Section 5.4.1) of type
+ // FLOW_CONTROL_ERROR.
+ return nil, ConnectionError(ErrCodeFlowControl)
+ }
+ return f, nil
+}
+
+func (f *SettingsFrame) IsAck() bool {
+ return f.FrameHeader.Flags.Has(FlagSettingsAck)
+}
+
+func (f *SettingsFrame) Value(s SettingID) (v uint32, ok bool) {
+ f.checkValid()
+ buf := f.p
+ for len(buf) > 0 {
+ settingID := SettingID(binary.BigEndian.Uint16(buf[:2]))
+ if settingID == s {
+ return binary.BigEndian.Uint32(buf[2:6]), true
+ }
+ buf = buf[6:]
+ }
+ return 0, false
+}
+
+// ForeachSetting runs fn for each setting.
+// It stops and returns the first error.
+func (f *SettingsFrame) ForeachSetting(fn func(Setting) error) error {
+ f.checkValid()
+ buf := f.p
+ for len(buf) > 0 {
+ if err := fn(Setting{
+ SettingID(binary.BigEndian.Uint16(buf[:2])),
+ binary.BigEndian.Uint32(buf[2:6]),
+ }); err != nil {
+ return err
+ }
+ buf = buf[6:]
+ }
+ return nil
+}
+
+// WriteSettings writes a SETTINGS frame with zero or more settings
+// specified and the ACK bit not set.
+//
+// It will perform exactly one Write to the underlying Writer.
+// It is the caller's responsibility to not call other Write methods concurrently.
+func (f *Framer) WriteSettings(settings ...Setting) error {
+ f.startWrite(FrameSettings, 0, 0)
+ for _, s := range settings {
+ f.writeUint16(uint16(s.ID))
+ f.writeUint32(s.Val)
+ }
+ return f.endWrite()
+}
+
+// WriteSettings writes an empty SETTINGS frame with the ACK bit set.
+//
+// It will perform exactly one Write to the underlying Writer.
+// It is the caller's responsibility to not call other Write methods concurrently.
+func (f *Framer) WriteSettingsAck() error {
+ f.startWrite(FrameSettings, FlagSettingsAck, 0)
+ return f.endWrite()
+}
+
+// A PingFrame is a mechanism for measuring a minimal round trip time
+// from the sender, as well as determining whether an idle connection
+// is still functional.
+// See http://http2.github.io/http2-spec/#rfc.section.6.7
+type PingFrame struct {
+ FrameHeader
+ Data [8]byte
+}
+
+func parsePingFrame(fh FrameHeader, payload []byte) (Frame, error) {
+ if len(payload) != 8 {
+ return nil, ConnectionError(ErrCodeFrameSize)
+ }
+ if fh.StreamID != 0 {
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+ f := &PingFrame{FrameHeader: fh}
+ copy(f.Data[:], payload)
+ return f, nil
+}
+
+func (f *Framer) WritePing(ack bool, data [8]byte) error {
+ var flags Flags
+ if ack {
+ flags = FlagPingAck
+ }
+ f.startWrite(FramePing, flags, 0)
+ f.writeBytes(data[:])
+ return f.endWrite()
+}
+
+// A GoAwayFrame informs the remote peer to stop creating streams on this connection.
+// See http://http2.github.io/http2-spec/#rfc.section.6.8
+type GoAwayFrame struct {
+ FrameHeader
+ LastStreamID uint32
+ ErrCode ErrCode
+ debugData []byte
+}
+
+// DebugData returns any debug data in the GOAWAY frame. Its contents
+// are not defined.
+// The caller must not retain the returned memory past the next
+// call to ReadFrame.
+func (f *GoAwayFrame) DebugData() []byte {
+ f.checkValid()
+ return f.debugData
+}
+
+func parseGoAwayFrame(fh FrameHeader, p []byte) (Frame, error) {
+ if fh.StreamID != 0 {
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+ if len(p) < 8 {
+ return nil, ConnectionError(ErrCodeFrameSize)
+ }
+ return &GoAwayFrame{
+ FrameHeader: fh,
+ LastStreamID: binary.BigEndian.Uint32(p[:4]) & (1<<31 - 1),
+ ErrCode: ErrCode(binary.BigEndian.Uint32(p[4:8])),
+ debugData: p[8:],
+ }, nil
+}
+
+func (f *Framer) WriteGoAway(maxStreamID uint32, code ErrCode, debugData []byte) error {
+ f.startWrite(FrameGoAway, 0, 0)
+ f.writeUint32(maxStreamID & (1<<31 - 1))
+ f.writeUint32(uint32(code))
+ f.writeBytes(debugData)
+ return f.endWrite()
+}
+
+// An UnknownFrame is the frame type returned when the frame type is unknown
+// or no specific frame type parser exists.
+type UnknownFrame struct {
+ FrameHeader
+ p []byte
+}
+
+// Payload returns the frame's payload (after the header). It is not
+// valid to call this method after a subsequent call to
+// Framer.ReadFrame, nor is it valid to retain the returned slice.
+// The memory is owned by the Framer and is invalidated when the next
+// frame is read.
+func (f *UnknownFrame) Payload() []byte {
+ f.checkValid()
+ return f.p
+}
+
+func parseUnknownFrame(fh FrameHeader, p []byte) (Frame, error) {
+ return &UnknownFrame{fh, p}, nil
+}
+
+// A WindowUpdateFrame is used to implement flow control.
+// See http://http2.github.io/http2-spec/#rfc.section.6.9
+type WindowUpdateFrame struct {
+ FrameHeader
+ Increment uint32
+}
+
+func parseWindowUpdateFrame(fh FrameHeader, p []byte) (Frame, error) {
+ if len(p) != 4 {
+ return nil, ConnectionError(ErrCodeFrameSize)
+ }
+ inc := binary.BigEndian.Uint32(p[:4]) & 0x7fffffff // mask off high reserved bit
+ if inc == 0 {
+ // A receiver MUST treat the receipt of a
+ // WINDOW_UPDATE frame with an flow control window
+ // increment of 0 as a stream error (Section 5.4.2) of
+ // type PROTOCOL_ERROR; errors on the connection flow
+ // control window MUST be treated as a connection
+ // error (Section 5.4.1).
+ if fh.StreamID == 0 {
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+ return nil, StreamError{fh.StreamID, ErrCodeProtocol}
+ }
+ return &WindowUpdateFrame{
+ FrameHeader: fh,
+ Increment: inc,
+ }, nil
+}
+
+// WriteWindowUpdate writes a WINDOW_UPDATE frame.
+// The increment value must be between 1 and 2,147,483,647, inclusive.
+// If the Stream ID is zero, the window update applies to the
+// connection as a whole.
+func (f *Framer) WriteWindowUpdate(streamID, incr uint32) error {
+ // "The legal range for the increment to the flow control window is 1 to 2^31-1 (2,147,483,647) octets."
+ if (incr < 1 || incr > 2147483647) && !f.AllowIllegalWrites {
+ return errors.New("illegal window increment value")
+ }
+ f.startWrite(FrameWindowUpdate, 0, streamID)
+ f.writeUint32(incr)
+ return f.endWrite()
+}
+
+// A HeadersFrame is used to open a stream and additionally carries a
+// header block fragment.
+type HeadersFrame struct {
+ FrameHeader
+
+ // Priority is set if FlagHeadersPriority is set in the FrameHeader.
+ Priority PriorityParam
+
+ headerFragBuf []byte // not owned
+}
+
+func (f *HeadersFrame) HeaderBlockFragment() []byte {
+ f.checkValid()
+ return f.headerFragBuf
+}
+
+func (f *HeadersFrame) HeadersEnded() bool {
+ return f.FrameHeader.Flags.Has(FlagHeadersEndHeaders)
+}
+
+func (f *HeadersFrame) StreamEnded() bool {
+ return f.FrameHeader.Flags.Has(FlagHeadersEndStream)
+}
+
+func (f *HeadersFrame) HasPriority() bool {
+ return f.FrameHeader.Flags.Has(FlagHeadersPriority)
+}
+
+func parseHeadersFrame(fh FrameHeader, p []byte) (_ Frame, err error) {
+ hf := &HeadersFrame{
+ FrameHeader: fh,
+ }
+ if fh.StreamID == 0 {
+ // HEADERS frames MUST be associated with a stream. If a HEADERS frame
+ // is received whose stream identifier field is 0x0, the recipient MUST
+ // respond with a connection error (Section 5.4.1) of type
+ // PROTOCOL_ERROR.
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+ var padLength uint8
+ if fh.Flags.Has(FlagHeadersPadded) {
+ if p, padLength, err = readByte(p); err != nil {
+ return
+ }
+ }
+ if fh.Flags.Has(FlagHeadersPriority) {
+ var v uint32
+ p, v, err = readUint32(p)
+ if err != nil {
+ return nil, err
+ }
+ hf.Priority.StreamDep = v & 0x7fffffff
+ hf.Priority.Exclusive = (v != hf.Priority.StreamDep) // high bit was set
+ p, hf.Priority.Weight, err = readByte(p)
+ if err != nil {
+ return nil, err
+ }
+ }
+ if len(p)-int(padLength) <= 0 {
+ return nil, StreamError{fh.StreamID, ErrCodeProtocol}
+ }
+ hf.headerFragBuf = p[:len(p)-int(padLength)]
+ return hf, nil
+}
+
+// HeadersFrameParam are the parameters for writing a HEADERS frame.
+type HeadersFrameParam struct {
+ // StreamID is the required Stream ID to initiate.
+ StreamID uint32
+ // BlockFragment is part (or all) of a Header Block.
+ BlockFragment []byte
+
+ // EndStream indicates that the header block is the last that
+ // the endpoint will send for the identified stream. Setting
+ // this flag causes the stream to enter one of "half closed"
+ // states.
+ EndStream bool
+
+ // EndHeaders indicates that this frame contains an entire
+ // header block and is not followed by any
+ // CONTINUATION frames.
+ EndHeaders bool
+
+ // PadLength is the optional number of bytes of zeros to add
+ // to this frame.
+ PadLength uint8
+
+ // Priority, if non-zero, includes stream priority information
+ // in the HEADER frame.
+ Priority PriorityParam
+}
+
+// WriteHeaders writes a single HEADERS frame.
+//
+// This is a low-level header writing method. Encoding headers and
+// splitting them into any necessary CONTINUATION frames is handled
+// elsewhere.
+//
+// It will perform exactly one Write to the underlying Writer.
+// It is the caller's responsibility to not call other Write methods concurrently.
+func (f *Framer) WriteHeaders(p HeadersFrameParam) error {
+ if !validStreamID(p.StreamID) && !f.AllowIllegalWrites {
+ return errStreamID
+ }
+ var flags Flags
+ if p.PadLength != 0 {
+ flags |= FlagHeadersPadded
+ }
+ if p.EndStream {
+ flags |= FlagHeadersEndStream
+ }
+ if p.EndHeaders {
+ flags |= FlagHeadersEndHeaders
+ }
+ if !p.Priority.IsZero() {
+ flags |= FlagHeadersPriority
+ }
+ f.startWrite(FrameHeaders, flags, p.StreamID)
+ if p.PadLength != 0 {
+ f.writeByte(p.PadLength)
+ }
+ if !p.Priority.IsZero() {
+ v := p.Priority.StreamDep
+ if !validStreamID(v) && !f.AllowIllegalWrites {
+ return errors.New("invalid dependent stream id")
+ }
+ if p.Priority.Exclusive {
+ v |= 1 << 31
+ }
+ f.writeUint32(v)
+ f.writeByte(p.Priority.Weight)
+ }
+ f.wbuf = append(f.wbuf, p.BlockFragment...)
+ f.wbuf = append(f.wbuf, padZeros[:p.PadLength]...)
+ return f.endWrite()
+}
+
+// A PriorityFrame specifies the sender-advised priority of a stream.
+// See http://http2.github.io/http2-spec/#rfc.section.6.3
+type PriorityFrame struct {
+ FrameHeader
+ PriorityParam
+}
+
+// PriorityParam are the stream prioritzation parameters.
+type PriorityParam struct {
+ // StreamDep is a 31-bit stream identifier for the
+ // stream that this stream depends on. Zero means no
+ // dependency.
+ StreamDep uint32
+
+ // Exclusive is whether the dependency is exclusive.
+ Exclusive bool
+
+ // Weight is the stream's zero-indexed weight. It should be
+ // set together with StreamDep, or neither should be set. Per
+ // the spec, "Add one to the value to obtain a weight between
+ // 1 and 256."
+ Weight uint8
+}
+
+func (p PriorityParam) IsZero() bool {
+ return p == PriorityParam{}
+}
+
+func parsePriorityFrame(fh FrameHeader, payload []byte) (Frame, error) {
+ if fh.StreamID == 0 {
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+ if len(payload) != 5 {
+ return nil, ConnectionError(ErrCodeFrameSize)
+ }
+ v := binary.BigEndian.Uint32(payload[:4])
+ streamID := v & 0x7fffffff // mask off high bit
+ return &PriorityFrame{
+ FrameHeader: fh,
+ PriorityParam: PriorityParam{
+ Weight: payload[4],
+ StreamDep: streamID,
+ Exclusive: streamID != v, // was high bit set?
+ },
+ }, nil
+}
+
+// WritePriority writes a PRIORITY frame.
+//
+// It will perform exactly one Write to the underlying Writer.
+// It is the caller's responsibility to not call other Write methods concurrently.
+func (f *Framer) WritePriority(streamID uint32, p PriorityParam) error {
+ if !validStreamID(streamID) && !f.AllowIllegalWrites {
+ return errStreamID
+ }
+ f.startWrite(FramePriority, 0, streamID)
+ v := p.StreamDep
+ if p.Exclusive {
+ v |= 1 << 31
+ }
+ f.writeUint32(v)
+ f.writeByte(p.Weight)
+ return f.endWrite()
+}
+
+// A RSTStreamFrame allows for abnormal termination of a stream.
+// See http://http2.github.io/http2-spec/#rfc.section.6.4
+type RSTStreamFrame struct {
+ FrameHeader
+ ErrCode ErrCode
+}
+
+func parseRSTStreamFrame(fh FrameHeader, p []byte) (Frame, error) {
+ if len(p) != 4 {
+ return nil, ConnectionError(ErrCodeFrameSize)
+ }
+ if fh.StreamID == 0 {
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+ return &RSTStreamFrame{fh, ErrCode(binary.BigEndian.Uint32(p[:4]))}, nil
+}
+
+// WriteRSTStream writes a RST_STREAM frame.
+//
+// It will perform exactly one Write to the underlying Writer.
+// It is the caller's responsibility to not call other Write methods concurrently.
+func (f *Framer) WriteRSTStream(streamID uint32, code ErrCode) error {
+ if !validStreamID(streamID) && !f.AllowIllegalWrites {
+ return errStreamID
+ }
+ f.startWrite(FrameRSTStream, 0, streamID)
+ f.writeUint32(uint32(code))
+ return f.endWrite()
+}
+
+// A ContinuationFrame is used to continue a sequence of header block fragments.
+// See http://http2.github.io/http2-spec/#rfc.section.6.10
+type ContinuationFrame struct {
+ FrameHeader
+ headerFragBuf []byte
+}
+
+func parseContinuationFrame(fh FrameHeader, p []byte) (Frame, error) {
+ return &ContinuationFrame{fh, p}, nil
+}
+
+func (f *ContinuationFrame) StreamEnded() bool {
+ return f.FrameHeader.Flags.Has(FlagDataEndStream)
+}
+
+func (f *ContinuationFrame) HeaderBlockFragment() []byte {
+ f.checkValid()
+ return f.headerFragBuf
+}
+
+func (f *ContinuationFrame) HeadersEnded() bool {
+ return f.FrameHeader.Flags.Has(FlagContinuationEndHeaders)
+}
+
+// WriteContinuation writes a CONTINUATION frame.
+//
+// It will perform exactly one Write to the underlying Writer.
+// It is the caller's responsibility to not call other Write methods concurrently.
+func (f *Framer) WriteContinuation(streamID uint32, endHeaders bool, headerBlockFragment []byte) error {
+ if !validStreamID(streamID) && !f.AllowIllegalWrites {
+ return errStreamID
+ }
+ var flags Flags
+ if endHeaders {
+ flags |= FlagContinuationEndHeaders
+ }
+ f.startWrite(FrameContinuation, flags, streamID)
+ f.wbuf = append(f.wbuf, headerBlockFragment...)
+ return f.endWrite()
+}
+
+// A PushPromiseFrame is used to initiate a server stream.
+// See http://http2.github.io/http2-spec/#rfc.section.6.6
+type PushPromiseFrame struct {
+ FrameHeader
+ PromiseID uint32
+ headerFragBuf []byte // not owned
+}
+
+func (f *PushPromiseFrame) HeaderBlockFragment() []byte {
+ f.checkValid()
+ return f.headerFragBuf
+}
+
+func (f *PushPromiseFrame) HeadersEnded() bool {
+ return f.FrameHeader.Flags.Has(FlagPushPromiseEndHeaders)
+}
+
+func parsePushPromise(fh FrameHeader, p []byte) (_ Frame, err error) {
+ pp := &PushPromiseFrame{
+ FrameHeader: fh,
+ }
+ if pp.StreamID == 0 {
+ // PUSH_PROMISE frames MUST be associated with an existing,
+ // peer-initiated stream. The stream identifier of a
+ // PUSH_PROMISE frame indicates the stream it is associated
+ // with. If the stream identifier field specifies the value
+ // 0x0, a recipient MUST respond with a connection error
+ // (Section 5.4.1) of type PROTOCOL_ERROR.
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+ // The PUSH_PROMISE frame includes optional padding.
+ // Padding fields and flags are identical to those defined for DATA frames
+ var padLength uint8
+ if fh.Flags.Has(FlagPushPromisePadded) {
+ if p, padLength, err = readByte(p); err != nil {
+ return
+ }
+ }
+
+ p, pp.PromiseID, err = readUint32(p)
+ if err != nil {
+ return
+ }
+ pp.PromiseID = pp.PromiseID & (1<<31 - 1)
+
+ if int(padLength) > len(p) {
+ // like the DATA frame, error out if padding is longer than the body.
+ return nil, ConnectionError(ErrCodeProtocol)
+ }
+ pp.headerFragBuf = p[:len(p)-int(padLength)]
+ return pp, nil
+}
+
+// PushPromiseParam are the parameters for writing a PUSH_PROMISE frame.
+type PushPromiseParam struct {
+ // StreamID is the required Stream ID to initiate.
+ StreamID uint32
+
+ // PromiseID is the required Stream ID which this
+ // Push Promises
+ PromiseID uint32
+
+ // BlockFragment is part (or all) of a Header Block.
+ BlockFragment []byte
+
+ // EndHeaders indicates that this frame contains an entire
+ // header block and is not followed by any
+ // CONTINUATION frames.
+ EndHeaders bool
+
+ // PadLength is the optional number of bytes of zeros to add
+ // to this frame.
+ PadLength uint8
+}
+
+// WritePushPromise writes a single PushPromise Frame.
+//
+// As with Header Frames, This is the low level call for writing
+// individual frames. Continuation frames are handled elsewhere.
+//
+// It will perform exactly one Write to the underlying Writer.
+// It is the caller's responsibility to not call other Write methods concurrently.
+func (f *Framer) WritePushPromise(p PushPromiseParam) error {
+ if !validStreamID(p.StreamID) && !f.AllowIllegalWrites {
+ return errStreamID
+ }
+ var flags Flags
+ if p.PadLength != 0 {
+ flags |= FlagPushPromisePadded
+ }
+ if p.EndHeaders {
+ flags |= FlagPushPromiseEndHeaders
+ }
+ f.startWrite(FramePushPromise, flags, p.StreamID)
+ if p.PadLength != 0 {
+ f.writeByte(p.PadLength)
+ }
+ if !validStreamID(p.PromiseID) && !f.AllowIllegalWrites {
+ return errStreamID
+ }
+ f.writeUint32(p.PromiseID)
+ f.wbuf = append(f.wbuf, p.BlockFragment...)
+ f.wbuf = append(f.wbuf, padZeros[:p.PadLength]...)
+ return f.endWrite()
+}
+
+// WriteRawFrame writes a raw frame. This can be used to write
+// extension frames unknown to this package.
+func (f *Framer) WriteRawFrame(t FrameType, flags Flags, streamID uint32, payload []byte) error {
+ f.startWrite(t, flags, streamID)
+ f.writeBytes(payload)
+ return f.endWrite()
+}
+
+func readByte(p []byte) (remain []byte, b byte, err error) {
+ if len(p) == 0 {
+ return nil, 0, io.ErrUnexpectedEOF
+ }
+ return p[1:], p[0], nil
+}
+
+func readUint32(p []byte) (remain []byte, v uint32, err error) {
+ if len(p) < 4 {
+ return nil, 0, io.ErrUnexpectedEOF
+ }
+ return p[4:], binary.BigEndian.Uint32(p[:4]), nil
+}
+
+type streamEnder interface {
+ StreamEnded() bool
+}
+
+type headersEnder interface {
+ HeadersEnded() bool
+}
diff --git a/http2/frame_test.go b/http2/frame_test.go
new file mode 100644
index 0000000..20027c5
--- /dev/null
+++ b/http2/frame_test.go
@@ -0,0 +1,597 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package http2
+
+import (
+ "bytes"
+ "reflect"
+ "strings"
+ "testing"
+ "unsafe"
+)
+
+func testFramer() (*Framer, *bytes.Buffer) {
+ buf := new(bytes.Buffer)
+ return NewFramer(buf, buf), buf
+}
+
+func TestFrameSizes(t *testing.T) {
+ // Catch people rearranging the FrameHeader fields.
+ if got, want := int(unsafe.Sizeof(FrameHeader{})), 12; got != want {
+ t.Errorf("FrameHeader size = %d; want %d", got, want)
+ }
+}
+
+func TestFrameTypeString(t *testing.T) {
+ tests := []struct {
+ ft FrameType
+ want string
+ }{
+ {FrameData, "DATA"},
+ {FramePing, "PING"},
+ {FrameGoAway, "GOAWAY"},
+ {0xf, "UNKNOWN_FRAME_TYPE_15"},
+ }
+
+ for i, tt := range tests {
+ got := tt.ft.String()
+ if got != tt.want {
+ t.Errorf("%d. String(FrameType %d) = %q; want %q", i, int(tt.ft), got, tt.want)
+ }
+ }
+}
+
+func TestWriteRST(t *testing.T) {
+ fr, buf := testFramer()
+ var streamID uint32 = 1<<24 + 2<<16 + 3<<8 + 4
+ var errCode uint32 = 7<<24 + 6<<16 + 5<<8 + 4
+ fr.WriteRSTStream(streamID, ErrCode(errCode))
+ const wantEnc = "\x00\x00\x04\x03\x00\x01\x02\x03\x04\x07\x06\x05\x04"
+ if buf.String() != wantEnc {
+ t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+ }
+ f, err := fr.ReadFrame()
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := &RSTStreamFrame{
+ FrameHeader: FrameHeader{
+ valid: true,
+ Type: 0x3,
+ Flags: 0x0,
+ Length: 0x4,
+ StreamID: 0x1020304,
+ },
+ ErrCode: 0x7060504,
+ }
+ if !reflect.DeepEqual(f, want) {
+ t.Errorf("parsed back %#v; want %#v", f, want)
+ }
+}
+
+func TestWriteData(t *testing.T) {
+ fr, buf := testFramer()
+ var streamID uint32 = 1<<24 + 2<<16 + 3<<8 + 4
+ data := []byte("ABC")
+ fr.WriteData(streamID, true, data)
+ const wantEnc = "\x00\x00\x03\x00\x01\x01\x02\x03\x04ABC"
+ if buf.String() != wantEnc {
+ t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+ }
+ f, err := fr.ReadFrame()
+ if err != nil {
+ t.Fatal(err)
+ }
+ df, ok := f.(*DataFrame)
+ if !ok {
+ t.Fatalf("got %T; want *DataFrame", f)
+ }
+ if !bytes.Equal(df.Data(), data) {
+ t.Errorf("got %q; want %q", df.Data(), data)
+ }
+ if f.Header().Flags&1 == 0 {
+ t.Errorf("didn't see END_STREAM flag")
+ }
+}
+
+func TestWriteHeaders(t *testing.T) {
+ tests := []struct {
+ name string
+ p HeadersFrameParam
+ wantEnc string
+ wantFrame *HeadersFrame
+ }{
+ {
+ "basic",
+ HeadersFrameParam{
+ StreamID: 42,
+ BlockFragment: []byte("abc"),
+ Priority: PriorityParam{},
+ },
+ "\x00\x00\x03\x01\x00\x00\x00\x00*abc",
+ &HeadersFrame{
+ FrameHeader: FrameHeader{
+ valid: true,
+ StreamID: 42,
+ Type: FrameHeaders,
+ Length: uint32(len("abc")),
+ },
+ Priority: PriorityParam{},
+ headerFragBuf: []byte("abc"),
+ },
+ },
+ {
+ "basic + end flags",
+ HeadersFrameParam{
+ StreamID: 42,
+ BlockFragment: []byte("abc"),
+ EndStream: true,
+ EndHeaders: true,
+ Priority: PriorityParam{},
+ },
+ "\x00\x00\x03\x01\x05\x00\x00\x00*abc",
+ &HeadersFrame{
+ FrameHeader: FrameHeader{
+ valid: true,
+ StreamID: 42,
+ Type: FrameHeaders,
+ Flags: FlagHeadersEndStream | FlagHeadersEndHeaders,
+ Length: uint32(len("abc")),
+ },
+ Priority: PriorityParam{},
+ headerFragBuf: []byte("abc"),
+ },
+ },
+ {
+ "with padding",
+ HeadersFrameParam{
+ StreamID: 42,
+ BlockFragment: []byte("abc"),
+ EndStream: true,
+ EndHeaders: true,
+ PadLength: 5,
+ Priority: PriorityParam{},
+ },
+ "\x00\x00\t\x01\r\x00\x00\x00*\x05abc\x00\x00\x00\x00\x00",
+ &HeadersFrame{
+ FrameHeader: FrameHeader{
+ valid: true,
+ StreamID: 42,
+ Type: FrameHeaders,
+ Flags: FlagHeadersEndStream | FlagHeadersEndHeaders | FlagHeadersPadded,
+ Length: uint32(1 + len("abc") + 5), // pad length + contents + padding
+ },
+ Priority: PriorityParam{},
+ headerFragBuf: []byte("abc"),
+ },
+ },
+ {
+ "with priority",
+ HeadersFrameParam{
+ StreamID: 42,
+ BlockFragment: []byte("abc"),
+ EndStream: true,
+ EndHeaders: true,
+ PadLength: 2,
+ Priority: PriorityParam{
+ StreamDep: 15,
+ Exclusive: true,
+ Weight: 127,
+ },
+ },
+ "\x00\x00\v\x01-\x00\x00\x00*\x02\x80\x00\x00\x0f\u007fabc\x00\x00",
+ &HeadersFrame{
+ FrameHeader: FrameHeader{
+ valid: true,
+ StreamID: 42,
+ Type: FrameHeaders,
+ Flags: FlagHeadersEndStream | FlagHeadersEndHeaders | FlagHeadersPadded | FlagHeadersPriority,
+ Length: uint32(1 + 5 + len("abc") + 2), // pad length + priority + contents + padding
+ },
+ Priority: PriorityParam{
+ StreamDep: 15,
+ Exclusive: true,
+ Weight: 127,
+ },
+ headerFragBuf: []byte("abc"),
+ },
+ },
+ }
+ for _, tt := range tests {
+ fr, buf := testFramer()
+ if err := fr.WriteHeaders(tt.p); err != nil {
+ t.Errorf("test %q: %v", tt.name, err)
+ continue
+ }
+ if buf.String() != tt.wantEnc {
+ t.Errorf("test %q: encoded %q; want %q", tt.name, buf.Bytes(), tt.wantEnc)
+ }
+ f, err := fr.ReadFrame()
+ if err != nil {
+ t.Errorf("test %q: failed to read the frame back: %v", tt.name, err)
+ continue
+ }
+ if !reflect.DeepEqual(f, tt.wantFrame) {
+ t.Errorf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tt.name, f, tt.wantFrame)
+ }
+ }
+}
+
+func TestWriteContinuation(t *testing.T) {
+ const streamID = 42
+ tests := []struct {
+ name string
+ end bool
+ frag []byte
+
+ wantFrame *ContinuationFrame
+ }{
+ {
+ "not end",
+ false,
+ []byte("abc"),
+ &ContinuationFrame{
+ FrameHeader: FrameHeader{
+ valid: true,
+ StreamID: streamID,
+ Type: FrameContinuation,
+ Length: uint32(len("abc")),
+ },
+ headerFragBuf: []byte("abc"),
+ },
+ },
+ {
+ "end",
+ true,
+ []byte("def"),
+ &ContinuationFrame{
+ FrameHeader: FrameHeader{
+ valid: true,
+ StreamID: streamID,
+ Type: FrameContinuation,
+ Flags: FlagContinuationEndHeaders,
+ Length: uint32(len("def")),
+ },
+ headerFragBuf: []byte("def"),
+ },
+ },
+ }
+ for _, tt := range tests {
+ fr, _ := testFramer()
+ if err := fr.WriteContinuation(streamID, tt.end, tt.frag); err != nil {
+ t.Errorf("test %q: %v", tt.name, err)
+ continue
+ }
+ f, err := fr.ReadFrame()
+ if err != nil {
+ t.Errorf("test %q: failed to read the frame back: %v", tt.name, err)
+ continue
+ }
+ if !reflect.DeepEqual(f, tt.wantFrame) {
+ t.Errorf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tt.name, f, tt.wantFrame)
+ }
+ }
+}
+
+func TestWritePriority(t *testing.T) {
+ const streamID = 42
+ tests := []struct {
+ name string
+ priority PriorityParam
+ wantFrame *PriorityFrame
+ }{
+ {
+ "not exclusive",
+ PriorityParam{
+ StreamDep: 2,
+ Exclusive: false,
+ Weight: 127,
+ },
+ &PriorityFrame{
+ FrameHeader{
+ valid: true,
+ StreamID: streamID,
+ Type: FramePriority,
+ Length: 5,
+ },
+ PriorityParam{
+ StreamDep: 2,
+ Exclusive: false,
+ Weight: 127,
+ },
+ },
+ },
+
+ {
+ "exclusive",
+ PriorityParam{
+ StreamDep: 3,
+ Exclusive: true,
+ Weight: 77,
+ },
+ &PriorityFrame{
+ FrameHeader{
+ valid: true,
+ StreamID: streamID,
+ Type: FramePriority,
+ Length: 5,
+ },
+ PriorityParam{
+ StreamDep: 3,
+ Exclusive: true,
+ Weight: 77,
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ fr, _ := testFramer()
+ if err := fr.WritePriority(streamID, tt.priority); err != nil {
+ t.Errorf("test %q: %v", tt.name, err)
+ continue
+ }
+ f, err := fr.ReadFrame()
+ if err != nil {
+ t.Errorf("test %q: failed to read the frame back: %v", tt.name, err)
+ continue
+ }
+ if !reflect.DeepEqual(f, tt.wantFrame) {
+ t.Errorf("test %q: mismatch.\n got: %#v\nwant: %#v\n", tt.name, f, tt.wantFrame)
+ }
+ }
+}
+
+func TestWriteSettings(t *testing.T) {
+ fr, buf := testFramer()
+ settings := []Setting{{1, 2}, {3, 4}}
+ fr.WriteSettings(settings...)
+ const wantEnc = "\x00\x00\f\x04\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\x02\x00\x03\x00\x00\x00\x04"
+ if buf.String() != wantEnc {
+ t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+ }
+ f, err := fr.ReadFrame()
+ if err != nil {
+ t.Fatal(err)
+ }
+ sf, ok := f.(*SettingsFrame)
+ if !ok {
+ t.Fatalf("Got a %T; want a SettingsFrame", f)
+ }
+ var got []Setting
+ sf.ForeachSetting(func(s Setting) error {
+ got = append(got, s)
+ valBack, ok := sf.Value(s.ID)
+ if !ok || valBack != s.Val {
+ t.Errorf("Value(%d) = %v, %v; want %v, true", s.ID, valBack, ok, s.Val)
+ }
+ return nil
+ })
+ if !reflect.DeepEqual(settings, got) {
+ t.Errorf("Read settings %+v != written settings %+v", got, settings)
+ }
+}
+
+func TestWriteSettingsAck(t *testing.T) {
+ fr, buf := testFramer()
+ fr.WriteSettingsAck()
+ const wantEnc = "\x00\x00\x00\x04\x01\x00\x00\x00\x00"
+ if buf.String() != wantEnc {
+ t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+ }
+}
+
+func TestWriteWindowUpdate(t *testing.T) {
+ fr, buf := testFramer()
+ const streamID = 1<<24 + 2<<16 + 3<<8 + 4
+ const incr = 7<<24 + 6<<16 + 5<<8 + 4
+ if err := fr.WriteWindowUpdate(streamID, incr); err != nil {
+ t.Fatal(err)
+ }
+ const wantEnc = "\x00\x00\x04\x08\x00\x01\x02\x03\x04\x07\x06\x05\x04"
+ if buf.String() != wantEnc {
+ t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+ }
+ f, err := fr.ReadFrame()
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := &WindowUpdateFrame{
+ FrameHeader: FrameHeader{
+ valid: true,
+ Type: 0x8,
+ Flags: 0x0,
+ Length: 0x4,
+ StreamID: 0x1020304,
+ },
+ Increment: 0x7060504,
+ }
+ if !reflect.DeepEqual(f, want) {
+ t.Errorf("parsed back %#v; want %#v", f, want)
+ }
+}
+
+func TestWritePing(t *testing.T) { testWritePing(t, false) }
+func TestWritePingAck(t *testing.T) { testWritePing(t, true) }
+
+func testWritePing(t *testing.T, ack bool) {
+ fr, buf := testFramer()
+ if err := fr.WritePing(ack, [8]byte{1, 2, 3, 4, 5, 6, 7, 8}); err != nil {
+ t.Fatal(err)
+ }
+ var wantFlags Flags
+ if ack {
+ wantFlags = FlagPingAck
+ }
+ var wantEnc = "\x00\x00\x08\x06" + string(wantFlags) + "\x00\x00\x00\x00" + "\x01\x02\x03\x04\x05\x06\x07\x08"
+ if buf.String() != wantEnc {
+ t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+ }
+
+ f, err := fr.ReadFrame()
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := &PingFrame{
+ FrameHeader: FrameHeader{
+ valid: true,
+ Type: 0x6,
+ Flags: wantFlags,
+ Length: 0x8,
+ StreamID: 0,
+ },
+ Data: [8]byte{1, 2, 3, 4, 5, 6, 7, 8},
+ }
+ if !reflect.DeepEqual(f, want) {
+ t.Errorf("parsed back %#v; want %#v", f, want)
+ }
+}
+
+func TestReadFrameHeader(t *testing.T) {
+ tests := []struct {
+ in string
+ want FrameHeader
+ }{
+ {in: "\x00\x00\x00" + "\x00" + "\x00" + "\x00\x00\x00\x00", want: FrameHeader{}},
+ {in: "\x01\x02\x03" + "\x04" + "\x05" + "\x06\x07\x08\x09", want: FrameHeader{
+ Length: 66051, Type: 4, Flags: 5, StreamID: 101124105,
+ }},
+ // Ignore high bit:
+ {in: "\xff\xff\xff" + "\xff" + "\xff" + "\xff\xff\xff\xff", want: FrameHeader{
+ Length: 16777215, Type: 255, Flags: 255, StreamID: 2147483647}},
+ {in: "\xff\xff\xff" + "\xff" + "\xff" + "\x7f\xff\xff\xff", want: FrameHeader{
+ Length: 16777215, Type: 255, Flags: 255, StreamID: 2147483647}},
+ }
+ for i, tt := range tests {
+ got, err := readFrameHeader(make([]byte, 9), strings.NewReader(tt.in))
+ if err != nil {
+ t.Errorf("%d. readFrameHeader(%q) = %v", i, tt.in, err)
+ continue
+ }
+ tt.want.valid = true
+ if got != tt.want {
+ t.Errorf("%d. readFrameHeader(%q) = %+v; want %+v", i, tt.in, got, tt.want)
+ }
+ }
+}
+
+func TestReadWriteFrameHeader(t *testing.T) {
+ tests := []struct {
+ len uint32
+ typ FrameType
+ flags Flags
+ streamID uint32
+ }{
+ {len: 0, typ: 255, flags: 1, streamID: 0},
+ {len: 0, typ: 255, flags: 1, streamID: 1},
+ {len: 0, typ: 255, flags: 1, streamID: 255},
+ {len: 0, typ: 255, flags: 1, streamID: 256},
+ {len: 0, typ: 255, flags: 1, streamID: 65535},
+ {len: 0, typ: 255, flags: 1, streamID: 65536},
+
+ {len: 0, typ: 1, flags: 255, streamID: 1},
+ {len: 255, typ: 1, flags: 255, streamID: 1},
+ {len: 256, typ: 1, flags: 255, streamID: 1},
+ {len: 65535, typ: 1, flags: 255, streamID: 1},
+ {len: 65536, typ: 1, flags: 255, streamID: 1},
+ {len: 16777215, typ: 1, flags: 255, streamID: 1},
+ }
+ for _, tt := range tests {
+ fr, buf := testFramer()
+ fr.startWrite(tt.typ, tt.flags, tt.streamID)
+ fr.writeBytes(make([]byte, tt.len))
+ fr.endWrite()
+ fh, err := ReadFrameHeader(buf)
+ if err != nil {
+ t.Errorf("ReadFrameHeader(%+v) = %v", tt, err)
+ continue
+ }
+ if fh.Type != tt.typ || fh.Flags != tt.flags || fh.Length != tt.len || fh.StreamID != tt.streamID {
+ t.Errorf("ReadFrameHeader(%+v) = %+v; mismatch", tt, fh)
+ }
+ }
+
+}
+
+func TestWriteTooLargeFrame(t *testing.T) {
+ fr, _ := testFramer()
+ fr.startWrite(0, 1, 1)
+ fr.writeBytes(make([]byte, 1<<24))
+ err := fr.endWrite()
+ if err != ErrFrameTooLarge {
+ t.Errorf("endWrite = %v; want errFrameTooLarge", err)
+ }
+}
+
+func TestWriteGoAway(t *testing.T) {
+ const debug = "foo"
+ fr, buf := testFramer()
+ if err := fr.WriteGoAway(0x01020304, 0x05060708, []byte(debug)); err != nil {
+ t.Fatal(err)
+ }
+ const wantEnc = "\x00\x00\v\a\x00\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08" + debug
+ if buf.String() != wantEnc {
+ t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+ }
+ f, err := fr.ReadFrame()
+ if err != nil {
+ t.Fatal(err)
+ }
+ want := &GoAwayFrame{
+ FrameHeader: FrameHeader{
+ valid: true,
+ Type: 0x7,
+ Flags: 0,
+ Length: uint32(4 + 4 + len(debug)),
+ StreamID: 0,
+ },
+ LastStreamID: 0x01020304,
+ ErrCode: 0x05060708,
+ debugData: []byte(debug),
+ }
+ if !reflect.DeepEqual(f, want) {
+ t.Fatalf("parsed back:\n%#v\nwant:\n%#v", f, want)
+ }
+ if got := string(f.(*GoAwayFrame).DebugData()); got != debug {
+ t.Errorf("debug data = %q; want %q", got, debug)
+ }
+}
+
+func TestWritePushPromise(t *testing.T) {
+ pp := PushPromiseParam{
+ StreamID: 42,
+ PromiseID: 42,
+ BlockFragment: []byte("abc"),
+ }
+ fr, buf := testFramer()
+ if err := fr.WritePushPromise(pp); err != nil {
+ t.Fatal(err)
+ }
+ const wantEnc = "\x00\x00\x07\x05\x00\x00\x00\x00*\x00\x00\x00*abc"
+ if buf.String() != wantEnc {
+ t.Errorf("encoded as %q; want %q", buf.Bytes(), wantEnc)
+ }
+ f, err := fr.ReadFrame()
+ if err != nil {
+ t.Fatal(err)
+ }
+ _, ok := f.(*PushPromiseFrame)
+ if !ok {
+ t.Fatalf("got %T; want *PushPromiseFrame", f)
+ }
+ want := &PushPromiseFrame{
+ FrameHeader: FrameHeader{
+ valid: true,
+ Type: 0x5,
+ Flags: 0x0,
+ Length: 0x7,
+ StreamID: 42,
+ },
+ PromiseID: 42,
+ headerFragBuf: []byte("abc"),
+ }
+ if !reflect.DeepEqual(f, want) {
+ t.Fatalf("parsed back:\n%#v\nwant:\n%#v", f, want)
+ }
+}
diff --git a/http2/gotrack.go b/http2/gotrack.go
new file mode 100644
index 0000000..7dc2ef9
--- /dev/null
+++ b/http2/gotrack.go
@@ -0,0 +1,173 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+// Defensive debug-only utility to track that functions run on the
+// goroutine that they're supposed to.
+
+package http2
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "os"
+ "runtime"
+ "strconv"
+ "sync"
+)
+
+var DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1"
+
+type goroutineLock uint64
+
+func newGoroutineLock() goroutineLock {
+ if !DebugGoroutines {
+ return 0
+ }
+ return goroutineLock(curGoroutineID())
+}
+
+func (g goroutineLock) check() {
+ if !DebugGoroutines {
+ return
+ }
+ if curGoroutineID() != uint64(g) {
+ panic("running on the wrong goroutine")
+ }
+}
+
+func (g goroutineLock) checkNotOn() {
+ if !DebugGoroutines {
+ return
+ }
+ if curGoroutineID() == uint64(g) {
+ panic("running on the wrong goroutine")
+ }
+}
+
+var goroutineSpace = []byte("goroutine ")
+
+func curGoroutineID() uint64 {
+ bp := littleBuf.Get().(*[]byte)
+ defer littleBuf.Put(bp)
+ b := *bp
+ b = b[:runtime.Stack(b, false)]
+ // Parse the 4707 out of "goroutine 4707 ["
+ b = bytes.TrimPrefix(b, goroutineSpace)
+ i := bytes.IndexByte(b, ' ')
+ if i < 0 {
+ panic(fmt.Sprintf("No space found in %q", b))
+ }
+ b = b[:i]
+ n, err := parseUintBytes(b, 10, 64)
+ if err != nil {
+ panic(fmt.Sprintf("Failed to parse goroutine ID out of %q: %v", b, err))
+ }
+ return n
+}
+
+var littleBuf = sync.Pool{
+ New: func() interface{} {
+ buf := make([]byte, 64)
+ return &buf
+ },
+}
+
+// parseUintBytes is like strconv.ParseUint, but using a []byte.
+func parseUintBytes(s []byte, base int, bitSize int) (n uint64, err error) {
+ var cutoff, maxVal uint64
+
+ if bitSize == 0 {
+ bitSize = int(strconv.IntSize)
+ }
+
+ s0 := s
+ switch {
+ case len(s) < 1:
+ err = strconv.ErrSyntax
+ goto Error
+
+ case 2 <= base && base <= 36:
+ // valid base; nothing to do
+
+ case base == 0:
+ // Look for octal, hex prefix.
+ switch {
+ case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'):
+ base = 16
+ s = s[2:]
+ if len(s) < 1 {
+ err = strconv.ErrSyntax
+ goto Error
+ }
+ case s[0] == '0':
+ base = 8
+ default:
+ base = 10
+ }
+
+ default:
+ err = errors.New("invalid base " + strconv.Itoa(base))
+ goto Error
+ }
+
+ n = 0
+ cutoff = cutoff64(base)
+ maxVal = 1<<uint(bitSize) - 1
+
+ for i := 0; i < len(s); i++ {
+ var v byte
+ d := s[i]
+ switch {
+ case '0' <= d && d <= '9':
+ v = d - '0'
+ case 'a' <= d && d <= 'z':
+ v = d - 'a' + 10
+ case 'A' <= d && d <= 'Z':
+ v = d - 'A' + 10
+ default:
+ n = 0
+ err = strconv.ErrSyntax
+ goto Error
+ }
+ if int(v) >= base {
+ n = 0
+ err = strconv.ErrSyntax
+ goto Error
+ }
+
+ if n >= cutoff {
+ // n*base overflows
+ n = 1<<64 - 1
+ err = strconv.ErrRange
+ goto Error
+ }
+ n *= uint64(base)
+
+ n1 := n + uint64(v)
+ if n1 < n || n1 > maxVal {
+ // n+v overflows
+ n = 1<<64 - 1
+ err = strconv.ErrRange
+ goto Error
+ }
+ n = n1
+ }
+
+ return n, nil
+
+Error:
+ return n, &strconv.NumError{Func: "ParseUint", Num: string(s0), Err: err}
+}
+
+// Return the first number n such that n*base >= 1<<64.
+func cutoff64(base int) uint64 {
+ if base < 2 {
+ return 0
+ }
+ return (1<<64-1)/uint64(base) + 1
+}
diff --git a/http2/gotrack_test.go b/http2/gotrack_test.go
new file mode 100644
index 0000000..3ff4957
--- /dev/null
+++ b/http2/gotrack_test.go
@@ -0,0 +1,33 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+)
+
+func TestGoroutineLock(t *testing.T) {
+ DebugGoroutines = true
+ g := newGoroutineLock()
+ g.check()
+
+ sawPanic := make(chan interface{})
+ go func() {
+ defer func() { sawPanic <- recover() }()
+ g.check() // should panic
+ }()
+ e := <-sawPanic
+ if e == nil {
+ t.Fatal("did not see panic from check in other goroutine")
+ }
+ if !strings.Contains(fmt.Sprint(e), "wrong goroutine") {
+ t.Errorf("expected on see panic about running on the wrong goroutine; got %v", e)
+ }
+}
diff --git a/http2/h2demo/.gitignore b/http2/h2demo/.gitignore
new file mode 100644
index 0000000..0de86dd
--- /dev/null
+++ b/http2/h2demo/.gitignore
@@ -0,0 +1,5 @@
+h2demo
+h2demo.linux
+client-id.dat
+client-secret.dat
+token.dat
diff --git a/http2/h2demo/Makefile b/http2/h2demo/Makefile
new file mode 100644
index 0000000..3a77cf0
--- /dev/null
+++ b/http2/h2demo/Makefile
@@ -0,0 +1,5 @@
+h2demo.linux: h2demo.go
+ GOOS=linux go build --tags=h2demo -o h2demo.linux .
+
+upload: h2demo.linux
+ cat h2demo.linux | go run launch.go --write_object=http2-demo-server-tls/h2demo --write_object_is_public
diff --git a/http2/h2demo/README b/http2/h2demo/README
new file mode 100644
index 0000000..212a96f
--- /dev/null
+++ b/http2/h2demo/README
@@ -0,0 +1,16 @@
+
+Client:
+ -- Firefox nightly with about:config network.http.spdy.enabled.http2draft set true
+ -- Chrome: go to chrome://flags/#enable-spdy4, save and restart (button at bottom)
+
+Make CA:
+$ openssl genrsa -out rootCA.key 2048
+$ openssl req -x509 -new -nodes -key rootCA.key -days 1024 -out rootCA.pem
+... install that to Firefox
+
+Make cert:
+$ openssl genrsa -out server.key 2048
+$ openssl req -new -key server.key -out server.csr
+$ openssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 500
+
+
diff --git a/http2/h2demo/h2demo.go b/http2/h2demo/h2demo.go
new file mode 100644
index 0000000..b5ee4b2
--- /dev/null
+++ b/http2/h2demo/h2demo.go
@@ -0,0 +1,426 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+// +build h2demo
+
+package main
+
+import (
+ "bytes"
+ "crypto/tls"
+ "flag"
+ "fmt"
+ "hash/crc32"
+ "image"
+ "image/jpeg"
+ "io"
+ "io/ioutil"
+ "log"
+ "net"
+ "net/http"
+ "os/exec"
+ "path"
+ "regexp"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "camlistore.org/pkg/googlestorage"
+ "camlistore.org/pkg/singleflight"
+ "golang.org/x/net/http2"
+)
+
+var (
+ openFirefox = flag.Bool("openff", false, "Open Firefox")
+ addr = flag.String("addr", "localhost:4430", "TLS address to listen on")
+ httpAddr = flag.String("httpaddr", "", "If non-empty, address to listen for regular HTTP on")
+ prod = flag.Bool("prod", false, "Whether to configure itself to be the production http2.golang.org server.")
+)
+
+func homeOldHTTP(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, `<html>
+<body>
+<h1>Go + HTTP/2</h1>
+<p>Welcome to <a href="https://golang.org/">the Go language</a>'s <a href="https://http2.github.io/">HTTP/2</a> demo & interop server.</p>
+<p>Unfortunately, you're <b>not</b> using HTTP/2 right now. To do so:</p>
+<ul>
+ <li>Use Firefox Nightly or go to <b>about:config</b> and enable "network.http.spdy.enabled.http2draft"</li>
+ <li>Use Google Chrome Canary and/or go to <b>chrome://flags/#enable-spdy4</b> to <i>Enable SPDY/4</i> (Chrome's name for HTTP/2)</li>
+</ul>
+<p>See code & instructions for connecting at <a href="https://github.com/golang/net/tree/master/http2">https://github.com/golang/net/tree/master/http2</a>.</p>
+
+</body></html>`)
+}
+
+func home(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path != "/" {
+ http.NotFound(w, r)
+ return
+ }
+ io.WriteString(w, `<html>
+<body>
+<h1>Go + HTTP/2</h1>
+
+<p>Welcome to <a href="https://golang.org/">the Go language</a>'s <a
+href="https://http2.github.io/">HTTP/2</a> demo & interop server.</p>
+
+<p>Congratulations, <b>you're using HTTP/2 right now</b>.</p>
+
+<p>This server exists for others in the HTTP/2 community to test their HTTP/2 client implementations and point out flaws in our server.</p>
+
+<p> The code is currently at <a
+href="https://golang.org/x/net/http2">github.com/bradfitz/http2</a>
+but will move to the Go standard library at some point in the future
+(enabled by default, without users needing to change their code).</p>
+
+<p>Contact info: <i>bradfitz@golang.org</i>, or <a
+href="https://golang.org/x/net/http2/issues">file a bug</a>.</p>
+
+<h2>Handlers for testing</h2>
+<ul>
+ <li>GET <a href="/reqinfo">/reqinfo</a> to dump the request + headers received</li>
+ <li>GET <a href="/clockstream">/clockstream</a> streams the current time every second</li>
+ <li>GET <a href="/gophertiles">/gophertiles</a> to see a page with a bunch of images</li>
+ <li>GET <a href="/file/gopher.png">/file/gopher.png</a> for a small file (does If-Modified-Since, Content-Range, etc)</li>
+ <li>GET <a href="/file/go.src.tar.gz">/file/go.src.tar.gz</a> for a larger file (~10 MB)</li>
+ <li>GET <a href="/redirect">/redirect</a> to redirect back to / (this page)</li>
+ <li>GET <a href="/goroutines">/goroutines</a> to see all active goroutines in this server</li>
+ <li>PUT something to <a href="/crc32">/crc32</a> to get a count of number of bytes and its CRC-32</li>
+</ul>
+
+</body></html>`)
+}
+
+func reqInfoHandler(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain")
+ fmt.Fprintf(w, "Method: %s\n", r.Method)
+ fmt.Fprintf(w, "Protocol: %s\n", r.Proto)
+ fmt.Fprintf(w, "Host: %s\n", r.Host)
+ fmt.Fprintf(w, "RemoteAddr: %s\n", r.RemoteAddr)
+ fmt.Fprintf(w, "RequestURI: %q\n", r.RequestURI)
+ fmt.Fprintf(w, "URL: %#v\n", r.URL)
+ fmt.Fprintf(w, "Body.ContentLength: %d (-1 means unknown)\n", r.ContentLength)
+ fmt.Fprintf(w, "Close: %v (relevant for HTTP/1 only)\n", r.Close)
+ fmt.Fprintf(w, "TLS: %#v\n", r.TLS)
+ fmt.Fprintf(w, "\nHeaders:\n")
+ r.Header.Write(w)
+}
+
+func crcHandler(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "PUT" {
+ http.Error(w, "PUT required.", 400)
+ return
+ }
+ crc := crc32.NewIEEE()
+ n, err := io.Copy(crc, r.Body)
+ if err == nil {
+ w.Header().Set("Content-Type", "text/plain")
+ fmt.Fprintf(w, "bytes=%d, CRC32=%x", n, crc.Sum(nil))
+ }
+}
+
+var (
+ fsGrp singleflight.Group
+ fsMu sync.Mutex // guards fsCache
+ fsCache = map[string]http.Handler{}
+)
+
+// fileServer returns a file-serving handler that proxies URL.
+// It lazily fetches URL on the first access and caches its contents forever.
+func fileServer(url string) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ hi, err := fsGrp.Do(url, func() (interface{}, error) {
+ fsMu.Lock()
+ if h, ok := fsCache[url]; ok {
+ fsMu.Unlock()
+ return h, nil
+ }
+ fsMu.Unlock()
+
+ res, err := http.Get(url)
+ if err != nil {
+ return nil, err
+ }
+ defer res.Body.Close()
+ slurp, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return nil, err
+ }
+
+ modTime := time.Now()
+ var h http.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ http.ServeContent(w, r, path.Base(url), modTime, bytes.NewReader(slurp))
+ })
+ fsMu.Lock()
+ fsCache[url] = h
+ fsMu.Unlock()
+ return h, nil
+ })
+ if err != nil {
+ http.Error(w, err.Error(), 500)
+ return
+ }
+ hi.(http.Handler).ServeHTTP(w, r)
+ })
+}
+
+func clockStreamHandler(w http.ResponseWriter, r *http.Request) {
+ clientGone := w.(http.CloseNotifier).CloseNotify()
+ w.Header().Set("Content-Type", "text/plain")
+ ticker := time.NewTicker(1 * time.Second)
+ defer ticker.Stop()
+ fmt.Fprintf(w, "# ~1KB of junk to force browsers to start rendering immediately: \n")
+ io.WriteString(w, strings.Repeat("# xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n", 13))
+
+ for {
+ fmt.Fprintf(w, "%v\n", time.Now())
+ w.(http.Flusher).Flush()
+ select {
+ case <-ticker.C:
+ case <-clientGone:
+ log.Printf("Client %v disconnected from the clock", r.RemoteAddr)
+ return
+ }
+ }
+}
+
+func registerHandlers() {
+ tiles := newGopherTilesHandler()
+
+ mux2 := http.NewServeMux()
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ if r.TLS == nil {
+ if r.URL.Path == "/gophertiles" {
+ tiles.ServeHTTP(w, r)
+ return
+ }
+ http.Redirect(w, r, "https://http2.golang.org/", http.StatusFound)
+ return
+ }
+ if r.ProtoMajor == 1 {
+ if r.URL.Path == "/reqinfo" {
+ reqInfoHandler(w, r)
+ return
+ }
+ homeOldHTTP(w, r)
+ return
+ }
+ mux2.ServeHTTP(w, r)
+ })
+ mux2.HandleFunc("/", home)
+ mux2.Handle("/file/gopher.png", fileServer("https://golang.org/doc/gopher/frontpage.png"))
+ mux2.Handle("/file/go.src.tar.gz", fileServer("https://storage.googleapis.com/golang/go1.4.1.src.tar.gz"))
+ mux2.HandleFunc("/reqinfo", reqInfoHandler)
+ mux2.HandleFunc("/crc32", crcHandler)
+ mux2.HandleFunc("/clockstream", clockStreamHandler)
+ mux2.Handle("/gophertiles", tiles)
+ mux2.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
+ http.Redirect(w, r, "/", http.StatusFound)
+ })
+ stripHomedir := regexp.MustCompile(`/(Users|home)/\w+`)
+ mux2.HandleFunc("/goroutines", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", "text/plain; charset=utf-8")
+ buf := make([]byte, 2<<20)
+ w.Write(stripHomedir.ReplaceAll(buf[:runtime.Stack(buf, true)], nil))
+ })
+}
+
+func newGopherTilesHandler() http.Handler {
+ const gopherURL = "https://blog.golang.org/go-programming-language-turns-two_gophers.jpg"
+ res, err := http.Get(gopherURL)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if res.StatusCode != 200 {
+ log.Fatalf("Error fetching %s: %v", gopherURL, res.Status)
+ }
+ slurp, err := ioutil.ReadAll(res.Body)
+ res.Body.Close()
+ if err != nil {
+ log.Fatal(err)
+ }
+ im, err := jpeg.Decode(bytes.NewReader(slurp))
+ if err != nil {
+ if len(slurp) > 1024 {
+ slurp = slurp[:1024]
+ }
+ log.Fatalf("Failed to decode gopher image: %v (got %q)", err, slurp)
+ }
+
+ type subImager interface {
+ SubImage(image.Rectangle) image.Image
+ }
+ const tileSize = 32
+ xt := im.Bounds().Max.X / tileSize
+ yt := im.Bounds().Max.Y / tileSize
+ var tile [][][]byte // y -> x -> jpeg bytes
+ for yi := 0; yi < yt; yi++ {
+ var row [][]byte
+ for xi := 0; xi < xt; xi++ {
+ si := im.(subImager).SubImage(image.Rectangle{
+ Min: image.Point{xi * tileSize, yi * tileSize},
+ Max: image.Point{(xi + 1) * tileSize, (yi + 1) * tileSize},
+ })
+ buf := new(bytes.Buffer)
+ if err := jpeg.Encode(buf, si, &jpeg.Options{Quality: 90}); err != nil {
+ log.Fatal(err)
+ }
+ row = append(row, buf.Bytes())
+ }
+ tile = append(tile, row)
+ }
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ms, _ := strconv.Atoi(r.FormValue("latency"))
+ const nanosPerMilli = 1e6
+ if r.FormValue("x") != "" {
+ x, _ := strconv.Atoi(r.FormValue("x"))
+ y, _ := strconv.Atoi(r.FormValue("y"))
+ if ms <= 1000 {
+ time.Sleep(time.Duration(ms) * nanosPerMilli)
+ }
+ if x >= 0 && x < xt && y >= 0 && y < yt {
+ http.ServeContent(w, r, "", time.Time{}, bytes.NewReader(tile[y][x]))
+ return
+ }
+ }
+ io.WriteString(w, "<html><body>")
+ fmt.Fprintf(w, "A grid of %d tiled images is below. Compare:<p>", xt*yt)
+ for _, ms := range []int{0, 30, 200, 1000} {
+ d := time.Duration(ms) * nanosPerMilli
+ fmt.Fprintf(w, "[<a href='https://%s/gophertiles?latency=%d'>HTTP/2, %v latency</a>] [<a href='http://%s/gophertiles?latency=%d'>HTTP/1, %v latency</a>]<br>\n",
+ httpsHost(), ms, d,
+ httpHost(), ms, d,
+ )
+ }
+ io.WriteString(w, "<p>\n")
+ cacheBust := time.Now().UnixNano()
+ for y := 0; y < yt; y++ {
+ for x := 0; x < xt; x++ {
+ fmt.Fprintf(w, "<img width=%d height=%d src='/gophertiles?x=%d&y=%d&cachebust=%d&latency=%d'>",
+ tileSize, tileSize, x, y, cacheBust, ms)
+ }
+ io.WriteString(w, "<br/>\n")
+ }
+ io.WriteString(w, "<hr><a href='/'><< Back to Go HTTP/2 demo server</a></body></html>")
+ })
+}
+
+func httpsHost() string {
+ if *prod {
+ return "http2.golang.org"
+ }
+ if v := *addr; strings.HasPrefix(v, ":") {
+ return "localhost" + v
+ } else {
+ return v
+ }
+}
+
+func httpHost() string {
+ if *prod {
+ return "http2.golang.org"
+ }
+ if v := *httpAddr; strings.HasPrefix(v, ":") {
+ return "localhost" + v
+ } else {
+ return v
+ }
+}
+
+func serveProdTLS() error {
+ c, err := googlestorage.NewServiceClient()
+ if err != nil {
+ return err
+ }
+ slurp := func(key string) ([]byte, error) {
+ const bucket = "http2-demo-server-tls"
+ rc, _, err := c.GetObject(&googlestorage.Object{
+ Bucket: bucket,
+ Key: key,
+ })
+ if err != nil {
+ return nil, fmt.Errorf("Error fetching GCS object %q in bucket %q: %v", key, bucket, err)
+ }
+ defer rc.Close()
+ return ioutil.ReadAll(rc)
+ }
+ certPem, err := slurp("http2.golang.org.chained.pem")
+ if err != nil {
+ return err
+ }
+ keyPem, err := slurp("http2.golang.org.key")
+ if err != nil {
+ return err
+ }
+ cert, err := tls.X509KeyPair(certPem, keyPem)
+ if err != nil {
+ return err
+ }
+ srv := &http.Server{
+ TLSConfig: &tls.Config{
+ Certificates: []tls.Certificate{cert},
+ },
+ }
+ http2.ConfigureServer(srv, &http2.Server{})
+ ln, err := net.Listen("tcp", ":443")
+ if err != nil {
+ return err
+ }
+ return srv.Serve(tls.NewListener(tcpKeepAliveListener{ln.(*net.TCPListener)}, srv.TLSConfig))
+}
+
+type tcpKeepAliveListener struct {
+ *net.TCPListener
+}
+
+func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
+ tc, err := ln.AcceptTCP()
+ if err != nil {
+ return
+ }
+ tc.SetKeepAlive(true)
+ tc.SetKeepAlivePeriod(3 * time.Minute)
+ return tc, nil
+}
+
+func serveProd() error {
+ errc := make(chan error, 2)
+ go func() { errc <- http.ListenAndServe(":80", nil) }()
+ go func() { errc <- serveProdTLS() }()
+ return <-errc
+}
+
+func main() {
+ var srv http.Server
+ flag.BoolVar(&http2.VerboseLogs, "verbose", false, "Verbose HTTP/2 debugging.")
+ flag.Parse()
+ srv.Addr = *addr
+
+ registerHandlers()
+
+ if *prod {
+ *httpAddr = "http2.golang.org"
+ log.Fatal(serveProd())
+ }
+
+ url := "https://" + *addr + "/"
+ log.Printf("Listening on " + url)
+ http2.ConfigureServer(&srv, &http2.Server{})
+
+ if *httpAddr != "" {
+ go func() { log.Fatal(http.ListenAndServe(*httpAddr, nil)) }()
+ }
+
+ go func() {
+ log.Fatal(srv.ListenAndServeTLS("server.crt", "server.key"))
+ }()
+ if *openFirefox && runtime.GOOS == "darwin" {
+ time.Sleep(250 * time.Millisecond)
+ exec.Command("open", "-b", "org.mozilla.nightly", "https://localhost:4430/").Run()
+ }
+ select {}
+}
diff --git a/http2/h2demo/launch.go b/http2/h2demo/launch.go
new file mode 100644
index 0000000..7466615
--- /dev/null
+++ b/http2/h2demo/launch.go
@@ -0,0 +1,302 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build ignore
+
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "os"
+ "strings"
+ "time"
+
+ "golang.org/x/oauth2"
+ "golang.org/x/oauth2/google"
+ compute "google.golang.org/api/compute/v1"
+)
+
+var (
+ proj = flag.String("project", "symbolic-datum-552", "name of Project")
+ zone = flag.String("zone", "us-central1-a", "GCE zone")
+ mach = flag.String("machinetype", "n1-standard-1", "Machine type")
+ instName = flag.String("instance_name", "http2-demo", "Name of VM instance.")
+ sshPub = flag.String("ssh_public_key", "", "ssh public key file to authorize. Can modify later in Google's web UI anyway.")
+ staticIP = flag.String("static_ip", "130.211.116.44", "Static IP to use. If empty, automatic.")
+
+ writeObject = flag.String("write_object", "", "If non-empty, a VM isn't created and the flag value is Google Cloud Storage bucket/object to write. The contents from stdin.")
+ publicObject = flag.Bool("write_object_is_public", false, "Whether the object created by --write_object should be public.")
+)
+
+func readFile(v string) string {
+ slurp, err := ioutil.ReadFile(v)
+ if err != nil {
+ log.Fatalf("Error reading %s: %v", v, err)
+ }
+ return strings.TrimSpace(string(slurp))
+}
+
+var config = &oauth2.Config{
+ // The client-id and secret should be for an "Installed Application" when using
+ // the CLI. Later we'll use a web application with a callback.
+ ClientID: readFile("client-id.dat"),
+ ClientSecret: readFile("client-secret.dat"),
+ Endpoint: google.Endpoint,
+ Scopes: []string{
+ compute.DevstorageFull_controlScope,
+ compute.ComputeScope,
+ "https://www.googleapis.com/auth/sqlservice",
+ "https://www.googleapis.com/auth/sqlservice.admin",
+ },
+ RedirectURL: "urn:ietf:wg:oauth:2.0:oob",
+}
+
+const baseConfig = `#cloud-config
+coreos:
+ units:
+ - name: h2demo.service
+ command: start
+ content: |
+ [Unit]
+ Description=HTTP2 Demo
+
+ [Service]
+ ExecStartPre=/bin/bash -c 'mkdir -p /opt/bin && curl -s -o /opt/bin/h2demo http://storage.googleapis.com/http2-demo-server-tls/h2demo && chmod +x /opt/bin/h2demo'
+ ExecStart=/opt/bin/h2demo --prod
+ RestartSec=5s
+ Restart=always
+ Type=simple
+
+ [Install]
+ WantedBy=multi-user.target
+`
+
+func main() {
+ flag.Parse()
+ if *proj == "" {
+ log.Fatalf("Missing --project flag")
+ }
+ prefix := "https://www.googleapis.com/compute/v1/projects/" + *proj
+ machType := prefix + "/zones/" + *zone + "/machineTypes/" + *mach
+
+ const tokenFileName = "token.dat"
+ tokenFile := tokenCacheFile(tokenFileName)
+ tokenSource := oauth2.ReuseTokenSource(nil, tokenFile)
+ token, err := tokenSource.Token()
+ if err != nil {
+ if *writeObject != "" {
+ log.Fatalf("Can't use --write_object without a valid token.dat file already cached.")
+ }
+ log.Printf("Error getting token from %s: %v", tokenFileName, err)
+ log.Printf("Get auth code from %v", config.AuthCodeURL("my-state"))
+ fmt.Print("\nEnter auth code: ")
+ sc := bufio.NewScanner(os.Stdin)
+ sc.Scan()
+ authCode := strings.TrimSpace(sc.Text())
+ token, err = config.Exchange(oauth2.NoContext, authCode)
+ if err != nil {
+ log.Fatalf("Error exchanging auth code for a token: %v", err)
+ }
+ if err := tokenFile.WriteToken(token); err != nil {
+ log.Fatalf("Error writing to %s: %v", tokenFileName, err)
+ }
+ tokenSource = oauth2.ReuseTokenSource(token, nil)
+ }
+
+ oauthClient := oauth2.NewClient(oauth2.NoContext, tokenSource)
+
+ if *writeObject != "" {
+ writeCloudStorageObject(oauthClient)
+ return
+ }
+
+ computeService, _ := compute.New(oauthClient)
+
+ natIP := *staticIP
+ if natIP == "" {
+ // Try to find it by name.
+ aggAddrList, err := computeService.Addresses.AggregatedList(*proj).Do()
+ if err != nil {
+ log.Fatal(err)
+ }
+ // http://godoc.org/code.google.com/p/google-api-go-client/compute/v1#AddressAggregatedList
+ IPLoop:
+ for _, asl := range aggAddrList.Items {
+ for _, addr := range asl.Addresses {
+ if addr.Name == *instName+"-ip" && addr.Status == "RESERVED" {
+ natIP = addr.Address
+ break IPLoop
+ }
+ }
+ }
+ }
+
+ cloudConfig := baseConfig
+ if *sshPub != "" {
+ key := strings.TrimSpace(readFile(*sshPub))
+ cloudConfig += fmt.Sprintf("\nssh_authorized_keys:\n - %s\n", key)
+ }
+ if os.Getenv("USER") == "bradfitz" {
+ cloudConfig += fmt.Sprintf("\nssh_authorized_keys:\n - %s\n", "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAIEAwks9dwWKlRC+73gRbvYtVg0vdCwDSuIlyt4z6xa/YU/jTDynM4R4W10hm2tPjy8iR1k8XhDv4/qdxe6m07NjG/By1tkmGpm1mGwho4Pr5kbAAy/Qg+NLCSdAYnnE00FQEcFOC15GFVMOW2AzDGKisReohwH9eIzHPzdYQNPRWXE= bradfitz@papag.bradfitz.com")
+ }
+ const maxCloudConfig = 32 << 10 // per compute API docs
+ if len(cloudConfig) > maxCloudConfig {
+ log.Fatalf("cloud config length of %d bytes is over %d byte limit", len(cloudConfig), maxCloudConfig)
+ }
+
+ instance := &compute.Instance{
+ Name: *instName,
+ Description: "Go Builder",
+ MachineType: machType,
+ Disks: []*compute.AttachedDisk{instanceDisk(computeService)},
+ Tags: &compute.Tags{
+ Items: []string{"http-server", "https-server"},
+ },
+ Metadata: &compute.Metadata{
+ Items: []*compute.MetadataItems{
+ {
+ Key: "user-data",
+ Value: cloudConfig,
+ },
+ },
+ },
+ NetworkInterfaces: []*compute.NetworkInterface{
+ &compute.NetworkInterface{
+ AccessConfigs: []*compute.AccessConfig{
+ &compute.AccessConfig{
+ Type: "ONE_TO_ONE_NAT",
+ Name: "External NAT",
+ NatIP: natIP,
+ },
+ },
+ Network: prefix + "/global/networks/default",
+ },
+ },
+ ServiceAccounts: []*compute.ServiceAccount{
+ {
+ Email: "default",
+ Scopes: []string{
+ compute.DevstorageFull_controlScope,
+ compute.ComputeScope,
+ },
+ },
+ },
+ }
+
+ log.Printf("Creating instance...")
+ op, err := computeService.Instances.Insert(*proj, *zone, instance).Do()
+ if err != nil {
+ log.Fatalf("Failed to create instance: %v", err)
+ }
+ opName := op.Name
+ log.Printf("Created. Waiting on operation %v", opName)
+OpLoop:
+ for {
+ time.Sleep(2 * time.Second)
+ op, err := computeService.ZoneOperations.Get(*proj, *zone, opName).Do()
+ if err != nil {
+ log.Fatalf("Failed to get op %s: %v", opName, err)
+ }
+ switch op.Status {
+ case "PENDING", "RUNNING":
+ log.Printf("Waiting on operation %v", opName)
+ continue
+ case "DONE":
+ if op.Error != nil {
+ for _, operr := range op.Error.Errors {
+ log.Printf("Error: %+v", operr)
+ }
+ log.Fatalf("Failed to start.")
+ }
+ log.Printf("Success. %+v", op)
+ break OpLoop
+ default:
+ log.Fatalf("Unknown status %q: %+v", op.Status, op)
+ }
+ }
+
+ inst, err := computeService.Instances.Get(*proj, *zone, *instName).Do()
+ if err != nil {
+ log.Fatalf("Error getting instance after creation: %v", err)
+ }
+ ij, _ := json.MarshalIndent(inst, "", " ")
+ log.Printf("Instance: %s", ij)
+}
+
+func instanceDisk(svc *compute.Service) *compute.AttachedDisk {
+ const imageURL = "https://www.googleapis.com/compute/v1/projects/coreos-cloud/global/images/coreos-stable-444-5-0-v20141016"
+ diskName := *instName + "-disk"
+
+ return &compute.AttachedDisk{
+ AutoDelete: true,
+ Boot: true,
+ Type: "PERSISTENT",
+ InitializeParams: &compute.AttachedDiskInitializeParams{
+ DiskName: diskName,
+ SourceImage: imageURL,
+ DiskSizeGb: 50,
+ },
+ }
+}
+
+func writeCloudStorageObject(httpClient *http.Client) {
+ content := os.Stdin
+ const maxSlurp = 1 << 20
+ var buf bytes.Buffer
+ n, err := io.CopyN(&buf, content, maxSlurp)
+ if err != nil && err != io.EOF {
+ log.Fatalf("Error reading from stdin: %v, %v", n, err)
+ }
+ contentType := http.DetectContentType(buf.Bytes())
+
+ req, err := http.NewRequest("PUT", "https://storage.googleapis.com/"+*writeObject, io.MultiReader(&buf, content))
+ if err != nil {
+ log.Fatal(err)
+ }
+ req.Header.Set("x-goog-api-version", "2")
+ if *publicObject {
+ req.Header.Set("x-goog-acl", "public-read")
+ }
+ req.Header.Set("Content-Type", contentType)
+ res, err := httpClient.Do(req)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if res.StatusCode != 200 {
+ res.Write(os.Stderr)
+ log.Fatalf("Failed.")
+ }
+ log.Printf("Success.")
+ os.Exit(0)
+}
+
+type tokenCacheFile string
+
+func (f tokenCacheFile) Token() (*oauth2.Token, error) {
+ slurp, err := ioutil.ReadFile(string(f))
+ if err != nil {
+ return nil, err
+ }
+ t := new(oauth2.Token)
+ if err := json.Unmarshal(slurp, t); err != nil {
+ return nil, err
+ }
+ return t, nil
+}
+
+func (f tokenCacheFile) WriteToken(t *oauth2.Token) error {
+ jt, err := json.Marshal(t)
+ if err != nil {
+ return err
+ }
+ return ioutil.WriteFile(string(f), jt, 0600)
+}
diff --git a/http2/h2demo/rootCA.key b/http2/h2demo/rootCA.key
new file mode 100644
index 0000000..a15a6ab
--- /dev/null
+++ b/http2/h2demo/rootCA.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAt5fAjp4fTcekWUTfzsp0kyih1OYbsGL0KX1eRbSSR8Od0+9Q
+62Hyny+GFwMTb4A/KU8mssoHvcceSAAbwfbxFK/+s51TobqUnORZrOoTZjkUygby
+XDSK99YBbcR1Pip8vwMTm4XKuLtCigeBBdjjAQdgUO28LENGlsMnmeYkJfODVGnV
+mr5Ltb9ANA8IKyTfsnHJ4iOCS/PlPbUj2q7YnoVLposUBMlgUb/CykX3mOoLb4yJ
+JQyA/iST6ZxiIEj36D4yWZ5lg7YJl+UiiBQHGCnPdGyipqV06ex0heYWcaiW8LWZ
+SUQ93jQ+WVCH8hT7DQO1dmsvUmXlq/JeAlwQ/QIDAQABAoIBAFFHV7JMAqPWnMYA
+nezY6J81v9+XN+7xABNWM2Q8uv4WdksbigGLTXR3/680Z2hXqJ7LMeC5XJACFT/e
+/Gr0vmpgOCygnCPfjGehGKpavtfksXV3edikUlnCXsOP1C//c1bFL+sMYmFCVgTx
+qYdDK8yKzXNGrKYT6q5YG7IglyRNV1rsQa8lM/5taFYiD1Ck/3tQi3YIq8Lcuser
+hrxsMABcQ6mi+EIvG6Xr4mfJug0dGJMHG4RG1UGFQn6RXrQq2+q53fC8ZbVUSi0j
+NQ918aKFzktwv+DouKU0ME4I9toks03gM860bAL7zCbKGmwR3hfgX/TqzVCWpG9E
+LDVfvekCgYEA8fk9N53jbBRmULUGEf4qWypcLGiZnNU0OeXWpbPV9aa3H0VDytA7
+8fCN2dPAVDPqlthMDdVe983NCNwp2Yo8ZimDgowyIAKhdC25s1kejuaiH9OAPj3c
+0f8KbriYX4n8zNHxFwK6Ae3pQ6EqOLJVCUsziUaZX9nyKY5aZlyX6xcCgYEAwjws
+K62PjC64U5wYddNLp+kNdJ4edx+a7qBb3mEgPvSFT2RO3/xafJyG8kQB30Mfstjd
+bRxyUV6N0vtX1zA7VQtRUAvfGCecpMo+VQZzcHXKzoRTnQ7eZg4Lmj5fQ9tOAKAo
+QCVBoSW/DI4PZL26CAMDcAba4Pa22ooLapoRIQsCgYA6pIfkkbxLNkpxpt2YwLtt
+Kr/590O7UaR9n6k8sW/aQBRDXNsILR1KDl2ifAIxpf9lnXgZJiwE7HiTfCAcW7c1
+nzwDCI0hWuHcMTS/NYsFYPnLsstyyjVZI3FY0h4DkYKV9Q9z3zJLQ2hz/nwoD3gy
+b2pHC7giFcTts1VPV4Nt8wKBgHeFn4ihHJweg76vZz3Z78w7VNRWGFklUalVdDK7
+gaQ7w2y/ROn/146mo0OhJaXFIFRlrpvdzVrU3GDf2YXJYDlM5ZRkObwbZADjksev
+WInzcgDy3KDg7WnPasRXbTfMU4t/AkW2p1QKbi3DnSVYuokDkbH2Beo45vxDxhKr
+C69RAoGBAIyo3+OJenoZmoNzNJl2WPW5MeBUzSh8T/bgyjFTdqFHF5WiYRD/lfHj
+x9Glyw2nutuT4hlOqHvKhgTYdDMsF2oQ72fe3v8Q5FU7FuKndNPEAyvKNXZaShVA
+hnlhv5DjXKb0wFWnt5PCCiQLtzG0yyHaITrrEme7FikkIcTxaX/Y
+-----END RSA PRIVATE KEY-----
diff --git a/http2/h2demo/rootCA.pem b/http2/h2demo/rootCA.pem
new file mode 100644
index 0000000..3a323e7
--- /dev/null
+++ b/http2/h2demo/rootCA.pem
@@ -0,0 +1,26 @@
+-----BEGIN CERTIFICATE-----
+MIIEWjCCA0KgAwIBAgIJALfRlWsI8YQHMA0GCSqGSIb3DQEBBQUAMHsxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEUMBIG
+A1UEChMLQnJhZGZpdHppbmMxEjAQBgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3
+DQEJARYOYnJhZEBkYW5nYS5jb20wHhcNMTQwNzE1MjA0NjA1WhcNMTcwNTA0MjA0
+NjA1WjB7MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBG
+cmFuY2lzY28xFDASBgNVBAoTC0JyYWRmaXR6aW5jMRIwEAYDVQQDEwlsb2NhbGhv
+c3QxHTAbBgkqhkiG9w0BCQEWDmJyYWRAZGFuZ2EuY29tMIIBIjANBgkqhkiG9w0B
+AQEFAAOCAQ8AMIIBCgKCAQEAt5fAjp4fTcekWUTfzsp0kyih1OYbsGL0KX1eRbSS
+R8Od0+9Q62Hyny+GFwMTb4A/KU8mssoHvcceSAAbwfbxFK/+s51TobqUnORZrOoT
+ZjkUygbyXDSK99YBbcR1Pip8vwMTm4XKuLtCigeBBdjjAQdgUO28LENGlsMnmeYk
+JfODVGnVmr5Ltb9ANA8IKyTfsnHJ4iOCS/PlPbUj2q7YnoVLposUBMlgUb/CykX3
+mOoLb4yJJQyA/iST6ZxiIEj36D4yWZ5lg7YJl+UiiBQHGCnPdGyipqV06ex0heYW
+caiW8LWZSUQ93jQ+WVCH8hT7DQO1dmsvUmXlq/JeAlwQ/QIDAQABo4HgMIHdMB0G
+A1UdDgQWBBRcAROthS4P4U7vTfjByC569R7E6DCBrQYDVR0jBIGlMIGigBRcAROt
+hS4P4U7vTfjByC569R7E6KF/pH0wezELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNB
+MRYwFAYDVQQHEw1TYW4gRnJhbmNpc2NvMRQwEgYDVQQKEwtCcmFkZml0emluYzES
+MBAGA1UEAxMJbG9jYWxob3N0MR0wGwYJKoZIhvcNAQkBFg5icmFkQGRhbmdhLmNv
+bYIJALfRlWsI8YQHMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAG6h
+U9f9sNH0/6oBbGGy2EVU0UgITUQIrFWo9rFkrW5k/XkDjQm+3lzjT0iGR4IxE/Ao
+eU6sQhua7wrWeFEn47GL98lnCsJdD7oZNhFmQ95Tb/LnDUjs5Yj9brP0NWzXfYU4
+UK2ZnINJRcJpB8iRCaCxE8DdcUF0XqIEq6pA272snoLmiXLMvNl3kYEdm+je6voD
+58SNVEUsztzQyXmJEhCpwVI0A6QCjzXj+qvpmw3ZZHi8JwXei8ZZBLTSFBki8Z7n
+sH9BBH38/SzUmAN4QHSPy1gjqm00OAE8NaYDkh/bzE4d7mLGGMWp/WE3KPSu82HF
+kPe6XoSbiLm/kxk32T0=
+-----END CERTIFICATE-----
diff --git a/http2/h2demo/rootCA.srl b/http2/h2demo/rootCA.srl
new file mode 100644
index 0000000..6db3891
--- /dev/null
+++ b/http2/h2demo/rootCA.srl
@@ -0,0 +1 @@
+E2CE26BF3285059C
diff --git a/http2/h2demo/server.crt b/http2/h2demo/server.crt
new file mode 100644
index 0000000..c59059b
--- /dev/null
+++ b/http2/h2demo/server.crt
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDPjCCAiYCCQDizia/MoUFnDANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJV
+UzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xFDASBgNVBAoT
+C0JyYWRmaXR6aW5jMRIwEAYDVQQDEwlsb2NhbGhvc3QxHTAbBgkqhkiG9w0BCQEW
+DmJyYWRAZGFuZ2EuY29tMB4XDTE0MDcxNTIwNTAyN1oXDTE1MTEyNzIwNTAyN1ow
+RzELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMQswCQYDVQQHEwJTRjEeMBwGA1UE
+ChMVYnJhZGZpdHogaHR0cDIgc2VydmVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
+MIIBCgKCAQEAs1Y9CyLFrdL8VQWN1WaifDqaZFnoqjHhCMlc1TfG2zA+InDifx2l
+gZD3o8FeNnAcfM2sPlk3+ZleOYw9P/CklFVDlvqmpCv9ss/BEp/dDaWvy1LmJ4c2
+dbQJfmTxn7CV1H3TsVJvKdwFmdoABb41NoBp6+NNO7OtDyhbIMiCI0pL3Nefb3HL
+A7hIMo3DYbORTtJLTIH9W8YKrEWL0lwHLrYFx/UdutZnv+HjdmO6vCN4na55mjws
+/vjKQUmc7xeY7Xe20xDEG2oDKVkL2eD7FfyrYMS3rO1ExP2KSqlXYG/1S9I/fz88
+F0GK7HX55b5WjZCl2J3ERVdnv/0MQv+sYQIDAQABMA0GCSqGSIb3DQEBBQUAA4IB
+AQC0zL+n/YpRZOdulSu9tS8FxrstXqGWoxfe+vIUgqfMZ5+0MkjJ/vW0FqlLDl2R
+rn4XaR3e7FmWkwdDVbq/UB6lPmoAaFkCgh9/5oapMaclNVNnfF3fjCJfRr+qj/iD
+EmJStTIN0ZuUjAlpiACmfnpEU55PafT5Zx+i1yE4FGjw8bJpFoyD4Hnm54nGjX19
+KeCuvcYFUPnBm3lcL0FalF2AjqV02WTHYNQk7YF/oeO7NKBoEgvGvKG3x+xaOeBI
+dwvdq175ZsGul30h+QjrRlXhH/twcuaT3GSdoysDl9cCYE8f1Mk8PD6gan3uBCJU
+90p6/CbU71bGbfpM2PHot2fm
+-----END CERTIFICATE-----
diff --git a/http2/h2demo/server.key b/http2/h2demo/server.key
new file mode 100644
index 0000000..f329c14
--- /dev/null
+++ b/http2/h2demo/server.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAs1Y9CyLFrdL8VQWN1WaifDqaZFnoqjHhCMlc1TfG2zA+InDi
+fx2lgZD3o8FeNnAcfM2sPlk3+ZleOYw9P/CklFVDlvqmpCv9ss/BEp/dDaWvy1Lm
+J4c2dbQJfmTxn7CV1H3TsVJvKdwFmdoABb41NoBp6+NNO7OtDyhbIMiCI0pL3Nef
+b3HLA7hIMo3DYbORTtJLTIH9W8YKrEWL0lwHLrYFx/UdutZnv+HjdmO6vCN4na55
+mjws/vjKQUmc7xeY7Xe20xDEG2oDKVkL2eD7FfyrYMS3rO1ExP2KSqlXYG/1S9I/
+fz88F0GK7HX55b5WjZCl2J3ERVdnv/0MQv+sYQIDAQABAoIBADQ2spUwbY+bcz4p
+3M66ECrNQTBggP40gYl2XyHxGGOu2xhZ94f9ELf1hjRWU2DUKWco1rJcdZClV6q3
+qwmXvcM2Q/SMS8JW0ImkNVl/0/NqPxGatEnj8zY30d/L8hGFb0orzFu/XYA5gCP4
+NbN2WrXgk3ZLeqwcNxHHtSiJWGJ/fPyeDWAu/apy75u9Xf2GlzBZmV6HYD9EfK80
+LTlI60f5FO487CrJnboL7ovPJrIHn+k05xRQqwma4orpz932rTXnTjs9Lg6KtbQN
+a7PrqfAntIISgr11a66Mng3IYH1lYqJsWJJwX/xHT4WLEy0EH4/0+PfYemJekz2+
+Co62drECgYEA6O9zVJZXrLSDsIi54cfxA7nEZWm5CAtkYWeAHa4EJ+IlZ7gIf9sL
+W8oFcEfFGpvwVqWZ+AsQ70dsjXAv3zXaG0tmg9FtqWp7pzRSMPidifZcQwWkKeTO
+gJnFmnVyed8h6GfjTEu4gxo1/S5U0V+mYSha01z5NTnN6ltKx1Or3b0CgYEAxRgm
+S30nZxnyg/V7ys61AZhst1DG2tkZXEMcA7dYhabMoXPJAP/EfhlWwpWYYUs/u0gS
+Wwmf5IivX5TlYScgmkvb/NYz0u4ZmOXkLTnLPtdKKFXhjXJcHjUP67jYmOxNlJLp
+V4vLRnFxTpffAV+OszzRxsXX6fvruwZBANYJeXUCgYBVouLFsFgfWGYp2rpr9XP4
+KK25kvrBqF6JKOIDB1zjxNJ3pUMKrl8oqccCFoCyXa4oTM2kUX0yWxHfleUjrMq4
+yimwQKiOZmV7fVLSSjSw6e/VfBd0h3gb82ygcplZkN0IclkwTY5SNKqwn/3y07V5
+drqdhkrgdJXtmQ6O5YYECQKBgATERcDToQ1USlI4sKrB/wyv1AlG8dg/IebiVJ4e
+ZAyvcQmClFzq0qS+FiQUnB/WQw9TeeYrwGs1hxBHuJh16srwhLyDrbMvQP06qh8R
+48F8UXXSRec22dV9MQphaROhu2qZdv1AC0WD3tqov6L33aqmEOi+xi8JgbT/PLk5
+c/c1AoGBAI1A/02ryksW6/wc7/6SP2M2rTy4m1sD/GnrTc67EHnRcVBdKO6qH2RY
+nqC8YcveC2ZghgPTDsA3VGuzuBXpwY6wTyV99q6jxQJ6/xcrD9/NUG6Uwv/xfCxl
+IJLeBYEqQundSSny3VtaAUK8Ul1nxpTvVRNwtcyWTo8RHAAyNPWd
+-----END RSA PRIVATE KEY-----
diff --git a/http2/h2i/README.md b/http2/h2i/README.md
new file mode 100644
index 0000000..fb5c5ef
--- /dev/null
+++ b/http2/h2i/README.md
@@ -0,0 +1,97 @@
+# h2i
+
+**h2i** is an interactive HTTP/2 ("h2") console debugger. Miss the good ol'
+days of telnetting to your HTTP/1.n servers? We're bringing you
+back.
+
+Features:
+- send raw HTTP/2 frames
+ - PING
+ - SETTINGS
+ - HEADERS
+ - etc
+- type in HTTP/1.n and have it auto-HPACK/frame-ify it for HTTP/2
+- pretty print all received HTTP/2 frames from the peer (including HPACK decoding)
+- tab completion of commands, options
+
+Not yet features, but soon:
+- unnecessary CONTINUATION frames on short boundaries, to test peer implementations
+- request bodies (DATA frames)
+- send invalid frames for testing server implementations (supported by underlying Framer)
+
+Later:
+- act like a server
+
+## Installation
+
+```
+$ go get golang.org/x/net/http2/h2i
+$ h2i <host>
+```
+
+## Demo
+
+```
+$ h2i
+Usage: h2i <hostname>
+
+ -insecure
+ Whether to skip TLS cert validation
+ -nextproto string
+ Comma-separated list of NPN/ALPN protocol names to negotiate. (default "h2,h2-14")
+
+$ h2i google.com
+Connecting to google.com:443 ...
+Connected to 74.125.224.41:443
+Negotiated protocol "h2-14"
+[FrameHeader SETTINGS len=18]
+ [MAX_CONCURRENT_STREAMS = 100]
+ [INITIAL_WINDOW_SIZE = 1048576]
+ [MAX_FRAME_SIZE = 16384]
+[FrameHeader WINDOW_UPDATE len=4]
+ Window-Increment = 983041
+
+h2i> PING h2iSayHI
+[FrameHeader PING flags=ACK len=8]
+ Data = "h2iSayHI"
+h2i> headers
+(as HTTP/1.1)> GET / HTTP/1.1
+(as HTTP/1.1)> Host: ip.appspot.com
+(as HTTP/1.1)> User-Agent: h2i/brad-n-blake
+(as HTTP/1.1)>
+Opening Stream-ID 1:
+ :authority = ip.appspot.com
+ :method = GET
+ :path = /
+ :scheme = https
+ user-agent = h2i/brad-n-blake
+[FrameHeader HEADERS flags=END_HEADERS stream=1 len=77]
+ :status = "200"
+ alternate-protocol = "443:quic,p=1"
+ content-length = "15"
+ content-type = "text/html"
+ date = "Fri, 01 May 2015 23:06:56 GMT"
+ server = "Google Frontend"
+[FrameHeader DATA flags=END_STREAM stream=1 len=15]
+ "173.164.155.78\n"
+[FrameHeader PING len=8]
+ Data = "\x00\x00\x00\x00\x00\x00\x00\x00"
+h2i> ping
+[FrameHeader PING flags=ACK len=8]
+ Data = "h2i_ping"
+h2i> ping
+[FrameHeader PING flags=ACK len=8]
+ Data = "h2i_ping"
+h2i> ping
+[FrameHeader GOAWAY len=22]
+ Last-Stream-ID = 1; Error-Code = PROTOCOL_ERROR (1)
+
+ReadFrame: EOF
+```
+
+## Status
+
+Quick few hour hack. So much yet to do. Feel free to file issues for
+bugs or wishlist items, but [@bmizerany](https://github.com/bmizerany/)
+and I aren't yet accepting pull requests until things settle down.
+
diff --git a/http2/h2i/h2i.go b/http2/h2i/h2i.go
new file mode 100644
index 0000000..73efe86
--- /dev/null
+++ b/http2/h2i/h2i.go
@@ -0,0 +1,489 @@
+// Copyright 2015 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+/*
+The h2i command is an interactive HTTP/2 console.
+
+Usage:
+ $ h2i [flags] <hostname>
+
+Interactive commands in the console: (all parts case-insensitive)
+
+ ping [data]
+ settings ack
+ settings FOO=n BAR=z
+ headers (open a new stream by typing HTTP/1.1)
+*/
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/tls"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "os"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "golang.org/x/crypto/ssh/terminal"
+ "golang.org/x/net/http2"
+ "golang.org/x/net/http2/hpack"
+)
+
+// Flags
+var (
+ flagNextProto = flag.String("nextproto", "h2,h2-14", "Comma-separated list of NPN/ALPN protocol names to negotiate.")
+ flagInsecure = flag.Bool("insecure", false, "Whether to skip TLS cert validation")
+)
+
+type command struct {
+ run func(*h2i, []string) error // required
+
+ // complete optionally specifies tokens (case-insensitive) which are
+ // valid for this subcommand.
+ complete func() []string
+}
+
+var commands = map[string]command{
+ "ping": command{run: (*h2i).cmdPing},
+ "settings": command{
+ run: (*h2i).cmdSettings,
+ complete: func() []string {
+ return []string{
+ "ACK",
+ http2.SettingHeaderTableSize.String(),
+ http2.SettingEnablePush.String(),
+ http2.SettingMaxConcurrentStreams.String(),
+ http2.SettingInitialWindowSize.String(),
+ http2.SettingMaxFrameSize.String(),
+ http2.SettingMaxHeaderListSize.String(),
+ }
+ },
+ },
+ "quit": command{run: (*h2i).cmdQuit},
+ "headers": command{run: (*h2i).cmdHeaders},
+}
+
+func usage() {
+ fmt.Fprintf(os.Stderr, "Usage: h2i <hostname>\n\n")
+ flag.PrintDefaults()
+ os.Exit(1)
+}
+
+// withPort adds ":443" if another port isn't already present.
+func withPort(host string) string {
+ if _, _, err := net.SplitHostPort(host); err != nil {
+ return net.JoinHostPort(host, "443")
+ }
+ return host
+}
+
+// h2i is the app's state.
+type h2i struct {
+ host string
+ tc *tls.Conn
+ framer *http2.Framer
+ term *terminal.Terminal
+
+ // owned by the command loop:
+ streamID uint32
+ hbuf bytes.Buffer
+ henc *hpack.Encoder
+
+ // owned by the readFrames loop:
+ peerSetting map[http2.SettingID]uint32
+ hdec *hpack.Decoder
+}
+
+func main() {
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() != 1 {
+ usage()
+ }
+ log.SetFlags(0)
+
+ host := flag.Arg(0)
+ app := &h2i{
+ host: host,
+ peerSetting: make(map[http2.SettingID]uint32),
+ }
+ app.henc = hpack.NewEncoder(&app.hbuf)
+
+ if err := app.Main(); err != nil {
+ if app.term != nil {
+ app.logf("%v\n", err)
+ } else {
+ fmt.Fprintf(os.Stderr, "%v\n", err)
+ }
+ os.Exit(1)
+ }
+ fmt.Fprintf(os.Stdout, "\n")
+}
+
+func (app *h2i) Main() error {
+ cfg := &tls.Config{
+ ServerName: app.host,
+ NextProtos: strings.Split(*flagNextProto, ","),
+ InsecureSkipVerify: *flagInsecure,
+ }
+
+ hostAndPort := withPort(app.host)
+ log.Printf("Connecting to %s ...", hostAndPort)
+ tc, err := tls.Dial("tcp", hostAndPort, cfg)
+ if err != nil {
+ return fmt.Errorf("Error dialing %s: %v", withPort(app.host), err)
+ }
+ log.Printf("Connected to %v", tc.RemoteAddr())
+ defer tc.Close()
+
+ if err := tc.Handshake(); err != nil {
+ return fmt.Errorf("TLS handshake: %v", err)
+ }
+ if !*flagInsecure {
+ if err := tc.VerifyHostname(app.host); err != nil {
+ return fmt.Errorf("VerifyHostname: %v", err)
+ }
+ }
+ state := tc.ConnectionState()
+ log.Printf("Negotiated protocol %q", state.NegotiatedProtocol)
+ if !state.NegotiatedProtocolIsMutual || state.NegotiatedProtocol == "" {
+ return fmt.Errorf("Could not negotiate protocol mutually")
+ }
+
+ if _, err := io.WriteString(tc, http2.ClientPreface); err != nil {
+ return err
+ }
+
+ app.framer = http2.NewFramer(tc, tc)
+
+ oldState, err := terminal.MakeRaw(0)
+ if err != nil {
+ return err
+ }
+ defer terminal.Restore(0, oldState)
+
+ var screen = struct {
+ io.Reader
+ io.Writer
+ }{os.Stdin, os.Stdout}
+
+ app.term = terminal.NewTerminal(screen, "h2i> ")
+ lastWord := regexp.MustCompile(`.+\W(\w+)$`)
+ app.term.AutoCompleteCallback = func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
+ if key != '\t' {
+ return
+ }
+ if pos != len(line) {
+ // TODO: we're being lazy for now, only supporting tab completion at the end.
+ return
+ }
+ // Auto-complete for the command itself.
+ if !strings.Contains(line, " ") {
+ var name string
+ name, _, ok = lookupCommand(line)
+ if !ok {
+ return
+ }
+ return name, len(name), true
+ }
+ _, c, ok := lookupCommand(line[:strings.IndexByte(line, ' ')])
+ if !ok || c.complete == nil {
+ return
+ }
+ if strings.HasSuffix(line, " ") {
+ app.logf("%s", strings.Join(c.complete(), " "))
+ return line, pos, true
+ }
+ m := lastWord.FindStringSubmatch(line)
+ if m == nil {
+ return line, len(line), true
+ }
+ soFar := m[1]
+ var match []string
+ for _, cand := range c.complete() {
+ if len(soFar) > len(cand) || !strings.EqualFold(cand[:len(soFar)], soFar) {
+ continue
+ }
+ match = append(match, cand)
+ }
+ if len(match) == 0 {
+ return
+ }
+ if len(match) > 1 {
+ // TODO: auto-complete any common prefix
+ app.logf("%s", strings.Join(match, " "))
+ return line, pos, true
+ }
+ newLine = line[:len(line)-len(soFar)] + match[0]
+ return newLine, len(newLine), true
+
+ }
+
+ errc := make(chan error, 2)
+ go func() { errc <- app.readFrames() }()
+ go func() { errc <- app.readConsole() }()
+ return <-errc
+}
+
+func (app *h2i) logf(format string, args ...interface{}) {
+ fmt.Fprintf(app.term, format+"\n", args...)
+}
+
+func (app *h2i) readConsole() error {
+ for {
+ line, err := app.term.ReadLine()
+ if err == io.EOF {
+ return nil
+ }
+ if err != nil {
+ return fmt.Errorf("terminal.ReadLine: %v", err)
+ }
+ f := strings.Fields(line)
+ if len(f) == 0 {
+ continue
+ }
+ cmd, args := f[0], f[1:]
+ if _, c, ok := lookupCommand(cmd); ok {
+ err = c.run(app, args)
+ } else {
+ app.logf("Unknown command %q", line)
+ }
+ if err == errExitApp {
+ return nil
+ }
+ if err != nil {
+ return err
+ }
+ }
+}
+
+func lookupCommand(prefix string) (name string, c command, ok bool) {
+ prefix = strings.ToLower(prefix)
+ if c, ok = commands[prefix]; ok {
+ return prefix, c, ok
+ }
+
+ for full, candidate := range commands {
+ if strings.HasPrefix(full, prefix) {
+ if c.run != nil {
+ return "", command{}, false // ambiguous
+ }
+ c = candidate
+ name = full
+ }
+ }
+ return name, c, c.run != nil
+}
+
+var errExitApp = errors.New("internal sentinel error value to quit the console reading loop")
+
+func (a *h2i) cmdQuit(args []string) error {
+ if len(args) > 0 {
+ a.logf("the QUIT command takes no argument")
+ return nil
+ }
+ return errExitApp
+}
+
+func (a *h2i) cmdSettings(args []string) error {
+ if len(args) == 1 && strings.EqualFold(args[0], "ACK") {
+ return a.framer.WriteSettingsAck()
+ }
+ var settings []http2.Setting
+ for _, arg := range args {
+ if strings.EqualFold(arg, "ACK") {
+ a.logf("Error: ACK must be only argument with the SETTINGS command")
+ return nil
+ }
+ eq := strings.Index(arg, "=")
+ if eq == -1 {
+ a.logf("Error: invalid argument %q (expected SETTING_NAME=nnnn)", arg)
+ return nil
+ }
+ sid, ok := settingByName(arg[:eq])
+ if !ok {
+ a.logf("Error: unknown setting name %q", arg[:eq])
+ return nil
+ }
+ val, err := strconv.ParseUint(arg[eq+1:], 10, 32)
+ if err != nil {
+ a.logf("Error: invalid argument %q (expected SETTING_NAME=nnnn)", arg)
+ return nil
+ }
+ settings = append(settings, http2.Setting{
+ ID: sid,
+ Val: uint32(val),
+ })
+ }
+ a.logf("Sending: %v", settings)
+ return a.framer.WriteSettings(settings...)
+}
+
+func settingByName(name string) (http2.SettingID, bool) {
+ for _, sid := range [...]http2.SettingID{
+ http2.SettingHeaderTableSize,
+ http2.SettingEnablePush,
+ http2.SettingMaxConcurrentStreams,
+ http2.SettingInitialWindowSize,
+ http2.SettingMaxFrameSize,
+ http2.SettingMaxHeaderListSize,
+ } {
+ if strings.EqualFold(sid.String(), name) {
+ return sid, true
+ }
+ }
+ return 0, false
+}
+
+func (app *h2i) cmdPing(args []string) error {
+ if len(args) > 1 {
+ app.logf("invalid PING usage: only accepts 0 or 1 args")
+ return nil // nil means don't end the program
+ }
+ var data [8]byte
+ if len(args) == 1 {
+ copy(data[:], args[0])
+ } else {
+ copy(data[:], "h2i_ping")
+ }
+ return app.framer.WritePing(false, data)
+}
+
+func (app *h2i) cmdHeaders(args []string) error {
+ if len(args) > 0 {
+ app.logf("Error: HEADERS doesn't yet take arguments.")
+ // TODO: flags for restricting window size, to force CONTINUATION
+ // frames.
+ return nil
+ }
+ var h1req bytes.Buffer
+ app.term.SetPrompt("(as HTTP/1.1)> ")
+ defer app.term.SetPrompt("h2i> ")
+ for {
+ line, err := app.term.ReadLine()
+ if err != nil {
+ return err
+ }
+ h1req.WriteString(line)
+ h1req.WriteString("\r\n")
+ if line == "" {
+ break
+ }
+ }
+ req, err := http.ReadRequest(bufio.NewReader(&h1req))
+ if err != nil {
+ app.logf("Invalid HTTP/1.1 request: %v", err)
+ return nil
+ }
+ if app.streamID == 0 {
+ app.streamID = 1
+ } else {
+ app.streamID += 2
+ }
+ app.logf("Opening Stream-ID %d:", app.streamID)
+ hbf := app.encodeHeaders(req)
+ if len(hbf) > 16<<10 {
+ app.logf("TODO: h2i doesn't yet write CONTINUATION frames. Copy it from transport.go")
+ return nil
+ }
+ return app.framer.WriteHeaders(http2.HeadersFrameParam{
+ StreamID: app.streamID,
+ BlockFragment: hbf,
+ EndStream: req.Method == "GET" || req.Method == "HEAD", // good enough for now
+ EndHeaders: true, // for now
+ })
+}
+
+func (app *h2i) readFrames() error {
+ for {
+ f, err := app.framer.ReadFrame()
+ if err != nil {
+ return fmt.Errorf("ReadFrame: %v", err)
+ }
+ app.logf("%v", f)
+ switch f := f.(type) {
+ case *http2.PingFrame:
+ app.logf(" Data = %q", f.Data)
+ case *http2.SettingsFrame:
+ f.ForeachSetting(func(s http2.Setting) error {
+ app.logf(" %v", s)
+ app.peerSetting[s.ID] = s.Val
+ return nil
+ })
+ case *http2.WindowUpdateFrame:
+ app.logf(" Window-Increment = %v\n", f.Increment)
+ case *http2.GoAwayFrame:
+ app.logf(" Last-Stream-ID = %d; Error-Code = %v (%d)\n", f.LastStreamID, f.ErrCode, f.ErrCode)
+ case *http2.DataFrame:
+ app.logf(" %q", f.Data())
+ case *http2.HeadersFrame:
+ if f.HasPriority() {
+ app.logf(" PRIORITY = %v", f.Priority)
+ }
+ if app.hdec == nil {
+ // TODO: if the user uses h2i to send a SETTINGS frame advertising
+ // something larger, we'll need to respect SETTINGS_HEADER_TABLE_SIZE
+ // and stuff here instead of using the 4k default. But for now:
+ tableSize := uint32(4 << 10)
+ app.hdec = hpack.NewDecoder(tableSize, app.onNewHeaderField)
+ }
+ app.hdec.Write(f.HeaderBlockFragment())
+ }
+ }
+}
+
+// called from readLoop
+func (app *h2i) onNewHeaderField(f hpack.HeaderField) {
+ if f.Sensitive {
+ app.logf(" %s = %q (SENSITIVE)", f.Name, f.Value)
+ }
+ app.logf(" %s = %q", f.Name, f.Value)
+}
+
+func (app *h2i) encodeHeaders(req *http.Request) []byte {
+ app.hbuf.Reset()
+
+ // TODO(bradfitz): figure out :authority-vs-Host stuff between http2 and Go
+ host := req.Host
+ if host == "" {
+ host = req.URL.Host
+ }
+
+ path := req.URL.Path
+ if path == "" {
+ path = "/"
+ }
+
+ app.writeHeader(":authority", host) // probably not right for all sites
+ app.writeHeader(":method", req.Method)
+ app.writeHeader(":path", path)
+ app.writeHeader(":scheme", "https")
+
+ for k, vv := range req.Header {
+ lowKey := strings.ToLower(k)
+ if lowKey == "host" {
+ continue
+ }
+ for _, v := range vv {
+ app.writeHeader(lowKey, v)
+ }
+ }
+ return app.hbuf.Bytes()
+}
+
+func (app *h2i) writeHeader(name, value string) {
+ app.henc.WriteField(hpack.HeaderField{Name: name, Value: value})
+ app.logf(" %s = %s", name, value)
+}
diff --git a/http2/headermap.go b/http2/headermap.go
new file mode 100644
index 0000000..67c7c48
--- /dev/null
+++ b/http2/headermap.go
@@ -0,0 +1,80 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "net/http"
+ "strings"
+)
+
+var (
+ commonLowerHeader = map[string]string{} // Go-Canonical-Case -> lower-case
+ commonCanonHeader = map[string]string{} // lower-case -> Go-Canonical-Case
+)
+
+func init() {
+ for _, v := range []string{
+ "accept",
+ "accept-charset",
+ "accept-encoding",
+ "accept-language",
+ "accept-ranges",
+ "age",
+ "access-control-allow-origin",
+ "allow",
+ "authorization",
+ "cache-control",
+ "content-disposition",
+ "content-encoding",
+ "content-language",
+ "content-length",
+ "content-location",
+ "content-range",
+ "content-type",
+ "cookie",
+ "date",
+ "etag",
+ "expect",
+ "expires",
+ "from",
+ "host",
+ "if-match",
+ "if-modified-since",
+ "if-none-match",
+ "if-unmodified-since",
+ "last-modified",
+ "link",
+ "location",
+ "max-forwards",
+ "proxy-authenticate",
+ "proxy-authorization",
+ "range",
+ "referer",
+ "refresh",
+ "retry-after",
+ "server",
+ "set-cookie",
+ "strict-transport-security",
+ "transfer-encoding",
+ "user-agent",
+ "vary",
+ "via",
+ "www-authenticate",
+ } {
+ chk := http.CanonicalHeaderKey(v)
+ commonLowerHeader[chk] = v
+ commonCanonHeader[v] = chk
+ }
+}
+
+func lowerHeader(v string) string {
+ if s, ok := commonLowerHeader[v]; ok {
+ return s
+ }
+ return strings.ToLower(v)
+}
diff --git a/http2/hpack/encode.go b/http2/hpack/encode.go
new file mode 100644
index 0000000..19bd9f4
--- /dev/null
+++ b/http2/hpack/encode.go
@@ -0,0 +1,252 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package hpack
+
+import (
+ "io"
+)
+
+const (
+ uint32Max = ^uint32(0)
+ initialHeaderTableSize = 4096
+)
+
+type Encoder struct {
+ dynTab dynamicTable
+ // minSize is the minimum table size set by
+ // SetMaxDynamicTableSize after the previous Header Table Size
+ // Update.
+ minSize uint32
+ // maxSizeLimit is the maximum table size this encoder
+ // supports. This will protect the encoder from too large
+ // size.
+ maxSizeLimit uint32
+ // tableSizeUpdate indicates whether "Header Table Size
+ // Update" is required.
+ tableSizeUpdate bool
+ w io.Writer
+ buf []byte
+}
+
+// NewEncoder returns a new Encoder which performs HPACK encoding. An
+// encoded data is written to w.
+func NewEncoder(w io.Writer) *Encoder {
+ e := &Encoder{
+ minSize: uint32Max,
+ maxSizeLimit: initialHeaderTableSize,
+ tableSizeUpdate: false,
+ w: w,
+ }
+ e.dynTab.setMaxSize(initialHeaderTableSize)
+ return e
+}
+
+// WriteField encodes f into a single Write to e's underlying Writer.
+// This function may also produce bytes for "Header Table Size Update"
+// if necessary. If produced, it is done before encoding f.
+func (e *Encoder) WriteField(f HeaderField) error {
+ e.buf = e.buf[:0]
+
+ if e.tableSizeUpdate {
+ e.tableSizeUpdate = false
+ if e.minSize < e.dynTab.maxSize {
+ e.buf = appendTableSize(e.buf, e.minSize)
+ }
+ e.minSize = uint32Max
+ e.buf = appendTableSize(e.buf, e.dynTab.maxSize)
+ }
+
+ idx, nameValueMatch := e.searchTable(f)
+ if nameValueMatch {
+ e.buf = appendIndexed(e.buf, idx)
+ } else {
+ indexing := e.shouldIndex(f)
+ if indexing {
+ e.dynTab.add(f)
+ }
+
+ if idx == 0 {
+ e.buf = appendNewName(e.buf, f, indexing)
+ } else {
+ e.buf = appendIndexedName(e.buf, f, idx, indexing)
+ }
+ }
+ n, err := e.w.Write(e.buf)
+ if err == nil && n != len(e.buf) {
+ err = io.ErrShortWrite
+ }
+ return err
+}
+
+// searchTable searches f in both stable and dynamic header tables.
+// The static header table is searched first. Only when there is no
+// exact match for both name and value, the dynamic header table is
+// then searched. If there is no match, i is 0. If both name and value
+// match, i is the matched index and nameValueMatch becomes true. If
+// only name matches, i points to that index and nameValueMatch
+// becomes false.
+func (e *Encoder) searchTable(f HeaderField) (i uint64, nameValueMatch bool) {
+ for idx, hf := range staticTable {
+ if !constantTimeStringCompare(hf.Name, f.Name) {
+ continue
+ }
+ if i == 0 {
+ i = uint64(idx + 1)
+ }
+ if f.Sensitive {
+ continue
+ }
+ if !constantTimeStringCompare(hf.Value, f.Value) {
+ continue
+ }
+ i = uint64(idx + 1)
+ nameValueMatch = true
+ return
+ }
+
+ j, nameValueMatch := e.dynTab.search(f)
+ if nameValueMatch || (i == 0 && j != 0) {
+ i = j + uint64(len(staticTable))
+ }
+ return
+}
+
+// SetMaxDynamicTableSize changes the dynamic header table size to v.
+// The actual size is bounded by the value passed to
+// SetMaxDynamicTableSizeLimit.
+func (e *Encoder) SetMaxDynamicTableSize(v uint32) {
+ if v > e.maxSizeLimit {
+ v = e.maxSizeLimit
+ }
+ if v < e.minSize {
+ e.minSize = v
+ }
+ e.tableSizeUpdate = true
+ e.dynTab.setMaxSize(v)
+}
+
+// SetMaxDynamicTableSizeLimit changes the maximum value that can be
+// specified in SetMaxDynamicTableSize to v. By default, it is set to
+// 4096, which is the same size of the default dynamic header table
+// size described in HPACK specification. If the current maximum
+// dynamic header table size is strictly greater than v, "Header Table
+// Size Update" will be done in the next WriteField call and the
+// maximum dynamic header table size is truncated to v.
+func (e *Encoder) SetMaxDynamicTableSizeLimit(v uint32) {
+ e.maxSizeLimit = v
+ if e.dynTab.maxSize > v {
+ e.tableSizeUpdate = true
+ e.dynTab.setMaxSize(v)
+ }
+}
+
+// shouldIndex reports whether f should be indexed.
+func (e *Encoder) shouldIndex(f HeaderField) bool {
+ return !f.Sensitive && f.size() <= e.dynTab.maxSize
+}
+
+// appendIndexed appends index i, as encoded in "Indexed Header Field"
+// representation, to dst and returns the extended buffer.
+func appendIndexed(dst []byte, i uint64) []byte {
+ first := len(dst)
+ dst = appendVarInt(dst, 7, i)
+ dst[first] |= 0x80
+ return dst
+}
+
+// appendNewName appends f, as encoded in one of "Literal Header field
+// - New Name" representation variants, to dst and returns the
+// extended buffer.
+//
+// If f.Sensitive is true, "Never Indexed" representation is used. If
+// f.Sensitive is false and indexing is true, "Inremental Indexing"
+// representation is used.
+func appendNewName(dst []byte, f HeaderField, indexing bool) []byte {
+ dst = append(dst, encodeTypeByte(indexing, f.Sensitive))
+ dst = appendHpackString(dst, f.Name)
+ return appendHpackString(dst, f.Value)
+}
+
+// appendIndexedName appends f and index i referring indexed name
+// entry, as encoded in one of "Literal Header field - Indexed Name"
+// representation variants, to dst and returns the extended buffer.
+//
+// If f.Sensitive is true, "Never Indexed" representation is used. If
+// f.Sensitive is false and indexing is true, "Incremental Indexing"
+// representation is used.
+func appendIndexedName(dst []byte, f HeaderField, i uint64, indexing bool) []byte {
+ first := len(dst)
+ var n byte
+ if indexing {
+ n = 6
+ } else {
+ n = 4
+ }
+ dst = appendVarInt(dst, n, i)
+ dst[first] |= encodeTypeByte(indexing, f.Sensitive)
+ return appendHpackString(dst, f.Value)
+}
+
+// appendTableSize appends v, as encoded in "Header Table Size Update"
+// representation, to dst and returns the extended buffer.
+func appendTableSize(dst []byte, v uint32) []byte {
+ first := len(dst)
+ dst = appendVarInt(dst, 5, uint64(v))
+ dst[first] |= 0x20
+ return dst
+}
+
+// appendVarInt appends i, as encoded in variable integer form using n
+// bit prefix, to dst and returns the extended buffer.
+//
+// See
+// http://http2.github.io/http2-spec/compression.html#integer.representation
+func appendVarInt(dst []byte, n byte, i uint64) []byte {
+ k := uint64((1 << n) - 1)
+ if i < k {
+ return append(dst, byte(i))
+ }
+ dst = append(dst, byte(k))
+ i -= k
+ for ; i >= 128; i >>= 7 {
+ dst = append(dst, byte(0x80|(i&0x7f)))
+ }
+ return append(dst, byte(i))
+}
+
+// appendHpackString appends s, as encoded in "String Literal"
+// representation, to dst and returns the the extended buffer.
+//
+// s will be encoded in Huffman codes only when it produces strictly
+// shorter byte string.
+func appendHpackString(dst []byte, s string) []byte {
+ huffmanLength := HuffmanEncodeLength(s)
+ if huffmanLength < uint64(len(s)) {
+ first := len(dst)
+ dst = appendVarInt(dst, 7, huffmanLength)
+ dst = AppendHuffmanString(dst, s)
+ dst[first] |= 0x80
+ } else {
+ dst = appendVarInt(dst, 7, uint64(len(s)))
+ dst = append(dst, s...)
+ }
+ return dst
+}
+
+// encodeTypeByte returns type byte. If sensitive is true, type byte
+// for "Never Indexed" representation is returned. If sensitive is
+// false and indexing is true, type byte for "Incremental Indexing"
+// representation is returned. Otherwise, type byte for "Without
+// Indexing" is returned.
+func encodeTypeByte(indexing, sensitive bool) byte {
+ if sensitive {
+ return 0x10
+ }
+ if indexing {
+ return 0x40
+ }
+ return 0
+}
diff --git a/http2/hpack/encode_test.go b/http2/hpack/encode_test.go
new file mode 100644
index 0000000..dce66c9
--- /dev/null
+++ b/http2/hpack/encode_test.go
@@ -0,0 +1,331 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package hpack
+
+import (
+ "bytes"
+ "encoding/hex"
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestEncoderTableSizeUpdate(t *testing.T) {
+ tests := []struct {
+ size1, size2 uint32
+ wantHex string
+ }{
+ // Should emit 2 table size updates (2048 and 4096)
+ {2048, 4096, "3fe10f 3fe11f 82"},
+
+ // Should emit 1 table size update (2048)
+ {16384, 2048, "3fe10f 82"},
+ }
+ for _, tt := range tests {
+ var buf bytes.Buffer
+ e := NewEncoder(&buf)
+ e.SetMaxDynamicTableSize(tt.size1)
+ e.SetMaxDynamicTableSize(tt.size2)
+ if err := e.WriteField(pair(":method", "GET")); err != nil {
+ t.Fatal(err)
+ }
+ want := removeSpace(tt.wantHex)
+ if got := hex.EncodeToString(buf.Bytes()); got != want {
+ t.Errorf("e.SetDynamicTableSize %v, %v = %q; want %q", tt.size1, tt.size2, got, want)
+ }
+ }
+}
+
+func TestEncoderWriteField(t *testing.T) {
+ var buf bytes.Buffer
+ e := NewEncoder(&buf)
+ var got []HeaderField
+ d := NewDecoder(4<<10, func(f HeaderField) {
+ got = append(got, f)
+ })
+
+ tests := []struct {
+ hdrs []HeaderField
+ }{
+ {[]HeaderField{
+ pair(":method", "GET"),
+ pair(":scheme", "http"),
+ pair(":path", "/"),
+ pair(":authority", "www.example.com"),
+ }},
+ {[]HeaderField{
+ pair(":method", "GET"),
+ pair(":scheme", "http"),
+ pair(":path", "/"),
+ pair(":authority", "www.example.com"),
+ pair("cache-control", "no-cache"),
+ }},
+ {[]HeaderField{
+ pair(":method", "GET"),
+ pair(":scheme", "https"),
+ pair(":path", "/index.html"),
+ pair(":authority", "www.example.com"),
+ pair("custom-key", "custom-value"),
+ }},
+ }
+ for i, tt := range tests {
+ buf.Reset()
+ got = got[:0]
+ for _, hf := range tt.hdrs {
+ if err := e.WriteField(hf); err != nil {
+ t.Fatal(err)
+ }
+ }
+ _, err := d.Write(buf.Bytes())
+ if err != nil {
+ t.Errorf("%d. Decoder Write = %v", i, err)
+ }
+ if !reflect.DeepEqual(got, tt.hdrs) {
+ t.Errorf("%d. Decoded %+v; want %+v", i, got, tt.hdrs)
+ }
+ }
+}
+
+func TestEncoderSearchTable(t *testing.T) {
+ e := NewEncoder(nil)
+
+ e.dynTab.add(pair("foo", "bar"))
+ e.dynTab.add(pair("blake", "miz"))
+ e.dynTab.add(pair(":method", "GET"))
+
+ tests := []struct {
+ hf HeaderField
+ wantI uint64
+ wantMatch bool
+ }{
+ // Name and Value match
+ {pair("foo", "bar"), uint64(len(staticTable) + 3), true},
+ {pair("blake", "miz"), uint64(len(staticTable) + 2), true},
+ {pair(":method", "GET"), 2, true},
+
+ // Only name match because Sensitive == true
+ {HeaderField{":method", "GET", true}, 2, false},
+
+ // Only Name matches
+ {pair("foo", "..."), uint64(len(staticTable) + 3), false},
+ {pair("blake", "..."), uint64(len(staticTable) + 2), false},
+ {pair(":method", "..."), 2, false},
+
+ // None match
+ {pair("foo-", "bar"), 0, false},
+ }
+ for _, tt := range tests {
+ if gotI, gotMatch := e.searchTable(tt.hf); gotI != tt.wantI || gotMatch != tt.wantMatch {
+ t.Errorf("d.search(%+v) = %v, %v; want %v, %v", tt.hf, gotI, gotMatch, tt.wantI, tt.wantMatch)
+ }
+ }
+}
+
+func TestAppendVarInt(t *testing.T) {
+ tests := []struct {
+ n byte
+ i uint64
+ want []byte
+ }{
+ // Fits in a byte:
+ {1, 0, []byte{0}},
+ {2, 2, []byte{2}},
+ {3, 6, []byte{6}},
+ {4, 14, []byte{14}},
+ {5, 30, []byte{30}},
+ {6, 62, []byte{62}},
+ {7, 126, []byte{126}},
+ {8, 254, []byte{254}},
+
+ // Multiple bytes:
+ {5, 1337, []byte{31, 154, 10}},
+ }
+ for _, tt := range tests {
+ got := appendVarInt(nil, tt.n, tt.i)
+ if !bytes.Equal(got, tt.want) {
+ t.Errorf("appendVarInt(nil, %v, %v) = %v; want %v", tt.n, tt.i, got, tt.want)
+ }
+ }
+}
+
+func TestAppendHpackString(t *testing.T) {
+ tests := []struct {
+ s, wantHex string
+ }{
+ // Huffman encoded
+ {"www.example.com", "8c f1e3 c2e5 f23a 6ba0 ab90 f4ff"},
+
+ // Not Huffman encoded
+ {"a", "01 61"},
+
+ // zero length
+ {"", "00"},
+ }
+ for _, tt := range tests {
+ want := removeSpace(tt.wantHex)
+ buf := appendHpackString(nil, tt.s)
+ if got := hex.EncodeToString(buf); want != got {
+ t.Errorf("appendHpackString(nil, %q) = %q; want %q", tt.s, got, want)
+ }
+ }
+}
+
+func TestAppendIndexed(t *testing.T) {
+ tests := []struct {
+ i uint64
+ wantHex string
+ }{
+ // 1 byte
+ {1, "81"},
+ {126, "fe"},
+
+ // 2 bytes
+ {127, "ff00"},
+ {128, "ff01"},
+ }
+ for _, tt := range tests {
+ want := removeSpace(tt.wantHex)
+ buf := appendIndexed(nil, tt.i)
+ if got := hex.EncodeToString(buf); want != got {
+ t.Errorf("appendIndex(nil, %v) = %q; want %q", tt.i, got, want)
+ }
+ }
+}
+
+func TestAppendNewName(t *testing.T) {
+ tests := []struct {
+ f HeaderField
+ indexing bool
+ wantHex string
+ }{
+ // Incremental indexing
+ {HeaderField{"custom-key", "custom-value", false}, true, "40 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
+
+ // Without indexing
+ {HeaderField{"custom-key", "custom-value", false}, false, "00 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
+
+ // Never indexed
+ {HeaderField{"custom-key", "custom-value", true}, true, "10 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
+ {HeaderField{"custom-key", "custom-value", true}, false, "10 88 25a8 49e9 5ba9 7d7f 89 25a8 49e9 5bb8 e8b4 bf"},
+ }
+ for _, tt := range tests {
+ want := removeSpace(tt.wantHex)
+ buf := appendNewName(nil, tt.f, tt.indexing)
+ if got := hex.EncodeToString(buf); want != got {
+ t.Errorf("appendNewName(nil, %+v, %v) = %q; want %q", tt.f, tt.indexing, got, want)
+ }
+ }
+}
+
+func TestAppendIndexedName(t *testing.T) {
+ tests := []struct {
+ f HeaderField
+ i uint64
+ indexing bool
+ wantHex string
+ }{
+ // Incremental indexing
+ {HeaderField{":status", "302", false}, 8, true, "48 82 6402"},
+
+ // Without indexing
+ {HeaderField{":status", "302", false}, 8, false, "08 82 6402"},
+
+ // Never indexed
+ {HeaderField{":status", "302", true}, 8, true, "18 82 6402"},
+ {HeaderField{":status", "302", true}, 8, false, "18 82 6402"},
+ }
+ for _, tt := range tests {
+ want := removeSpace(tt.wantHex)
+ buf := appendIndexedName(nil, tt.f, tt.i, tt.indexing)
+ if got := hex.EncodeToString(buf); want != got {
+ t.Errorf("appendIndexedName(nil, %+v, %v) = %q; want %q", tt.f, tt.indexing, got, want)
+ }
+ }
+}
+
+func TestAppendTableSize(t *testing.T) {
+ tests := []struct {
+ i uint32
+ wantHex string
+ }{
+ // Fits into 1 byte
+ {30, "3e"},
+
+ // Extra byte
+ {31, "3f00"},
+ {32, "3f01"},
+ }
+ for _, tt := range tests {
+ want := removeSpace(tt.wantHex)
+ buf := appendTableSize(nil, tt.i)
+ if got := hex.EncodeToString(buf); want != got {
+ t.Errorf("appendTableSize(nil, %v) = %q; want %q", tt.i, got, want)
+ }
+ }
+}
+
+func TestEncoderSetMaxDynamicTableSize(t *testing.T) {
+ var buf bytes.Buffer
+ e := NewEncoder(&buf)
+ tests := []struct {
+ v uint32
+ wantUpdate bool
+ wantMinSize uint32
+ wantMaxSize uint32
+ }{
+ // Set new table size to 2048
+ {2048, true, 2048, 2048},
+
+ // Set new table size to 16384, but still limited to
+ // 4096
+ {16384, true, 2048, 4096},
+ }
+ for _, tt := range tests {
+ e.SetMaxDynamicTableSize(tt.v)
+ if got := e.tableSizeUpdate; tt.wantUpdate != got {
+ t.Errorf("e.tableSizeUpdate = %v; want %v", got, tt.wantUpdate)
+ }
+ if got := e.minSize; tt.wantMinSize != got {
+ t.Errorf("e.minSize = %v; want %v", got, tt.wantMinSize)
+ }
+ if got := e.dynTab.maxSize; tt.wantMaxSize != got {
+ t.Errorf("e.maxSize = %v; want %v", got, tt.wantMaxSize)
+ }
+ }
+}
+
+func TestEncoderSetMaxDynamicTableSizeLimit(t *testing.T) {
+ e := NewEncoder(nil)
+ // 4095 < initialHeaderTableSize means maxSize is truncated to
+ // 4095.
+ e.SetMaxDynamicTableSizeLimit(4095)
+ if got, want := e.dynTab.maxSize, uint32(4095); got != want {
+ t.Errorf("e.dynTab.maxSize = %v; want %v", got, want)
+ }
+ if got, want := e.maxSizeLimit, uint32(4095); got != want {
+ t.Errorf("e.maxSizeLimit = %v; want %v", got, want)
+ }
+ if got, want := e.tableSizeUpdate, true; got != want {
+ t.Errorf("e.tableSizeUpdate = %v; want %v", got, want)
+ }
+ // maxSize will be truncated to maxSizeLimit
+ e.SetMaxDynamicTableSize(16384)
+ if got, want := e.dynTab.maxSize, uint32(4095); got != want {
+ t.Errorf("e.dynTab.maxSize = %v; want %v", got, want)
+ }
+ // 8192 > current maxSizeLimit, so maxSize does not change.
+ e.SetMaxDynamicTableSizeLimit(8192)
+ if got, want := e.dynTab.maxSize, uint32(4095); got != want {
+ t.Errorf("e.dynTab.maxSize = %v; want %v", got, want)
+ }
+ if got, want := e.maxSizeLimit, uint32(8192); got != want {
+ t.Errorf("e.maxSizeLimit = %v; want %v", got, want)
+ }
+}
+
+func removeSpace(s string) string {
+ return strings.Replace(s, " ", "", -1)
+}
diff --git a/http2/hpack/hpack.go b/http2/hpack/hpack.go
new file mode 100644
index 0000000..c9e36f7
--- /dev/null
+++ b/http2/hpack/hpack.go
@@ -0,0 +1,445 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+// Package hpack implements HPACK, a compression format for
+// efficiently representing HTTP header fields in the context of HTTP/2.
+//
+// See http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-09
+package hpack
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+)
+
+// A DecodingError is something the spec defines as a decoding error.
+type DecodingError struct {
+ Err error
+}
+
+func (de DecodingError) Error() string {
+ return fmt.Sprintf("decoding error: %v", de.Err)
+}
+
+// An InvalidIndexError is returned when an encoder references a table
+// entry before the static table or after the end of the dynamic table.
+type InvalidIndexError int
+
+func (e InvalidIndexError) Error() string {
+ return fmt.Sprintf("invalid indexed representation index %d", int(e))
+}
+
+// A HeaderField is a name-value pair. Both the name and value are
+// treated as opaque sequences of octets.
+type HeaderField struct {
+ Name, Value string
+
+ // Sensitive means that this header field should never be
+ // indexed.
+ Sensitive bool
+}
+
+func (hf *HeaderField) size() uint32 {
+ // http://http2.github.io/http2-spec/compression.html#rfc.section.4.1
+ // "The size of the dynamic table is the sum of the size of
+ // its entries. The size of an entry is the sum of its name's
+ // length in octets (as defined in Section 5.2), its value's
+ // length in octets (see Section 5.2), plus 32. The size of
+ // an entry is calculated using the length of the name and
+ // value without any Huffman encoding applied."
+
+ // This can overflow if somebody makes a large HeaderField
+ // Name and/or Value by hand, but we don't care, because that
+ // won't happen on the wire because the encoding doesn't allow
+ // it.
+ return uint32(len(hf.Name) + len(hf.Value) + 32)
+}
+
+// A Decoder is the decoding context for incremental processing of
+// header blocks.
+type Decoder struct {
+ dynTab dynamicTable
+ emit func(f HeaderField)
+
+ // buf is the unparsed buffer. It's only written to
+ // saveBuf if it was truncated in the middle of a header
+ // block. Because it's usually not owned, we can only
+ // process it under Write.
+ buf []byte // usually not owned
+ saveBuf bytes.Buffer
+}
+
+func NewDecoder(maxSize uint32, emitFunc func(f HeaderField)) *Decoder {
+ d := &Decoder{
+ emit: emitFunc,
+ }
+ d.dynTab.allowedMaxSize = maxSize
+ d.dynTab.setMaxSize(maxSize)
+ return d
+}
+
+// TODO: add method *Decoder.Reset(maxSize, emitFunc) to let callers re-use Decoders and their
+// underlying buffers for garbage reasons.
+
+func (d *Decoder) SetMaxDynamicTableSize(v uint32) {
+ d.dynTab.setMaxSize(v)
+}
+
+// SetAllowedMaxDynamicTableSize sets the upper bound that the encoded
+// stream (via dynamic table size updates) may set the maximum size
+// to.
+func (d *Decoder) SetAllowedMaxDynamicTableSize(v uint32) {
+ d.dynTab.allowedMaxSize = v
+}
+
+type dynamicTable struct {
+ // ents is the FIFO described at
+ // http://http2.github.io/http2-spec/compression.html#rfc.section.2.3.2
+ // The newest (low index) is append at the end, and items are
+ // evicted from the front.
+ ents []HeaderField
+ size uint32
+ maxSize uint32 // current maxSize
+ allowedMaxSize uint32 // maxSize may go up to this, inclusive
+}
+
+func (dt *dynamicTable) setMaxSize(v uint32) {
+ dt.maxSize = v
+ dt.evict()
+}
+
+// TODO: change dynamicTable to be a struct with a slice and a size int field,
+// per http://http2.github.io/http2-spec/compression.html#rfc.section.4.1:
+//
+//
+// Then make add increment the size. maybe the max size should move from Decoder to
+// dynamicTable and add should return an ok bool if there was enough space.
+//
+// Later we'll need a remove operation on dynamicTable.
+
+func (dt *dynamicTable) add(f HeaderField) {
+ dt.ents = append(dt.ents, f)
+ dt.size += f.size()
+ dt.evict()
+}
+
+// If we're too big, evict old stuff (front of the slice)
+func (dt *dynamicTable) evict() {
+ base := dt.ents // keep base pointer of slice
+ for dt.size > dt.maxSize {
+ dt.size -= dt.ents[0].size()
+ dt.ents = dt.ents[1:]
+ }
+
+ // Shift slice contents down if we evicted things.
+ if len(dt.ents) != len(base) {
+ copy(base, dt.ents)
+ dt.ents = base[:len(dt.ents)]
+ }
+}
+
+// constantTimeStringCompare compares string a and b in a constant
+// time manner.
+func constantTimeStringCompare(a, b string) bool {
+ if len(a) != len(b) {
+ return false
+ }
+
+ c := byte(0)
+
+ for i := 0; i < len(a); i++ {
+ c |= a[i] ^ b[i]
+ }
+
+ return c == 0
+}
+
+// Search searches f in the table. The return value i is 0 if there is
+// no name match. If there is name match or name/value match, i is the
+// index of that entry (1-based). If both name and value match,
+// nameValueMatch becomes true.
+func (dt *dynamicTable) search(f HeaderField) (i uint64, nameValueMatch bool) {
+ l := len(dt.ents)
+ for j := l - 1; j >= 0; j-- {
+ ent := dt.ents[j]
+ if !constantTimeStringCompare(ent.Name, f.Name) {
+ continue
+ }
+ if i == 0 {
+ i = uint64(l - j)
+ }
+ if f.Sensitive {
+ continue
+ }
+ if !constantTimeStringCompare(ent.Value, f.Value) {
+ continue
+ }
+ i = uint64(l - j)
+ nameValueMatch = true
+ return
+ }
+ return
+}
+
+func (d *Decoder) maxTableIndex() int {
+ return len(d.dynTab.ents) + len(staticTable)
+}
+
+func (d *Decoder) at(i uint64) (hf HeaderField, ok bool) {
+ if i < 1 {
+ return
+ }
+ if i > uint64(d.maxTableIndex()) {
+ return
+ }
+ if i <= uint64(len(staticTable)) {
+ return staticTable[i-1], true
+ }
+ dents := d.dynTab.ents
+ return dents[len(dents)-(int(i)-len(staticTable))], true
+}
+
+// Decode decodes an entire block.
+//
+// TODO: remove this method and make it incremental later? This is
+// easier for debugging now.
+func (d *Decoder) DecodeFull(p []byte) ([]HeaderField, error) {
+ var hf []HeaderField
+ saveFunc := d.emit
+ defer func() { d.emit = saveFunc }()
+ d.emit = func(f HeaderField) { hf = append(hf, f) }
+ if _, err := d.Write(p); err != nil {
+ return nil, err
+ }
+ if err := d.Close(); err != nil {
+ return nil, err
+ }
+ return hf, nil
+}
+
+func (d *Decoder) Close() error {
+ if d.saveBuf.Len() > 0 {
+ d.saveBuf.Reset()
+ return DecodingError{errors.New("truncated headers")}
+ }
+ return nil
+}
+
+func (d *Decoder) Write(p []byte) (n int, err error) {
+ if len(p) == 0 {
+ // Prevent state machine CPU attacks (making us redo
+ // work up to the point of finding out we don't have
+ // enough data)
+ return
+ }
+ // Only copy the data if we have to. Optimistically assume
+ // that p will contain a complete header block.
+ if d.saveBuf.Len() == 0 {
+ d.buf = p
+ } else {
+ d.saveBuf.Write(p)
+ d.buf = d.saveBuf.Bytes()
+ d.saveBuf.Reset()
+ }
+
+ for len(d.buf) > 0 {
+ err = d.parseHeaderFieldRepr()
+ if err != nil {
+ if err == errNeedMore {
+ err = nil
+ d.saveBuf.Write(d.buf)
+ }
+ break
+ }
+ }
+
+ return len(p), err
+}
+
+// errNeedMore is an internal sentinel error value that means the
+// buffer is truncated and we need to read more data before we can
+// continue parsing.
+var errNeedMore = errors.New("need more data")
+
+type indexType int
+
+const (
+ indexedTrue indexType = iota
+ indexedFalse
+ indexedNever
+)
+
+func (v indexType) indexed() bool { return v == indexedTrue }
+func (v indexType) sensitive() bool { return v == indexedNever }
+
+// returns errNeedMore if there isn't enough data available.
+// any other error is fatal.
+// consumes d.buf iff it returns nil.
+// precondition: must be called with len(d.buf) > 0
+func (d *Decoder) parseHeaderFieldRepr() error {
+ b := d.buf[0]
+ switch {
+ case b&128 != 0:
+ // Indexed representation.
+ // High bit set?
+ // http://http2.github.io/http2-spec/compression.html#rfc.section.6.1
+ return d.parseFieldIndexed()
+ case b&192 == 64:
+ // 6.2.1 Literal Header Field with Incremental Indexing
+ // 0b10xxxxxx: top two bits are 10
+ // http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.1
+ return d.parseFieldLiteral(6, indexedTrue)
+ case b&240 == 0:
+ // 6.2.2 Literal Header Field without Indexing
+ // 0b0000xxxx: top four bits are 0000
+ // http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.2
+ return d.parseFieldLiteral(4, indexedFalse)
+ case b&240 == 16:
+ // 6.2.3 Literal Header Field never Indexed
+ // 0b0001xxxx: top four bits are 0001
+ // http://http2.github.io/http2-spec/compression.html#rfc.section.6.2.3
+ return d.parseFieldLiteral(4, indexedNever)
+ case b&224 == 32:
+ // 6.3 Dynamic Table Size Update
+ // Top three bits are '001'.
+ // http://http2.github.io/http2-spec/compression.html#rfc.section.6.3
+ return d.parseDynamicTableSizeUpdate()
+ }
+
+ return DecodingError{errors.New("invalid encoding")}
+}
+
+// (same invariants and behavior as parseHeaderFieldRepr)
+func (d *Decoder) parseFieldIndexed() error {
+ buf := d.buf
+ idx, buf, err := readVarInt(7, buf)
+ if err != nil {
+ return err
+ }
+ hf, ok := d.at(idx)
+ if !ok {
+ return DecodingError{InvalidIndexError(idx)}
+ }
+ d.emit(HeaderField{Name: hf.Name, Value: hf.Value})
+ d.buf = buf
+ return nil
+}
+
+// (same invariants and behavior as parseHeaderFieldRepr)
+func (d *Decoder) parseFieldLiteral(n uint8, it indexType) error {
+ buf := d.buf
+ nameIdx, buf, err := readVarInt(n, buf)
+ if err != nil {
+ return err
+ }
+
+ var hf HeaderField
+ if nameIdx > 0 {
+ ihf, ok := d.at(nameIdx)
+ if !ok {
+ return DecodingError{InvalidIndexError(nameIdx)}
+ }
+ hf.Name = ihf.Name
+ } else {
+ hf.Name, buf, err = readString(buf)
+ if err != nil {
+ return err
+ }
+ }
+ hf.Value, buf, err = readString(buf)
+ if err != nil {
+ return err
+ }
+ d.buf = buf
+ if it.indexed() {
+ d.dynTab.add(hf)
+ }
+ hf.Sensitive = it.sensitive()
+ d.emit(hf)
+ return nil
+}
+
+// (same invariants and behavior as parseHeaderFieldRepr)
+func (d *Decoder) parseDynamicTableSizeUpdate() error {
+ buf := d.buf
+ size, buf, err := readVarInt(5, buf)
+ if err != nil {
+ return err
+ }
+ if size > uint64(d.dynTab.allowedMaxSize) {
+ return DecodingError{errors.New("dynamic table size update too large")}
+ }
+ d.dynTab.setMaxSize(uint32(size))
+ d.buf = buf
+ return nil
+}
+
+var errVarintOverflow = DecodingError{errors.New("varint integer overflow")}
+
+// readVarInt reads an unsigned variable length integer off the
+// beginning of p. n is the parameter as described in
+// http://http2.github.io/http2-spec/compression.html#rfc.section.5.1.
+//
+// n must always be between 1 and 8.
+//
+// The returned remain buffer is either a smaller suffix of p, or err != nil.
+// The error is errNeedMore if p doesn't contain a complete integer.
+func readVarInt(n byte, p []byte) (i uint64, remain []byte, err error) {
+ if n < 1 || n > 8 {
+ panic("bad n")
+ }
+ if len(p) == 0 {
+ return 0, p, errNeedMore
+ }
+ i = uint64(p[0])
+ if n < 8 {
+ i &= (1 << uint64(n)) - 1
+ }
+ if i < (1<<uint64(n))-1 {
+ return i, p[1:], nil
+ }
+
+ origP := p
+ p = p[1:]
+ var m uint64
+ for len(p) > 0 {
+ b := p[0]
+ p = p[1:]
+ i += uint64(b&127) << m
+ if b&128 == 0 {
+ return i, p, nil
+ }
+ m += 7
+ if m >= 63 { // TODO: proper overflow check. making this up.
+ return 0, origP, errVarintOverflow
+ }
+ }
+ return 0, origP, errNeedMore
+}
+
+func readString(p []byte) (s string, remain []byte, err error) {
+ if len(p) == 0 {
+ return "", p, errNeedMore
+ }
+ isHuff := p[0]&128 != 0
+ strLen, p, err := readVarInt(7, p)
+ if err != nil {
+ return "", p, err
+ }
+ if uint64(len(p)) < strLen {
+ return "", p, errNeedMore
+ }
+ if !isHuff {
+ return string(p[:strLen]), p[strLen:], nil
+ }
+
+ // TODO: optimize this garbage:
+ var buf bytes.Buffer
+ if _, err := HuffmanDecode(&buf, p[:strLen]); err != nil {
+ return "", nil, err
+ }
+ return buf.String(), p[strLen:], nil
+}
diff --git a/http2/hpack/hpack_test.go b/http2/hpack/hpack_test.go
new file mode 100644
index 0000000..08bee41
--- /dev/null
+++ b/http2/hpack/hpack_test.go
@@ -0,0 +1,648 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package hpack
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/hex"
+ "fmt"
+ "reflect"
+ "regexp"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+func TestStaticTable(t *testing.T) {
+ fromSpec := `
+ +-------+-----------------------------+---------------+
+ | 1 | :authority | |
+ | 2 | :method | GET |
+ | 3 | :method | POST |
+ | 4 | :path | / |
+ | 5 | :path | /index.html |
+ | 6 | :scheme | http |
+ | 7 | :scheme | https |
+ | 8 | :status | 200 |
+ | 9 | :status | 204 |
+ | 10 | :status | 206 |
+ | 11 | :status | 304 |
+ | 12 | :status | 400 |
+ | 13 | :status | 404 |
+ | 14 | :status | 500 |
+ | 15 | accept-charset | |
+ | 16 | accept-encoding | gzip, deflate |
+ | 17 | accept-language | |
+ | 18 | accept-ranges | |
+ | 19 | accept | |
+ | 20 | access-control-allow-origin | |
+ | 21 | age | |
+ | 22 | allow | |
+ | 23 | authorization | |
+ | 24 | cache-control | |
+ | 25 | content-disposition | |
+ | 26 | content-encoding | |
+ | 27 | content-language | |
+ | 28 | content-length | |
+ | 29 | content-location | |
+ | 30 | content-range | |
+ | 31 | content-type | |
+ | 32 | cookie | |
+ | 33 | date | |
+ | 34 | etag | |
+ | 35 | expect | |
+ | 36 | expires | |
+ | 37 | from | |
+ | 38 | host | |
+ | 39 | if-match | |
+ | 40 | if-modified-since | |
+ | 41 | if-none-match | |
+ | 42 | if-range | |
+ | 43 | if-unmodified-since | |
+ | 44 | last-modified | |
+ | 45 | link | |
+ | 46 | location | |
+ | 47 | max-forwards | |
+ | 48 | proxy-authenticate | |
+ | 49 | proxy-authorization | |
+ | 50 | range | |
+ | 51 | referer | |
+ | 52 | refresh | |
+ | 53 | retry-after | |
+ | 54 | server | |
+ | 55 | set-cookie | |
+ | 56 | strict-transport-security | |
+ | 57 | transfer-encoding | |
+ | 58 | user-agent | |
+ | 59 | vary | |
+ | 60 | via | |
+ | 61 | www-authenticate | |
+ +-------+-----------------------------+---------------+
+`
+ bs := bufio.NewScanner(strings.NewReader(fromSpec))
+ re := regexp.MustCompile(`\| (\d+)\s+\| (\S+)\s*\| (\S(.*\S)?)?\s+\|`)
+ for bs.Scan() {
+ l := bs.Text()
+ if !strings.Contains(l, "|") {
+ continue
+ }
+ m := re.FindStringSubmatch(l)
+ if m == nil {
+ continue
+ }
+ i, err := strconv.Atoi(m[1])
+ if err != nil {
+ t.Errorf("Bogus integer on line %q", l)
+ continue
+ }
+ if i < 1 || i > len(staticTable) {
+ t.Errorf("Bogus index %d on line %q", i, l)
+ continue
+ }
+ if got, want := staticTable[i-1].Name, m[2]; got != want {
+ t.Errorf("header index %d name = %q; want %q", i, got, want)
+ }
+ if got, want := staticTable[i-1].Value, m[3]; got != want {
+ t.Errorf("header index %d value = %q; want %q", i, got, want)
+ }
+ }
+ if err := bs.Err(); err != nil {
+ t.Error(err)
+ }
+}
+
+func (d *Decoder) mustAt(idx int) HeaderField {
+ if hf, ok := d.at(uint64(idx)); !ok {
+ panic(fmt.Sprintf("bogus index %d", idx))
+ } else {
+ return hf
+ }
+}
+
+func TestDynamicTableAt(t *testing.T) {
+ d := NewDecoder(4096, nil)
+ at := d.mustAt
+ if got, want := at(2), (pair(":method", "GET")); got != want {
+ t.Errorf("at(2) = %v; want %v", got, want)
+ }
+ d.dynTab.add(pair("foo", "bar"))
+ d.dynTab.add(pair("blake", "miz"))
+ if got, want := at(len(staticTable)+1), (pair("blake", "miz")); got != want {
+ t.Errorf("at(dyn 1) = %v; want %v", got, want)
+ }
+ if got, want := at(len(staticTable)+2), (pair("foo", "bar")); got != want {
+ t.Errorf("at(dyn 2) = %v; want %v", got, want)
+ }
+ if got, want := at(3), (pair(":method", "POST")); got != want {
+ t.Errorf("at(3) = %v; want %v", got, want)
+ }
+}
+
+func TestDynamicTableSearch(t *testing.T) {
+ dt := dynamicTable{}
+ dt.setMaxSize(4096)
+
+ dt.add(pair("foo", "bar"))
+ dt.add(pair("blake", "miz"))
+ dt.add(pair(":method", "GET"))
+
+ tests := []struct {
+ hf HeaderField
+ wantI uint64
+ wantMatch bool
+ }{
+ // Name and Value match
+ {pair("foo", "bar"), 3, true},
+ {pair(":method", "GET"), 1, true},
+
+ // Only name match because of Sensitive == true
+ {HeaderField{"blake", "miz", true}, 2, false},
+
+ // Only Name matches
+ {pair("foo", "..."), 3, false},
+ {pair("blake", "..."), 2, false},
+ {pair(":method", "..."), 1, false},
+
+ // None match
+ {pair("foo-", "bar"), 0, false},
+ }
+ for _, tt := range tests {
+ if gotI, gotMatch := dt.search(tt.hf); gotI != tt.wantI || gotMatch != tt.wantMatch {
+ t.Errorf("d.search(%+v) = %v, %v; want %v, %v", tt.hf, gotI, gotMatch, tt.wantI, tt.wantMatch)
+ }
+ }
+}
+
+func TestDynamicTableSizeEvict(t *testing.T) {
+ d := NewDecoder(4096, nil)
+ if want := uint32(0); d.dynTab.size != want {
+ t.Fatalf("size = %d; want %d", d.dynTab.size, want)
+ }
+ add := d.dynTab.add
+ add(pair("blake", "eats pizza"))
+ if want := uint32(15 + 32); d.dynTab.size != want {
+ t.Fatalf("after pizza, size = %d; want %d", d.dynTab.size, want)
+ }
+ add(pair("foo", "bar"))
+ if want := uint32(15 + 32 + 6 + 32); d.dynTab.size != want {
+ t.Fatalf("after foo bar, size = %d; want %d", d.dynTab.size, want)
+ }
+ d.dynTab.setMaxSize(15 + 32 + 1 /* slop */)
+ if want := uint32(6 + 32); d.dynTab.size != want {
+ t.Fatalf("after setMaxSize, size = %d; want %d", d.dynTab.size, want)
+ }
+ if got, want := d.mustAt(len(staticTable)+1), (pair("foo", "bar")); got != want {
+ t.Errorf("at(dyn 1) = %v; want %v", got, want)
+ }
+ add(pair("long", strings.Repeat("x", 500)))
+ if want := uint32(0); d.dynTab.size != want {
+ t.Fatalf("after big one, size = %d; want %d", d.dynTab.size, want)
+ }
+}
+
+func TestDecoderDecode(t *testing.T) {
+ tests := []struct {
+ name string
+ in []byte
+ want []HeaderField
+ wantDynTab []HeaderField // newest entry first
+ }{
+ // C.2.1 Literal Header Field with Indexing
+ // http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.1
+ {"C.2.1", dehex("400a 6375 7374 6f6d 2d6b 6579 0d63 7573 746f 6d2d 6865 6164 6572"),
+ []HeaderField{pair("custom-key", "custom-header")},
+ []HeaderField{pair("custom-key", "custom-header")},
+ },
+
+ // C.2.2 Literal Header Field without Indexing
+ // http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.2
+ {"C.2.2", dehex("040c 2f73 616d 706c 652f 7061 7468"),
+ []HeaderField{pair(":path", "/sample/path")},
+ []HeaderField{}},
+
+ // C.2.3 Literal Header Field never Indexed
+ // http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.3
+ {"C.2.3", dehex("1008 7061 7373 776f 7264 0673 6563 7265 74"),
+ []HeaderField{{"password", "secret", true}},
+ []HeaderField{}},
+
+ // C.2.4 Indexed Header Field
+ // http://http2.github.io/http2-spec/compression.html#rfc.section.C.2.4
+ {"C.2.4", []byte("\x82"),
+ []HeaderField{pair(":method", "GET")},
+ []HeaderField{}},
+ }
+ for _, tt := range tests {
+ d := NewDecoder(4096, nil)
+ hf, err := d.DecodeFull(tt.in)
+ if err != nil {
+ t.Errorf("%s: %v", tt.name, err)
+ continue
+ }
+ if !reflect.DeepEqual(hf, tt.want) {
+ t.Errorf("%s: Got %v; want %v", tt.name, hf, tt.want)
+ }
+ gotDynTab := d.dynTab.reverseCopy()
+ if !reflect.DeepEqual(gotDynTab, tt.wantDynTab) {
+ t.Errorf("%s: dynamic table after = %v; want %v", tt.name, gotDynTab, tt.wantDynTab)
+ }
+ }
+}
+
+func (dt *dynamicTable) reverseCopy() (hf []HeaderField) {
+ hf = make([]HeaderField, len(dt.ents))
+ for i := range hf {
+ hf[i] = dt.ents[len(dt.ents)-1-i]
+ }
+ return
+}
+
+type encAndWant struct {
+ enc []byte
+ want []HeaderField
+ wantDynTab []HeaderField
+ wantDynSize uint32
+}
+
+// C.3 Request Examples without Huffman Coding
+// http://http2.github.io/http2-spec/compression.html#rfc.section.C.3
+func TestDecodeC3_NoHuffman(t *testing.T) {
+ testDecodeSeries(t, 4096, []encAndWant{
+ {dehex("8286 8441 0f77 7777 2e65 7861 6d70 6c65 2e63 6f6d"),
+ []HeaderField{
+ pair(":method", "GET"),
+ pair(":scheme", "http"),
+ pair(":path", "/"),
+ pair(":authority", "www.example.com"),
+ },
+ []HeaderField{
+ pair(":authority", "www.example.com"),
+ },
+ 57,
+ },
+ {dehex("8286 84be 5808 6e6f 2d63 6163 6865"),
+ []HeaderField{
+ pair(":method", "GET"),
+ pair(":scheme", "http"),
+ pair(":path", "/"),
+ pair(":authority", "www.example.com"),
+ pair("cache-control", "no-cache"),
+ },
+ []HeaderField{
+ pair("cache-control", "no-cache"),
+ pair(":authority", "www.example.com"),
+ },
+ 110,
+ },
+ {dehex("8287 85bf 400a 6375 7374 6f6d 2d6b 6579 0c63 7573 746f 6d2d 7661 6c75 65"),
+ []HeaderField{
+ pair(":method", "GET"),
+ pair(":scheme", "https"),
+ pair(":path", "/index.html"),
+ pair(":authority", "www.example.com"),
+ pair("custom-key", "custom-value"),
+ },
+ []HeaderField{
+ pair("custom-key", "custom-value"),
+ pair("cache-control", "no-cache"),
+ pair(":authority", "www.example.com"),
+ },
+ 164,
+ },
+ })
+}
+
+// C.4 Request Examples with Huffman Coding
+// http://http2.github.io/http2-spec/compression.html#rfc.section.C.4
+func TestDecodeC4_Huffman(t *testing.T) {
+ testDecodeSeries(t, 4096, []encAndWant{
+ {dehex("8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff"),
+ []HeaderField{
+ pair(":method", "GET"),
+ pair(":scheme", "http"),
+ pair(":path", "/"),
+ pair(":authority", "www.example.com"),
+ },
+ []HeaderField{
+ pair(":authority", "www.example.com"),
+ },
+ 57,
+ },
+ {dehex("8286 84be 5886 a8eb 1064 9cbf"),
+ []HeaderField{
+ pair(":method", "GET"),
+ pair(":scheme", "http"),
+ pair(":path", "/"),
+ pair(":authority", "www.example.com"),
+ pair("cache-control", "no-cache"),
+ },
+ []HeaderField{
+ pair("cache-control", "no-cache"),
+ pair(":authority", "www.example.com"),
+ },
+ 110,
+ },
+ {dehex("8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925 a849 e95b b8e8 b4bf"),
+ []HeaderField{
+ pair(":method", "GET"),
+ pair(":scheme", "https"),
+ pair(":path", "/index.html"),
+ pair(":authority", "www.example.com"),
+ pair("custom-key", "custom-value"),
+ },
+ []HeaderField{
+ pair("custom-key", "custom-value"),
+ pair("cache-control", "no-cache"),
+ pair(":authority", "www.example.com"),
+ },
+ 164,
+ },
+ })
+}
+
+// http://http2.github.io/http2-spec/compression.html#rfc.section.C.5
+// "This section shows several consecutive header lists, corresponding
+// to HTTP responses, on the same connection. The HTTP/2 setting
+// parameter SETTINGS_HEADER_TABLE_SIZE is set to the value of 256
+// octets, causing some evictions to occur."
+func TestDecodeC5_ResponsesNoHuff(t *testing.T) {
+ testDecodeSeries(t, 256, []encAndWant{
+ {dehex(`
+4803 3330 3258 0770 7269 7661 7465 611d
+4d6f 6e2c 2032 3120 4f63 7420 3230 3133
+2032 303a 3133 3a32 3120 474d 546e 1768
+7474 7073 3a2f 2f77 7777 2e65 7861 6d70
+6c65 2e63 6f6d
+`),
+ []HeaderField{
+ pair(":status", "302"),
+ pair("cache-control", "private"),
+ pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+ pair("location", "https://www.example.com"),
+ },
+ []HeaderField{
+ pair("location", "https://www.example.com"),
+ pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+ pair("cache-control", "private"),
+ pair(":status", "302"),
+ },
+ 222,
+ },
+ {dehex("4803 3330 37c1 c0bf"),
+ []HeaderField{
+ pair(":status", "307"),
+ pair("cache-control", "private"),
+ pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+ pair("location", "https://www.example.com"),
+ },
+ []HeaderField{
+ pair(":status", "307"),
+ pair("location", "https://www.example.com"),
+ pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+ pair("cache-control", "private"),
+ },
+ 222,
+ },
+ {dehex(`
+88c1 611d 4d6f 6e2c 2032 3120 4f63 7420
+3230 3133 2032 303a 3133 3a32 3220 474d
+54c0 5a04 677a 6970 7738 666f 6f3d 4153
+444a 4b48 514b 425a 584f 5157 454f 5049
+5541 5851 5745 4f49 553b 206d 6178 2d61
+6765 3d33 3630 303b 2076 6572 7369 6f6e
+3d31
+`),
+ []HeaderField{
+ pair(":status", "200"),
+ pair("cache-control", "private"),
+ pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
+ pair("location", "https://www.example.com"),
+ pair("content-encoding", "gzip"),
+ pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
+ },
+ []HeaderField{
+ pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
+ pair("content-encoding", "gzip"),
+ pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
+ },
+ 215,
+ },
+ })
+}
+
+// http://http2.github.io/http2-spec/compression.html#rfc.section.C.6
+// "This section shows the same examples as the previous section, but
+// using Huffman encoding for the literal values. The HTTP/2 setting
+// parameter SETTINGS_HEADER_TABLE_SIZE is set to the value of 256
+// octets, causing some evictions to occur. The eviction mechanism
+// uses the length of the decoded literal values, so the same
+// evictions occurs as in the previous section."
+func TestDecodeC6_ResponsesHuffman(t *testing.T) {
+ testDecodeSeries(t, 256, []encAndWant{
+ {dehex(`
+4882 6402 5885 aec3 771a 4b61 96d0 7abe
+9410 54d4 44a8 2005 9504 0b81 66e0 82a6
+2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8
+e9ae 82ae 43d3
+`),
+ []HeaderField{
+ pair(":status", "302"),
+ pair("cache-control", "private"),
+ pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+ pair("location", "https://www.example.com"),
+ },
+ []HeaderField{
+ pair("location", "https://www.example.com"),
+ pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+ pair("cache-control", "private"),
+ pair(":status", "302"),
+ },
+ 222,
+ },
+ {dehex("4883 640e ffc1 c0bf"),
+ []HeaderField{
+ pair(":status", "307"),
+ pair("cache-control", "private"),
+ pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+ pair("location", "https://www.example.com"),
+ },
+ []HeaderField{
+ pair(":status", "307"),
+ pair("location", "https://www.example.com"),
+ pair("date", "Mon, 21 Oct 2013 20:13:21 GMT"),
+ pair("cache-control", "private"),
+ },
+ 222,
+ },
+ {dehex(`
+88c1 6196 d07a be94 1054 d444 a820 0595
+040b 8166 e084 a62d 1bff c05a 839b d9ab
+77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b
+3960 d5af 2708 7f36 72c1 ab27 0fb5 291f
+9587 3160 65c0 03ed 4ee5 b106 3d50 07
+`),
+ []HeaderField{
+ pair(":status", "200"),
+ pair("cache-control", "private"),
+ pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
+ pair("location", "https://www.example.com"),
+ pair("content-encoding", "gzip"),
+ pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
+ },
+ []HeaderField{
+ pair("set-cookie", "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"),
+ pair("content-encoding", "gzip"),
+ pair("date", "Mon, 21 Oct 2013 20:13:22 GMT"),
+ },
+ 215,
+ },
+ })
+}
+
+func testDecodeSeries(t *testing.T, size uint32, steps []encAndWant) {
+ d := NewDecoder(size, nil)
+ for i, step := range steps {
+ hf, err := d.DecodeFull(step.enc)
+ if err != nil {
+ t.Fatalf("Error at step index %d: %v", i, err)
+ }
+ if !reflect.DeepEqual(hf, step.want) {
+ t.Fatalf("At step index %d: Got headers %v; want %v", i, hf, step.want)
+ }
+ gotDynTab := d.dynTab.reverseCopy()
+ if !reflect.DeepEqual(gotDynTab, step.wantDynTab) {
+ t.Errorf("After step index %d, dynamic table = %v; want %v", i, gotDynTab, step.wantDynTab)
+ }
+ if d.dynTab.size != step.wantDynSize {
+ t.Errorf("After step index %d, dynamic table size = %v; want %v", i, d.dynTab.size, step.wantDynSize)
+ }
+ }
+}
+
+func TestHuffmanDecode(t *testing.T) {
+ tests := []struct {
+ inHex, want string
+ }{
+ {"f1e3 c2e5 f23a 6ba0 ab90 f4ff", "www.example.com"},
+ {"a8eb 1064 9cbf", "no-cache"},
+ {"25a8 49e9 5ba9 7d7f", "custom-key"},
+ {"25a8 49e9 5bb8 e8b4 bf", "custom-value"},
+ {"6402", "302"},
+ {"aec3 771a 4b", "private"},
+ {"d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff", "Mon, 21 Oct 2013 20:13:21 GMT"},
+ {"9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3", "https://www.example.com"},
+ {"9bd9 ab", "gzip"},
+ {"94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 3160 65c0 03ed 4ee5 b106 3d50 07",
+ "foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1"},
+ }
+ for i, tt := range tests {
+ var buf bytes.Buffer
+ in, err := hex.DecodeString(strings.Replace(tt.inHex, " ", "", -1))
+ if err != nil {
+ t.Errorf("%d. hex input error: %v", i, err)
+ continue
+ }
+ if _, err := HuffmanDecode(&buf, in); err != nil {
+ t.Errorf("%d. decode error: %v", i, err)
+ continue
+ }
+ if got := buf.String(); tt.want != got {
+ t.Errorf("%d. decode = %q; want %q", i, got, tt.want)
+ }
+ }
+}
+
+func TestAppendHuffmanString(t *testing.T) {
+ tests := []struct {
+ in, want string
+ }{
+ {"www.example.com", "f1e3 c2e5 f23a 6ba0 ab90 f4ff"},
+ {"no-cache", "a8eb 1064 9cbf"},
+ {"custom-key", "25a8 49e9 5ba9 7d7f"},
+ {"custom-value", "25a8 49e9 5bb8 e8b4 bf"},
+ {"302", "6402"},
+ {"private", "aec3 771a 4b"},
+ {"Mon, 21 Oct 2013 20:13:21 GMT", "d07a be94 1054 d444 a820 0595 040b 8166 e082 a62d 1bff"},
+ {"https://www.example.com", "9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 d3"},
+ {"gzip", "9bd9 ab"},
+ {"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+ "94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 3160 65c0 03ed 4ee5 b106 3d50 07"},
+ }
+ for i, tt := range tests {
+ buf := []byte{}
+ want := strings.Replace(tt.want, " ", "", -1)
+ buf = AppendHuffmanString(buf, tt.in)
+ if got := hex.EncodeToString(buf); want != got {
+ t.Errorf("%d. encode = %q; want %q", i, got, want)
+ }
+ }
+}
+
+func TestReadVarInt(t *testing.T) {
+ type res struct {
+ i uint64
+ consumed int
+ err error
+ }
+ tests := []struct {
+ n byte
+ p []byte
+ want res
+ }{
+ // Fits in a byte:
+ {1, []byte{0}, res{0, 1, nil}},
+ {2, []byte{2}, res{2, 1, nil}},
+ {3, []byte{6}, res{6, 1, nil}},
+ {4, []byte{14}, res{14, 1, nil}},
+ {5, []byte{30}, res{30, 1, nil}},
+ {6, []byte{62}, res{62, 1, nil}},
+ {7, []byte{126}, res{126, 1, nil}},
+ {8, []byte{254}, res{254, 1, nil}},
+
+ // Doesn't fit in a byte:
+ {1, []byte{1}, res{0, 0, errNeedMore}},
+ {2, []byte{3}, res{0, 0, errNeedMore}},
+ {3, []byte{7}, res{0, 0, errNeedMore}},
+ {4, []byte{15}, res{0, 0, errNeedMore}},
+ {5, []byte{31}, res{0, 0, errNeedMore}},
+ {6, []byte{63}, res{0, 0, errNeedMore}},
+ {7, []byte{127}, res{0, 0, errNeedMore}},
+ {8, []byte{255}, res{0, 0, errNeedMore}},
+
+ // Ignoring top bits:
+ {5, []byte{255, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 111
+ {5, []byte{159, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 100
+ {5, []byte{191, 154, 10}, res{1337, 3, nil}}, // high dummy three bits: 101
+
+ // Extra byte:
+ {5, []byte{191, 154, 10, 2}, res{1337, 3, nil}}, // extra byte
+
+ // Short a byte:
+ {5, []byte{191, 154}, res{0, 0, errNeedMore}},
+
+ // integer overflow:
+ {1, []byte{255, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}, res{0, 0, errVarintOverflow}},
+ }
+ for _, tt := range tests {
+ i, remain, err := readVarInt(tt.n, tt.p)
+ consumed := len(tt.p) - len(remain)
+ got := res{i, consumed, err}
+ if got != tt.want {
+ t.Errorf("readVarInt(%d, %v ~ %x) = %+v; want %+v", tt.n, tt.p, tt.p, got, tt.want)
+ }
+ }
+}
+
+func dehex(s string) []byte {
+ s = strings.Replace(s, " ", "", -1)
+ s = strings.Replace(s, "\n", "", -1)
+ b, err := hex.DecodeString(s)
+ if err != nil {
+ panic(err)
+ }
+ return b
+}
diff --git a/http2/hpack/huffman.go b/http2/hpack/huffman.go
new file mode 100644
index 0000000..9fe76f6
--- /dev/null
+++ b/http2/hpack/huffman.go
@@ -0,0 +1,159 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package hpack
+
+import (
+ "bytes"
+ "io"
+ "sync"
+)
+
+var bufPool = sync.Pool{
+ New: func() interface{} { return new(bytes.Buffer) },
+}
+
+// HuffmanDecode decodes the string in v and writes the expanded
+// result to w, returning the number of bytes written to w and the
+// Write call's return value. At most one Write call is made.
+func HuffmanDecode(w io.Writer, v []byte) (int, error) {
+ buf := bufPool.Get().(*bytes.Buffer)
+ buf.Reset()
+ defer bufPool.Put(buf)
+
+ n := rootHuffmanNode
+ cur, nbits := uint(0), uint8(0)
+ for _, b := range v {
+ cur = cur<<8 | uint(b)
+ nbits += 8
+ for nbits >= 8 {
+ n = n.children[byte(cur>>(nbits-8))]
+ if n.children == nil {
+ buf.WriteByte(n.sym)
+ nbits -= n.codeLen
+ n = rootHuffmanNode
+ } else {
+ nbits -= 8
+ }
+ }
+ }
+ for nbits > 0 {
+ n = n.children[byte(cur<<(8-nbits))]
+ if n.children != nil || n.codeLen > nbits {
+ break
+ }
+ buf.WriteByte(n.sym)
+ nbits -= n.codeLen
+ n = rootHuffmanNode
+ }
+ return w.Write(buf.Bytes())
+}
+
+type node struct {
+ // children is non-nil for internal nodes
+ children []*node
+
+ // The following are only valid if children is nil:
+ codeLen uint8 // number of bits that led to the output of sym
+ sym byte // output symbol
+}
+
+func newInternalNode() *node {
+ return &node{children: make([]*node, 256)}
+}
+
+var rootHuffmanNode = newInternalNode()
+
+func init() {
+ for i, code := range huffmanCodes {
+ if i > 255 {
+ panic("too many huffman codes")
+ }
+ addDecoderNode(byte(i), code, huffmanCodeLen[i])
+ }
+}
+
+func addDecoderNode(sym byte, code uint32, codeLen uint8) {
+ cur := rootHuffmanNode
+ for codeLen > 8 {
+ codeLen -= 8
+ i := uint8(code >> codeLen)
+ if cur.children[i] == nil {
+ cur.children[i] = newInternalNode()
+ }
+ cur = cur.children[i]
+ }
+ shift := 8 - codeLen
+ start, end := int(uint8(code<<shift)), int(1<<shift)
+ for i := start; i < start+end; i++ {
+ cur.children[i] = &node{sym: sym, codeLen: codeLen}
+ }
+}
+
+// AppendHuffmanString appends s, as encoded in Huffman codes, to dst
+// and returns the extended buffer.
+func AppendHuffmanString(dst []byte, s string) []byte {
+ rembits := uint8(8)
+
+ for i := 0; i < len(s); i++ {
+ if rembits == 8 {
+ dst = append(dst, 0)
+ }
+ dst, rembits = appendByteToHuffmanCode(dst, rembits, s[i])
+ }
+
+ if rembits < 8 {
+ // special EOS symbol
+ code := uint32(0x3fffffff)
+ nbits := uint8(30)
+
+ t := uint8(code >> (nbits - rembits))
+ dst[len(dst)-1] |= t
+ }
+
+ return dst
+}
+
+// HuffmanEncodeLength returns the number of bytes required to encode
+// s in Huffman codes. The result is round up to byte boundary.
+func HuffmanEncodeLength(s string) uint64 {
+ n := uint64(0)
+ for i := 0; i < len(s); i++ {
+ n += uint64(huffmanCodeLen[s[i]])
+ }
+ return (n + 7) / 8
+}
+
+// appendByteToHuffmanCode appends Huffman code for c to dst and
+// returns the extended buffer and the remaining bits in the last
+// element. The appending is not byte aligned and the remaining bits
+// in the last element of dst is given in rembits.
+func appendByteToHuffmanCode(dst []byte, rembits uint8, c byte) ([]byte, uint8) {
+ code := huffmanCodes[c]
+ nbits := huffmanCodeLen[c]
+
+ for {
+ if rembits > nbits {
+ t := uint8(code << (rembits - nbits))
+ dst[len(dst)-1] |= t
+ rembits -= nbits
+ break
+ }
+
+ t := uint8(code >> (nbits - rembits))
+ dst[len(dst)-1] |= t
+
+ nbits -= rembits
+ rembits = 8
+
+ if nbits == 0 {
+ break
+ }
+
+ dst = append(dst, 0)
+ }
+
+ return dst, rembits
+}
diff --git a/http2/hpack/tables.go b/http2/hpack/tables.go
new file mode 100644
index 0000000..f898e25
--- /dev/null
+++ b/http2/hpack/tables.go
@@ -0,0 +1,353 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package hpack
+
+func pair(name, value string) HeaderField {
+ return HeaderField{Name: name, Value: value}
+}
+
+// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-07#appendix-B
+var staticTable = []HeaderField{
+ pair(":authority", ""), // index 1 (1-based)
+ pair(":method", "GET"),
+ pair(":method", "POST"),
+ pair(":path", "/"),
+ pair(":path", "/index.html"),
+ pair(":scheme", "http"),
+ pair(":scheme", "https"),
+ pair(":status", "200"),
+ pair(":status", "204"),
+ pair(":status", "206"),
+ pair(":status", "304"),
+ pair(":status", "400"),
+ pair(":status", "404"),
+ pair(":status", "500"),
+ pair("accept-charset", ""),
+ pair("accept-encoding", "gzip, deflate"),
+ pair("accept-language", ""),
+ pair("accept-ranges", ""),
+ pair("accept", ""),
+ pair("access-control-allow-origin", ""),
+ pair("age", ""),
+ pair("allow", ""),
+ pair("authorization", ""),
+ pair("cache-control", ""),
+ pair("content-disposition", ""),
+ pair("content-encoding", ""),
+ pair("content-language", ""),
+ pair("content-length", ""),
+ pair("content-location", ""),
+ pair("content-range", ""),
+ pair("content-type", ""),
+ pair("cookie", ""),
+ pair("date", ""),
+ pair("etag", ""),
+ pair("expect", ""),
+ pair("expires", ""),
+ pair("from", ""),
+ pair("host", ""),
+ pair("if-match", ""),
+ pair("if-modified-since", ""),
+ pair("if-none-match", ""),
+ pair("if-range", ""),
+ pair("if-unmodified-since", ""),
+ pair("last-modified", ""),
+ pair("link", ""),
+ pair("location", ""),
+ pair("max-forwards", ""),
+ pair("proxy-authenticate", ""),
+ pair("proxy-authorization", ""),
+ pair("range", ""),
+ pair("referer", ""),
+ pair("refresh", ""),
+ pair("retry-after", ""),
+ pair("server", ""),
+ pair("set-cookie", ""),
+ pair("strict-transport-security", ""),
+ pair("transfer-encoding", ""),
+ pair("user-agent", ""),
+ pair("vary", ""),
+ pair("via", ""),
+ pair("www-authenticate", ""),
+}
+
+var huffmanCodes = []uint32{
+ 0x1ff8,
+ 0x7fffd8,
+ 0xfffffe2,
+ 0xfffffe3,
+ 0xfffffe4,
+ 0xfffffe5,
+ 0xfffffe6,
+ 0xfffffe7,
+ 0xfffffe8,
+ 0xffffea,
+ 0x3ffffffc,
+ 0xfffffe9,
+ 0xfffffea,
+ 0x3ffffffd,
+ 0xfffffeb,
+ 0xfffffec,
+ 0xfffffed,
+ 0xfffffee,
+ 0xfffffef,
+ 0xffffff0,
+ 0xffffff1,
+ 0xffffff2,
+ 0x3ffffffe,
+ 0xffffff3,
+ 0xffffff4,
+ 0xffffff5,
+ 0xffffff6,
+ 0xffffff7,
+ 0xffffff8,
+ 0xffffff9,
+ 0xffffffa,
+ 0xffffffb,
+ 0x14,
+ 0x3f8,
+ 0x3f9,
+ 0xffa,
+ 0x1ff9,
+ 0x15,
+ 0xf8,
+ 0x7fa,
+ 0x3fa,
+ 0x3fb,
+ 0xf9,
+ 0x7fb,
+ 0xfa,
+ 0x16,
+ 0x17,
+ 0x18,
+ 0x0,
+ 0x1,
+ 0x2,
+ 0x19,
+ 0x1a,
+ 0x1b,
+ 0x1c,
+ 0x1d,
+ 0x1e,
+ 0x1f,
+ 0x5c,
+ 0xfb,
+ 0x7ffc,
+ 0x20,
+ 0xffb,
+ 0x3fc,
+ 0x1ffa,
+ 0x21,
+ 0x5d,
+ 0x5e,
+ 0x5f,
+ 0x60,
+ 0x61,
+ 0x62,
+ 0x63,
+ 0x64,
+ 0x65,
+ 0x66,
+ 0x67,
+ 0x68,
+ 0x69,
+ 0x6a,
+ 0x6b,
+ 0x6c,
+ 0x6d,
+ 0x6e,
+ 0x6f,
+ 0x70,
+ 0x71,
+ 0x72,
+ 0xfc,
+ 0x73,
+ 0xfd,
+ 0x1ffb,
+ 0x7fff0,
+ 0x1ffc,
+ 0x3ffc,
+ 0x22,
+ 0x7ffd,
+ 0x3,
+ 0x23,
+ 0x4,
+ 0x24,
+ 0x5,
+ 0x25,
+ 0x26,
+ 0x27,
+ 0x6,
+ 0x74,
+ 0x75,
+ 0x28,
+ 0x29,
+ 0x2a,
+ 0x7,
+ 0x2b,
+ 0x76,
+ 0x2c,
+ 0x8,
+ 0x9,
+ 0x2d,
+ 0x77,
+ 0x78,
+ 0x79,
+ 0x7a,
+ 0x7b,
+ 0x7ffe,
+ 0x7fc,
+ 0x3ffd,
+ 0x1ffd,
+ 0xffffffc,
+ 0xfffe6,
+ 0x3fffd2,
+ 0xfffe7,
+ 0xfffe8,
+ 0x3fffd3,
+ 0x3fffd4,
+ 0x3fffd5,
+ 0x7fffd9,
+ 0x3fffd6,
+ 0x7fffda,
+ 0x7fffdb,
+ 0x7fffdc,
+ 0x7fffdd,
+ 0x7fffde,
+ 0xffffeb,
+ 0x7fffdf,
+ 0xffffec,
+ 0xffffed,
+ 0x3fffd7,
+ 0x7fffe0,
+ 0xffffee,
+ 0x7fffe1,
+ 0x7fffe2,
+ 0x7fffe3,
+ 0x7fffe4,
+ 0x1fffdc,
+ 0x3fffd8,
+ 0x7fffe5,
+ 0x3fffd9,
+ 0x7fffe6,
+ 0x7fffe7,
+ 0xffffef,
+ 0x3fffda,
+ 0x1fffdd,
+ 0xfffe9,
+ 0x3fffdb,
+ 0x3fffdc,
+ 0x7fffe8,
+ 0x7fffe9,
+ 0x1fffde,
+ 0x7fffea,
+ 0x3fffdd,
+ 0x3fffde,
+ 0xfffff0,
+ 0x1fffdf,
+ 0x3fffdf,
+ 0x7fffeb,
+ 0x7fffec,
+ 0x1fffe0,
+ 0x1fffe1,
+ 0x3fffe0,
+ 0x1fffe2,
+ 0x7fffed,
+ 0x3fffe1,
+ 0x7fffee,
+ 0x7fffef,
+ 0xfffea,
+ 0x3fffe2,
+ 0x3fffe3,
+ 0x3fffe4,
+ 0x7ffff0,
+ 0x3fffe5,
+ 0x3fffe6,
+ 0x7ffff1,
+ 0x3ffffe0,
+ 0x3ffffe1,
+ 0xfffeb,
+ 0x7fff1,
+ 0x3fffe7,
+ 0x7ffff2,
+ 0x3fffe8,
+ 0x1ffffec,
+ 0x3ffffe2,
+ 0x3ffffe3,
+ 0x3ffffe4,
+ 0x7ffffde,
+ 0x7ffffdf,
+ 0x3ffffe5,
+ 0xfffff1,
+ 0x1ffffed,
+ 0x7fff2,
+ 0x1fffe3,
+ 0x3ffffe6,
+ 0x7ffffe0,
+ 0x7ffffe1,
+ 0x3ffffe7,
+ 0x7ffffe2,
+ 0xfffff2,
+ 0x1fffe4,
+ 0x1fffe5,
+ 0x3ffffe8,
+ 0x3ffffe9,
+ 0xffffffd,
+ 0x7ffffe3,
+ 0x7ffffe4,
+ 0x7ffffe5,
+ 0xfffec,
+ 0xfffff3,
+ 0xfffed,
+ 0x1fffe6,
+ 0x3fffe9,
+ 0x1fffe7,
+ 0x1fffe8,
+ 0x7ffff3,
+ 0x3fffea,
+ 0x3fffeb,
+ 0x1ffffee,
+ 0x1ffffef,
+ 0xfffff4,
+ 0xfffff5,
+ 0x3ffffea,
+ 0x7ffff4,
+ 0x3ffffeb,
+ 0x7ffffe6,
+ 0x3ffffec,
+ 0x3ffffed,
+ 0x7ffffe7,
+ 0x7ffffe8,
+ 0x7ffffe9,
+ 0x7ffffea,
+ 0x7ffffeb,
+ 0xffffffe,
+ 0x7ffffec,
+ 0x7ffffed,
+ 0x7ffffee,
+ 0x7ffffef,
+ 0x7fffff0,
+ 0x3ffffee,
+}
+
+var huffmanCodeLen = []uint8{
+ 13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28,
+ 28, 28, 28, 28, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 28,
+ 6, 10, 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6,
+ 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10,
+ 13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
+ 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6,
+ 15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6, 6, 6, 5,
+ 6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 15, 11, 14, 13, 28,
+ 20, 22, 20, 20, 22, 22, 22, 23, 22, 23, 23, 23, 23, 23, 24, 23,
+ 24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23, 24,
+ 22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, 23,
+ 21, 21, 22, 21, 23, 22, 23, 23, 20, 22, 22, 22, 23, 22, 22, 23,
+ 26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27, 26, 24, 25,
+ 19, 21, 26, 27, 27, 26, 27, 24, 21, 21, 26, 26, 28, 27, 27, 27,
+ 20, 24, 20, 21, 22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23,
+ 26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27, 27, 27, 27, 27, 26,
+}
diff --git a/http2/http2.go b/http2/http2.go
new file mode 100644
index 0000000..35f9b26
--- /dev/null
+++ b/http2/http2.go
@@ -0,0 +1,249 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+// Package http2 implements the HTTP/2 protocol.
+//
+// This is a work in progress. This package is low-level and intended
+// to be used directly by very few people. Most users will use it
+// indirectly through integration with the net/http package. See
+// ConfigureServer. That ConfigureServer call will likely be automatic
+// or available via an empty import in the future.
+//
+// See http://http2.github.io/
+package http2
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "net/http"
+ "strconv"
+ "sync"
+)
+
+var VerboseLogs = false
+
+const (
+ // ClientPreface is the string that must be sent by new
+ // connections from clients.
+ ClientPreface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
+
+ // SETTINGS_MAX_FRAME_SIZE default
+ // http://http2.github.io/http2-spec/#rfc.section.6.5.2
+ initialMaxFrameSize = 16384
+
+ // NextProtoTLS is the NPN/ALPN protocol negotiated during
+ // HTTP/2's TLS setup.
+ NextProtoTLS = "h2"
+
+ // http://http2.github.io/http2-spec/#SettingValues
+ initialHeaderTableSize = 4096
+
+ initialWindowSize = 65535 // 6.9.2 Initial Flow Control Window Size
+
+ defaultMaxReadFrameSize = 1 << 20
+)
+
+var (
+ clientPreface = []byte(ClientPreface)
+)
+
+type streamState int
+
+const (
+ stateIdle streamState = iota
+ stateOpen
+ stateHalfClosedLocal
+ stateHalfClosedRemote
+ stateResvLocal
+ stateResvRemote
+ stateClosed
+)
+
+var stateName = [...]string{
+ stateIdle: "Idle",
+ stateOpen: "Open",
+ stateHalfClosedLocal: "HalfClosedLocal",
+ stateHalfClosedRemote: "HalfClosedRemote",
+ stateResvLocal: "ResvLocal",
+ stateResvRemote: "ResvRemote",
+ stateClosed: "Closed",
+}
+
+func (st streamState) String() string {
+ 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", uint16(s))
+}
+
+func validHeader(v string) bool {
+ if len(v) == 0 {
+ return false
+ }
+ for _, r := range v {
+ // "Just as in HTTP/1.x, header field names are
+ // strings of ASCII characters that are compared in a
+ // case-insensitive fashion. However, header field
+ // names MUST be converted to lowercase prior to their
+ // encoding in HTTP/2. "
+ if r >= 127 || ('A' <= r && r <= 'Z') {
+ return false
+ }
+ }
+ return true
+}
+
+var httpCodeStringCommon = map[int]string{} // n -> strconv.Itoa(n)
+
+func init() {
+ for i := 100; i <= 999; i++ {
+ if v := http.StatusText(i); v != "" {
+ httpCodeStringCommon[i] = strconv.Itoa(i)
+ }
+ }
+}
+
+func httpCodeString(code int) string {
+ if s, ok := httpCodeStringCommon[code]; ok {
+ return s
+ }
+ return strconv.Itoa(code)
+}
+
+// from pkg io
+type stringWriter interface {
+ WriteString(s string) (n int, err error)
+}
+
+// A gate lets two goroutines coordinate their activities.
+type gate chan struct{}
+
+func (g gate) Done() { g <- struct{}{} }
+func (g gate) Wait() { <-g }
+
+// A closeWaiter is like a sync.WaitGroup but only goes 1 to 0 (open to closed).
+type closeWaiter chan struct{}
+
+// Init makes a closeWaiter usable.
+// It exists because so a closeWaiter value can be placed inside a
+// larger struct and have the Mutex and Cond's memory in the same
+// allocation.
+func (cw *closeWaiter) Init() {
+ *cw = make(chan struct{})
+}
+
+// Close marks the closeWaiter as closed and unblocks any waiters.
+func (cw closeWaiter) Close() {
+ close(cw)
+}
+
+// Wait waits for the closeWaiter to become closed.
+func (cw closeWaiter) Wait() {
+ <-cw
+}
+
+// bufferedWriter is a buffered writer that writes to w.
+// Its buffered writer is lazily allocated as needed, to minimize
+// idle memory usage with many connections.
+type bufferedWriter struct {
+ w io.Writer // immutable
+ bw *bufio.Writer // non-nil when data is buffered
+}
+
+func newBufferedWriter(w io.Writer) *bufferedWriter {
+ return &bufferedWriter{w: w}
+}
+
+var bufWriterPool = sync.Pool{
+ New: func() interface{} {
+ // TODO: pick something better? this is a bit under
+ // (3 x typical 1500 byte MTU) at least.
+ return bufio.NewWriterSize(nil, 4<<10)
+ },
+}
+
+func (w *bufferedWriter) Write(p []byte) (n int, err error) {
+ if w.bw == nil {
+ bw := bufWriterPool.Get().(*bufio.Writer)
+ bw.Reset(w.w)
+ w.bw = bw
+ }
+ return w.bw.Write(p)
+}
+
+func (w *bufferedWriter) Flush() error {
+ bw := w.bw
+ if bw == nil {
+ return nil
+ }
+ err := bw.Flush()
+ bw.Reset(nil)
+ bufWriterPool.Put(bw)
+ w.bw = nil
+ return err
+}
diff --git a/http2/http2_test.go b/http2/http2_test.go
new file mode 100644
index 0000000..55407d6
--- /dev/null
+++ b/http2/http2_test.go
@@ -0,0 +1,152 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "bytes"
+ "errors"
+ "flag"
+ "fmt"
+ "net/http"
+ "os/exec"
+ "strconv"
+ "strings"
+ "testing"
+
+ "golang.org/x/net/http2/hpack"
+)
+
+var knownFailing = flag.Bool("known_failing", false, "Run known-failing tests.")
+
+func condSkipFailingTest(t *testing.T) {
+ if !*knownFailing {
+ t.Skip("Skipping known-failing test without --known_failing")
+ }
+}
+
+func init() {
+ DebugGoroutines = true
+ 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]"},
+ {Setting{1<<16 - 1, 123}, "[UNKNOWN_SETTING_65535 = 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
+ st *serverTester // optional
+}
+
+func (w twriter) Write(p []byte) (n int, err error) {
+ if w.st != nil {
+ ps := string(p)
+ for _, phrase := range w.st.logFilter {
+ if strings.Contains(ps, phrase) {
+ return len(p), nil // no logging
+ }
+ }
+ }
+ w.t.Logf("%s", p)
+ return len(p), nil
+}
+
+// like encodeHeader, but don't add implicit psuedo headers.
+func encodeHeaderNoImplicit(t *testing.T, headers ...string) []byte {
+ var buf bytes.Buffer
+ enc := hpack.NewEncoder(&buf)
+ for len(headers) > 0 {
+ k, v := headers[0], headers[1]
+ headers = headers[2:]
+ if err := enc.WriteField(hpack.HeaderField{Name: k, Value: v}); err != nil {
+ t.Fatalf("HPACK encoding error for %q/%q: %v", k, v, err)
+ }
+ }
+ return buf.Bytes()
+}
+
+// Verify that curl has http2.
+func requireCurl(t *testing.T) {
+ out, err := dockerLogs(curl(t, "--version"))
+ if err != nil {
+ t.Skipf("failed to determine curl features; skipping test")
+ }
+ if !strings.Contains(string(out), "HTTP2") {
+ t.Skip("curl doesn't support HTTP2; skipping test")
+ }
+}
+
+func curl(t *testing.T, args ...string) (container string) {
+ out, err := exec.Command("docker", append([]string{"run", "-d", "--net=host", "gohttp2/curl"}, args...)...).CombinedOutput()
+ if err != nil {
+ t.Skipf("Failed to run curl in docker: %v, %s", err, out)
+ }
+ return strings.TrimSpace(string(out))
+}
+
+type puppetCommand struct {
+ fn func(w http.ResponseWriter, r *http.Request)
+ done chan<- bool
+}
+
+type handlerPuppet struct {
+ ch chan puppetCommand
+}
+
+func newHandlerPuppet() *handlerPuppet {
+ return &handlerPuppet{
+ ch: make(chan puppetCommand),
+ }
+}
+
+func (p *handlerPuppet) act(w http.ResponseWriter, r *http.Request) {
+ for cmd := range p.ch {
+ cmd.fn(w, r)
+ cmd.done <- true
+ }
+}
+
+func (p *handlerPuppet) done() { close(p.ch) }
+func (p *handlerPuppet) do(fn func(http.ResponseWriter, *http.Request)) {
+ done := make(chan bool)
+ p.ch <- puppetCommand{fn, done}
+ <-done
+}
+func dockerLogs(container string) ([]byte, error) {
+ out, err := exec.Command("docker", "wait", container).CombinedOutput()
+ if err != nil {
+ return out, err
+ }
+ exitStatus, err := strconv.Atoi(strings.TrimSpace(string(out)))
+ if err != nil {
+ return out, errors.New("unexpected exit status from docker wait")
+ }
+ out, err = exec.Command("docker", "logs", container).CombinedOutput()
+ exec.Command("docker", "rm", container).Run()
+ if err == nil && exitStatus != 0 {
+ err = fmt.Errorf("exit status %d: %s", exitStatus, out)
+ }
+ return out, err
+}
+
+func kill(container string) {
+ exec.Command("docker", "kill", container).Run()
+ exec.Command("docker", "rm", container).Run()
+}
diff --git a/http2/pipe.go b/http2/pipe.go
new file mode 100644
index 0000000..ce9aad5
--- /dev/null
+++ b/http2/pipe.go
@@ -0,0 +1,43 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "sync"
+)
+
+type pipe struct {
+ b buffer
+ c sync.Cond
+ m sync.Mutex
+}
+
+// Read waits until data is available and copies bytes
+// from the buffer into p.
+func (r *pipe) Read(p []byte) (n int, err error) {
+ r.c.L.Lock()
+ defer r.c.L.Unlock()
+ for r.b.Len() == 0 && !r.b.closed {
+ r.c.Wait()
+ }
+ return r.b.Read(p)
+}
+
+// Write copies bytes from p into the buffer and wakes a reader.
+// It is an error to write more data than the buffer can hold.
+func (w *pipe) Write(p []byte) (n int, err error) {
+ w.c.L.Lock()
+ defer w.c.L.Unlock()
+ defer w.c.Signal()
+ return w.b.Write(p)
+}
+
+func (c *pipe) Close(err error) {
+ c.c.L.Lock()
+ defer c.c.L.Unlock()
+ defer c.c.Signal()
+ c.b.Close(err)
+}
diff --git a/http2/pipe_test.go b/http2/pipe_test.go
new file mode 100644
index 0000000..10c4c32
--- /dev/null
+++ b/http2/pipe_test.go
@@ -0,0 +1,24 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "errors"
+ "testing"
+)
+
+func TestPipeClose(t *testing.T) {
+ var p pipe
+ p.c.L = &p.m
+ a := errors.New("a")
+ b := errors.New("b")
+ p.Close(a)
+ p.Close(b)
+ _, err := p.Read(make([]byte, 1))
+ if err != a {
+ t.Errorf("err = %v want %v", err, a)
+ }
+}
diff --git a/http2/priority_test.go b/http2/priority_test.go
new file mode 100644
index 0000000..d9648fd
--- /dev/null
+++ b/http2/priority_test.go
@@ -0,0 +1,121 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "testing"
+)
+
+func TestPriority(t *testing.T) {
+ // A -> B
+ // move A's parent to B
+ streams := make(map[uint32]*stream)
+ a := &stream{
+ parent: nil,
+ weight: 16,
+ }
+ streams[1] = a
+ b := &stream{
+ parent: a,
+ weight: 16,
+ }
+ streams[2] = b
+ adjustStreamPriority(streams, 1, PriorityParam{
+ Weight: 20,
+ StreamDep: 2,
+ })
+ if a.parent != b {
+ t.Errorf("Expected A's parent to be B")
+ }
+ if a.weight != 20 {
+ t.Errorf("Expected A's weight to be 20; got %d", a.weight)
+ }
+ if b.parent != nil {
+ t.Errorf("Expected B to have no parent")
+ }
+ if b.weight != 16 {
+ t.Errorf("Expected B's weight to be 16; got %d", b.weight)
+ }
+}
+
+func TestPriorityExclusiveZero(t *testing.T) {
+ // A B and C are all children of the 0 stream.
+ // Exclusive reprioritization to any of the streams
+ // should bring the rest of the streams under the
+ // reprioritized stream
+ streams := make(map[uint32]*stream)
+ a := &stream{
+ parent: nil,
+ weight: 16,
+ }
+ streams[1] = a
+ b := &stream{
+ parent: nil,
+ weight: 16,
+ }
+ streams[2] = b
+ c := &stream{
+ parent: nil,
+ weight: 16,
+ }
+ streams[3] = c
+ adjustStreamPriority(streams, 3, PriorityParam{
+ Weight: 20,
+ StreamDep: 0,
+ Exclusive: true,
+ })
+ if a.parent != c {
+ t.Errorf("Expected A's parent to be C")
+ }
+ if a.weight != 16 {
+ t.Errorf("Expected A's weight to be 16; got %d", a.weight)
+ }
+ if b.parent != c {
+ t.Errorf("Expected B's parent to be C")
+ }
+ if b.weight != 16 {
+ t.Errorf("Expected B's weight to be 16; got %d", b.weight)
+ }
+ if c.parent != nil {
+ t.Errorf("Expected C to have no parent")
+ }
+ if c.weight != 20 {
+ t.Errorf("Expected C's weight to be 20; got %d", b.weight)
+ }
+}
+
+func TestPriorityOwnParent(t *testing.T) {
+ streams := make(map[uint32]*stream)
+ a := &stream{
+ parent: nil,
+ weight: 16,
+ }
+ streams[1] = a
+ b := &stream{
+ parent: a,
+ weight: 16,
+ }
+ streams[2] = b
+ adjustStreamPriority(streams, 1, PriorityParam{
+ Weight: 20,
+ StreamDep: 1,
+ })
+ if a.parent != nil {
+ t.Errorf("Expected A's parent to be nil")
+ }
+ if a.weight != 20 {
+ t.Errorf("Expected A's weight to be 20; got %d", a.weight)
+ }
+ if b.parent != a {
+ t.Errorf("Expected B's parent to be A")
+ }
+ if b.weight != 16 {
+ t.Errorf("Expected B's weight to be 16; got %d", b.weight)
+ }
+
+}
diff --git a/http2/server.go b/http2/server.go
new file mode 100644
index 0000000..99cc673
--- /dev/null
+++ b/http2/server.go
@@ -0,0 +1,1780 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+// TODO: replace all <-sc.doneServing with reads from the stream's cw
+// instead, and make sure that on close we close all open
+// streams. then remove doneServing?
+
+// TODO: finish GOAWAY support. Consider each incoming frame type and
+// whether it should be ignored during a shutdown race.
+
+// TODO: disconnect idle clients. GFE seems to do 4 minutes. make
+// configurable? or maximum number of idle clients and remove the
+// oldest?
+
+// TODO: turn off the serve goroutine when idle, so
+// an idle conn only has the readFrames goroutine active. (which could
+// also be optimized probably to pin less memory in crypto/tls). This
+// would involve tracking when the serve goroutine is active (atomic
+// int32 read/CAS probably?) and starting it up when frames arrive,
+// and shutting it down when all handlers exit. the occasional PING
+// packets could use time.AfterFunc to call sc.wakeStartServeLoop()
+// (which is a no-op if already running) and then queue the PING write
+// as normal. The serve loop would then exit in most cases (if no
+// Handlers running) and not be woken up again until the PING packet
+// returns.
+
+// TODO (maybe): add a mechanism for Handlers to going into
+// half-closed-local mode (rw.(io.Closer) test?) but not exit their
+// handler, and continue to be able to read from the
+// Request.Body. This would be a somewhat semantic change from HTTP/1
+// (or at least what we expose in net/http), so I'd probably want to
+// add it there too. For now, this package says that returning from
+// the Handler ServeHTTP function means you're both done reading and
+// done writing, without a way to stop just one or the other.
+
+package http2
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "golang.org/x/net/http2/hpack"
+)
+
+const (
+ prefaceTimeout = 10 * time.Second
+ firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway
+ handlerChunkWriteSize = 4 << 10
+ defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to?
+)
+
+var (
+ errClientDisconnected = errors.New("client disconnected")
+ errClosedBody = errors.New("body closed by handler")
+ errStreamBroken = errors.New("http2: stream broken")
+)
+
+var responseWriterStatePool = sync.Pool{
+ New: func() interface{} {
+ rws := &responseWriterState{}
+ rws.bw = bufio.NewWriterSize(chunkWriter{rws}, handlerChunkWriteSize)
+ return rws
+ },
+}
+
+// Test hooks.
+var (
+ testHookOnConn func()
+ testHookGetServerConn func(*serverConn)
+ testHookOnPanicMu *sync.Mutex // nil except in tests
+ testHookOnPanic func(sc *serverConn, panicVal interface{}) (rePanic bool)
+)
+
+// Server is an HTTP/2 server.
+type Server struct {
+ // MaxHandlers limits the number of http.Handler ServeHTTP goroutines
+ // which may run at a time over all connections.
+ // Negative or zero no limit.
+ // TODO: implement
+ MaxHandlers int
+
+ // MaxConcurrentStreams optionally specifies the number of
+ // concurrent streams that each client may have open at a
+ // time. This is unrelated to the number of http.Handler goroutines
+ // which may be active globally, which is MaxHandlers.
+ // If zero, MaxConcurrentStreams defaults to at least 100, per
+ // the HTTP/2 spec's recommendations.
+ MaxConcurrentStreams 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
+ // default value is used.
+ MaxReadFrameSize uint32
+
+ // PermitProhibitedCipherSuites, if true, permits the use of
+ // cipher suites prohibited by the HTTP/2 spec.
+ PermitProhibitedCipherSuites bool
+}
+
+func (s *Server) maxReadFrameSize() uint32 {
+ if v := s.MaxReadFrameSize; v >= minMaxFrameSize && v <= maxFrameSize {
+ return v
+ }
+ return defaultMaxReadFrameSize
+}
+
+func (s *Server) maxConcurrentStreams() uint32 {
+ if v := s.MaxConcurrentStreams; v > 0 {
+ return v
+ }
+ return defaultMaxStreams
+}
+
+// ConfigureServer adds HTTP/2 support to a net/http Server.
+//
+// The configuration conf may be nil.
+//
+// ConfigureServer must be called before s begins serving.
+func ConfigureServer(s *http.Server, conf *Server) {
+ if conf == nil {
+ conf = new(Server)
+ }
+ if s.TLSConfig == nil {
+ s.TLSConfig = new(tls.Config)
+ }
+
+ // Note: not setting MinVersion to tls.VersionTLS12,
+ // as we don't want to interfere with HTTP/1.1 traffic
+ // on the user's server. We enforce TLS 1.2 later once
+ // we accept a connection. Ideally this should be done
+ // during next-proto selection, but using TLS <1.2 with
+ // HTTP/2 is still the client's bug.
+
+ // Be sure we advertise tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+ // at least.
+ // TODO: enable PreferServerCipherSuites?
+ if s.TLSConfig.CipherSuites != nil {
+ const requiredCipher = tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+ haveRequired := false
+ for _, v := range s.TLSConfig.CipherSuites {
+ if v == requiredCipher {
+ haveRequired = true
+ break
+ }
+ }
+ if !haveRequired {
+ s.TLSConfig.CipherSuites = append(s.TLSConfig.CipherSuites, requiredCipher)
+ }
+ }
+
+ haveNPN := false
+ for _, p := range s.TLSConfig.NextProtos {
+ if p == NextProtoTLS {
+ haveNPN = true
+ break
+ }
+ }
+ if !haveNPN {
+ s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, NextProtoTLS)
+ }
+ // h2-14 is temporary (as of 2015-03-05) while we wait for all browsers
+ // to switch to "h2".
+ s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, "h2-14")
+
+ if s.TLSNextProto == nil {
+ s.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){}
+ }
+ protoHandler := func(hs *http.Server, c *tls.Conn, h http.Handler) {
+ if testHookOnConn != nil {
+ testHookOnConn()
+ }
+ conf.handleConn(hs, c, h)
+ }
+ s.TLSNextProto[NextProtoTLS] = protoHandler
+ s.TLSNextProto["h2-14"] = protoHandler // temporary; see above.
+}
+
+func (srv *Server) handleConn(hs *http.Server, c net.Conn, h http.Handler) {
+ sc := &serverConn{
+ srv: srv,
+ hs: hs,
+ conn: c,
+ remoteAddrStr: c.RemoteAddr().String(),
+ bw: newBufferedWriter(c),
+ handler: h,
+ streams: make(map[uint32]*stream),
+ readFrameCh: make(chan frameAndGate),
+ readFrameErrCh: make(chan error, 1), // must be buffered for 1
+ wantWriteFrameCh: make(chan frameWriteMsg, 8),
+ wroteFrameCh: make(chan struct{}, 1), // buffered; one send in reading goroutine
+ bodyReadCh: make(chan bodyReadMsg), // buffering doesn't matter either way
+ doneServing: make(chan struct{}),
+ advMaxStreams: srv.maxConcurrentStreams(),
+ writeSched: writeScheduler{
+ maxFrameSize: initialMaxFrameSize,
+ },
+ initialWindowSize: initialWindowSize,
+ headerTableSize: initialHeaderTableSize,
+ serveG: newGoroutineLock(),
+ pushEnabled: true,
+ }
+ sc.flow.add(initialWindowSize)
+ sc.inflow.add(initialWindowSize)
+ sc.hpackEncoder = hpack.NewEncoder(&sc.headerWriteBuf)
+ sc.hpackDecoder = hpack.NewDecoder(initialHeaderTableSize, sc.onNewHeaderField)
+
+ fr := NewFramer(sc.bw, c)
+ fr.SetMaxReadFrameSize(srv.maxReadFrameSize())
+ sc.framer = fr
+
+ if tc, ok := c.(*tls.Conn); ok {
+ sc.tlsState = new(tls.ConnectionState)
+ *sc.tlsState = tc.ConnectionState()
+ // 9.2 Use of TLS Features
+ // An implementation of HTTP/2 over TLS MUST use TLS
+ // 1.2 or higher with the restrictions on feature set
+ // and cipher suite described in this section. Due to
+ // implementation limitations, it might not be
+ // possible to fail TLS negotiation. An endpoint MUST
+ // immediately terminate an HTTP/2 connection that
+ // does not meet the TLS requirements described in
+ // this section with a connection error (Section
+ // 5.4.1) of type INADEQUATE_SECURITY.
+ if sc.tlsState.Version < tls.VersionTLS12 {
+ sc.rejectConn(ErrCodeInadequateSecurity, "TLS version too low")
+ return
+ }
+
+ if sc.tlsState.ServerName == "" {
+ // Client must use SNI, but we don't enforce that anymore,
+ // since it was causing problems when connecting to bare IP
+ // addresses during development.
+ //
+ // TODO: optionally enforce? Or enforce at the time we receive
+ // a new request, and verify the the ServerName matches the :authority?
+ // But that precludes proxy situations, perhaps.
+ //
+ // So for now, do nothing here again.
+ }
+
+ if !srv.PermitProhibitedCipherSuites && isBadCipher(sc.tlsState.CipherSuite) {
+ // "Endpoints MAY choose to generate a connection error
+ // (Section 5.4.1) of type INADEQUATE_SECURITY if one of
+ // the prohibited cipher suites are negotiated."
+ //
+ // We choose that. In my opinion, the spec is weak
+ // here. It also says both parties must support at least
+ // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 so there's no
+ // excuses here. If we really must, we could allow an
+ // "AllowInsecureWeakCiphers" option on the server later.
+ // Let's see how it plays out first.
+ sc.rejectConn(ErrCodeInadequateSecurity, fmt.Sprintf("Prohibited TLS 1.2 Cipher Suite: %x", sc.tlsState.CipherSuite))
+ return
+ }
+ }
+
+ if hook := testHookGetServerConn; hook != nil {
+ hook(sc)
+ }
+ sc.serve()
+}
+
+// isBadCipher reports whether the cipher is blacklisted by the HTTP/2 spec.
+func isBadCipher(cipher uint16) bool {
+ switch cipher {
+ case tls.TLS_RSA_WITH_RC4_128_SHA,
+ tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+ tls.TLS_RSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+ tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA:
+ // Reject cipher suites from Appendix A.
+ // "This list includes those cipher suites that do not
+ // offer an ephemeral key exchange and those that are
+ // based on the TLS null, stream or block cipher type"
+ return true
+ default:
+ return false
+ }
+}
+
+func (sc *serverConn) rejectConn(err ErrCode, debug string) {
+ log.Printf("REJECTING conn: %v, %s", err, debug)
+ // ignoring errors. hanging up anyway.
+ sc.framer.WriteGoAway(0, err, []byte(debug))
+ sc.bw.Flush()
+ sc.conn.Close()
+}
+
+// frameAndGates coordinates the readFrames and serve
+// goroutines. Because the Framer interface only permits the most
+// recently-read Frame from being accessed, the readFrames goroutine
+// blocks until it has a frame, passes it to serve, and then waits for
+// serve to be done with it before reading the next one.
+type frameAndGate struct {
+ f Frame
+ g gate
+}
+
+type serverConn struct {
+ // Immutable:
+ srv *Server
+ hs *http.Server
+ conn net.Conn
+ bw *bufferedWriter // writing to conn
+ handler http.Handler
+ framer *Framer
+ hpackDecoder *hpack.Decoder
+ doneServing chan struct{} // closed when serverConn.serve ends
+ readFrameCh chan frameAndGate // written by serverConn.readFrames
+ readFrameErrCh chan error
+ wantWriteFrameCh chan frameWriteMsg // from handlers -> serve
+ wroteFrameCh chan struct{} // from writeFrameAsync -> serve, tickles more frame writes
+ bodyReadCh chan bodyReadMsg // from handlers -> serve
+ testHookCh chan func() // code to run on the serve loop
+ flow flow // conn-wide (not stream-specific) outbound flow control
+ inflow flow // conn-wide inbound flow control
+ tlsState *tls.ConnectionState // shared by all handlers, like net/http
+ remoteAddrStr string
+
+ // Everything following is owned by the serve loop; use serveG.check():
+ serveG goroutineLock // used to verify funcs are on serve()
+ pushEnabled bool
+ sawFirstSettings bool // got the initial SETTINGS frame after the preface
+ needToSendSettingsAck bool
+ unackedSettings int // how many SETTINGS have we sent without ACKs?
+ clientMaxStreams uint32 // SETTINGS_MAX_CONCURRENT_STREAMS from client (our PUSH_PROMISE limit)
+ advMaxStreams uint32 // our SETTINGS_MAX_CONCURRENT_STREAMS advertised the client
+ curOpenStreams uint32 // client's number of open streams
+ maxStreamID uint32 // max ever seen
+ streams map[uint32]*stream
+ initialWindowSize int32
+ headerTableSize uint32
+ maxHeaderListSize uint32 // zero means unknown (default)
+ canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case
+ req requestParam // non-zero while reading request headers
+ writingFrame bool // started write goroutine but haven't heard back on wroteFrameCh
+ needsFrameFlush bool // last frame write wasn't a flush
+ writeSched writeScheduler
+ inGoAway bool // we've started to or sent GOAWAY
+ needToSendGoAway bool // we need to schedule a GOAWAY frame write
+ goAwayCode ErrCode
+ shutdownTimerCh <-chan time.Time // nil until used
+ shutdownTimer *time.Timer // nil until used
+
+ // Owned by the writeFrameAsync goroutine:
+ headerWriteBuf bytes.Buffer
+ hpackEncoder *hpack.Encoder
+}
+
+// requestParam is the state of the next request, initialized over
+// potentially several frames HEADERS + zero or more CONTINUATION
+// frames.
+type requestParam struct {
+ // stream is non-nil if we're reading (HEADER or CONTINUATION)
+ // frames for a request (but not DATA).
+ stream *stream
+ header http.Header
+ method, path string
+ scheme, authority string
+ sawRegularHeader bool // saw a non-pseudo header already
+ invalidHeader bool // an invalid header was seen
+}
+
+// stream represents a stream. This is the minimal metadata needed by
+// the serve goroutine. Most of the actual stream state is owned by
+// the http.Handler's goroutine in the responseWriter. Because the
+// responseWriter's responseWriterState is recycled at the end of a
+// handler, this struct intentionally has no pointer to the
+// *responseWriter{,State} itself, as the Handler ending nils out the
+// responseWriter's state field.
+type stream struct {
+ // immutable:
+ id uint32
+ body *pipe // non-nil if expecting DATA frames
+ cw closeWaiter // closed wait stream transitions to closed state
+
+ // owned by serverConn's serve loop:
+ bodyBytes int64 // body bytes seen so far
+ declBodyBytes int64 // or -1 if undeclared
+ flow flow // limits writing from Handler to client
+ inflow flow // what the client is allowed to POST/etc to us
+ parent *stream // or nil
+ weight uint8
+ state streamState
+ sentReset bool // only true once detached from streams map
+ gotReset bool // only true once detacted from streams map
+}
+
+func (sc *serverConn) Framer() *Framer { return sc.framer }
+func (sc *serverConn) CloseConn() error { return sc.conn.Close() }
+func (sc *serverConn) Flush() error { return sc.bw.Flush() }
+func (sc *serverConn) HeaderEncoder() (*hpack.Encoder, *bytes.Buffer) {
+ return sc.hpackEncoder, &sc.headerWriteBuf
+}
+
+func (sc *serverConn) state(streamID uint32) (streamState, *stream) {
+ sc.serveG.check()
+ // http://http2.github.io/http2-spec/#rfc.section.5.1
+ if st, ok := sc.streams[streamID]; ok {
+ return st.state, st
+ }
+ // "The first use of a new stream identifier implicitly closes all
+ // streams in the "idle" state that might have been initiated by
+ // that peer with a lower-valued stream identifier. For example, if
+ // a client sends a HEADERS frame on stream 7 without ever sending a
+ // frame on stream 5, then stream 5 transitions to the "closed"
+ // state when the first frame for stream 7 is sent or received."
+ if streamID <= sc.maxStreamID {
+ return stateClosed, nil
+ }
+ return stateIdle, nil
+}
+
+func (sc *serverConn) vlogf(format string, args ...interface{}) {
+ if VerboseLogs {
+ sc.logf(format, args...)
+ }
+}
+
+func (sc *serverConn) logf(format string, args ...interface{}) {
+ if lg := sc.hs.ErrorLog; lg != nil {
+ lg.Printf(format, args...)
+ } else {
+ log.Printf(format, args...)
+ }
+}
+
+func (sc *serverConn) condlogf(err error, format string, args ...interface{}) {
+ if err == nil {
+ return
+ }
+ str := err.Error()
+ if err == io.EOF || strings.Contains(str, "use of closed network connection") {
+ // Boring, expected errors.
+ sc.vlogf(format, args...)
+ } else {
+ sc.logf(format, args...)
+ }
+}
+
+func (sc *serverConn) onNewHeaderField(f hpack.HeaderField) {
+ sc.serveG.check()
+ sc.vlogf("got header field %+v", f)
+ switch {
+ case !validHeader(f.Name):
+ sc.req.invalidHeader = true
+ case strings.HasPrefix(f.Name, ":"):
+ if sc.req.sawRegularHeader {
+ sc.logf("pseudo-header after regular header")
+ sc.req.invalidHeader = true
+ return
+ }
+ var dst *string
+ switch f.Name {
+ case ":method":
+ dst = &sc.req.method
+ case ":path":
+ dst = &sc.req.path
+ case ":scheme":
+ dst = &sc.req.scheme
+ case ":authority":
+ dst = &sc.req.authority
+ default:
+ // 8.1.2.1 Pseudo-Header Fields
+ // "Endpoints MUST treat a request or response
+ // that contains undefined or invalid
+ // pseudo-header fields as malformed (Section
+ // 8.1.2.6)."
+ sc.logf("invalid pseudo-header %q", f.Name)
+ sc.req.invalidHeader = true
+ return
+ }
+ if *dst != "" {
+ sc.logf("duplicate pseudo-header %q sent", f.Name)
+ sc.req.invalidHeader = true
+ return
+ }
+ *dst = f.Value
+ case f.Name == "cookie":
+ sc.req.sawRegularHeader = true
+ if s, ok := sc.req.header["Cookie"]; ok && len(s) == 1 {
+ s[0] = s[0] + "; " + f.Value
+ } else {
+ sc.req.header.Add("Cookie", f.Value)
+ }
+ default:
+ sc.req.sawRegularHeader = true
+ sc.req.header.Add(sc.canonicalHeader(f.Name), f.Value)
+ }
+}
+
+func (sc *serverConn) canonicalHeader(v string) string {
+ sc.serveG.check()
+ cv, ok := commonCanonHeader[v]
+ if ok {
+ return cv
+ }
+ cv, ok = sc.canonHeader[v]
+ if ok {
+ return cv
+ }
+ if sc.canonHeader == nil {
+ sc.canonHeader = make(map[string]string)
+ }
+ cv = http.CanonicalHeaderKey(v)
+ sc.canonHeader[v] = cv
+ return cv
+}
+
+// readFrames is the loop that reads incoming frames.
+// It's run on its own goroutine.
+func (sc *serverConn) readFrames() {
+ g := make(gate, 1)
+ for {
+ f, err := sc.framer.ReadFrame()
+ if err != nil {
+ sc.readFrameErrCh <- err
+ close(sc.readFrameCh)
+ return
+ }
+ sc.readFrameCh <- frameAndGate{f, g}
+ // We can't read another frame until this one is
+ // processed, as the ReadFrame interface doesn't copy
+ // memory. The Frame accessor methods access the last
+ // frame's (shared) buffer. So we wait for the
+ // serve goroutine to tell us it's done:
+ g.Wait()
+ }
+}
+
+// writeFrameAsync runs in its own goroutine and writes a single frame
+// and then reports when it's done.
+// At most one goroutine can be running writeFrameAsync at a time per
+// serverConn.
+func (sc *serverConn) writeFrameAsync(wm frameWriteMsg) {
+ err := wm.write.writeFrame(sc)
+ if ch := wm.done; ch != nil {
+ select {
+ case ch <- err:
+ default:
+ panic(fmt.Sprintf("unbuffered done channel passed in for type %T", wm.write))
+ }
+ }
+ sc.wroteFrameCh <- struct{}{} // tickle frame selection scheduler
+}
+
+func (sc *serverConn) closeAllStreamsOnConnClose() {
+ sc.serveG.check()
+ for _, st := range sc.streams {
+ sc.closeStream(st, errClientDisconnected)
+ }
+}
+
+func (sc *serverConn) stopShutdownTimer() {
+ sc.serveG.check()
+ if t := sc.shutdownTimer; t != nil {
+ t.Stop()
+ }
+}
+
+func (sc *serverConn) notePanic() {
+ if testHookOnPanicMu != nil {
+ testHookOnPanicMu.Lock()
+ defer testHookOnPanicMu.Unlock()
+ }
+ if testHookOnPanic != nil {
+ if e := recover(); e != nil {
+ if testHookOnPanic(sc, e) {
+ panic(e)
+ }
+ }
+ }
+}
+
+func (sc *serverConn) serve() {
+ sc.serveG.check()
+ defer sc.notePanic()
+ defer sc.conn.Close()
+ defer sc.closeAllStreamsOnConnClose()
+ defer sc.stopShutdownTimer()
+ defer close(sc.doneServing) // unblocks handlers trying to send
+
+ sc.vlogf("HTTP/2 connection from %v on %p", sc.conn.RemoteAddr(), sc.hs)
+
+ sc.writeFrame(frameWriteMsg{
+ write: writeSettings{
+ {SettingMaxFrameSize, sc.srv.maxReadFrameSize()},
+ {SettingMaxConcurrentStreams, sc.advMaxStreams},
+
+ // TODO: more actual settings, notably
+ // SettingInitialWindowSize, but then we also
+ // want to bump up the conn window size the
+ // same amount here right after the settings
+ },
+ })
+ sc.unackedSettings++
+
+ if err := sc.readPreface(); err != nil {
+ sc.condlogf(err, "error reading preface from client %v: %v", sc.conn.RemoteAddr(), err)
+ return
+ }
+
+ go sc.readFrames() // closed by defer sc.conn.Close above
+
+ settingsTimer := time.NewTimer(firstSettingsTimeout)
+ for {
+ select {
+ case wm := <-sc.wantWriteFrameCh:
+ sc.writeFrame(wm)
+ case <-sc.wroteFrameCh:
+ if sc.writingFrame != true {
+ panic("internal error: expected to be already writing a frame")
+ }
+ sc.writingFrame = false
+ sc.scheduleFrameWrite()
+ case fg, ok := <-sc.readFrameCh:
+ if !ok {
+ sc.readFrameCh = nil
+ }
+ if !sc.processFrameFromReader(fg, ok) {
+ return
+ }
+ if settingsTimer.C != nil {
+ settingsTimer.Stop()
+ settingsTimer.C = nil
+ }
+ case m := <-sc.bodyReadCh:
+ sc.noteBodyRead(m.st, m.n)
+ case <-settingsTimer.C:
+ sc.logf("timeout waiting for SETTINGS frames from %v", sc.conn.RemoteAddr())
+ return
+ case <-sc.shutdownTimerCh:
+ sc.vlogf("GOAWAY close timer fired; closing conn from %v", sc.conn.RemoteAddr())
+ return
+ case fn := <-sc.testHookCh:
+ fn()
+ }
+ }
+}
+
+// readPreface reads the ClientPreface greeting from the peer
+// or returns an error on timeout or an invalid greeting.
+func (sc *serverConn) readPreface() error {
+ errc := make(chan error, 1)
+ go func() {
+ // Read the client preface
+ buf := make([]byte, len(ClientPreface))
+ if _, err := io.ReadFull(sc.conn, buf); err != nil {
+ errc <- err
+ } else if !bytes.Equal(buf, clientPreface) {
+ errc <- fmt.Errorf("bogus greeting %q", buf)
+ } else {
+ errc <- nil
+ }
+ }()
+ timer := time.NewTimer(prefaceTimeout) // TODO: configurable on *Server?
+ defer timer.Stop()
+ select {
+ case <-timer.C:
+ return errors.New("timeout waiting for client preface")
+ case err := <-errc:
+ if err == nil {
+ sc.vlogf("client %v said hello", sc.conn.RemoteAddr())
+ }
+ return err
+ }
+}
+
+// writeDataFromHandler writes the data described in req to stream.id.
+//
+// The provided ch is used to avoid allocating new channels for each
+// write operation. It's expected that the caller reuses writeData and ch
+// over time.
+//
+// The flow control currently happens in the Handler where it waits
+// for 1 or more bytes to be available to then write here. So at this
+// point we know that we have flow control. But this might have to
+// change when priority is implemented, so the serve goroutine knows
+// the total amount of bytes waiting to be sent and can can have more
+// scheduling decisions available.
+func (sc *serverConn) writeDataFromHandler(stream *stream, writeData *writeData, ch chan error) error {
+ sc.writeFrameFromHandler(frameWriteMsg{
+ write: writeData,
+ stream: stream,
+ done: ch,
+ })
+ select {
+ case err := <-ch:
+ return err
+ case <-sc.doneServing:
+ return errClientDisconnected
+ case <-stream.cw:
+ return errStreamBroken
+ }
+}
+
+// writeFrameFromHandler sends wm to sc.wantWriteFrameCh, but aborts
+// if the connection has gone away.
+//
+// This must not be run from the serve goroutine itself, else it might
+// deadlock writing to sc.wantWriteFrameCh (which is only mildly
+// buffered and is read by serve itself). If you're on the serve
+// goroutine, call writeFrame instead.
+func (sc *serverConn) writeFrameFromHandler(wm frameWriteMsg) {
+ sc.serveG.checkNotOn() // NOT
+ select {
+ case sc.wantWriteFrameCh <- wm:
+ case <-sc.doneServing:
+ // Client has closed their connection to the server.
+ }
+}
+
+// writeFrame schedules a frame to write and sends it if there's nothing
+// already being written.
+//
+// There is no pushback here (the serve goroutine never blocks). It's
+// the http.Handlers that block, waiting for their previous frames to
+// make it onto the wire
+//
+// If you're not on the serve goroutine, use writeFrameFromHandler instead.
+func (sc *serverConn) writeFrame(wm frameWriteMsg) {
+ sc.serveG.check()
+ sc.writeSched.add(wm)
+ sc.scheduleFrameWrite()
+}
+
+// startFrameWrite starts a goroutine to write wm (in a separate
+// goroutine since that might block on the network), and updates the
+// serve goroutine's state about the world, updated from info in wm.
+func (sc *serverConn) startFrameWrite(wm frameWriteMsg) {
+ sc.serveG.check()
+ if sc.writingFrame {
+ panic("internal error: can only be writing one frame at a time")
+ }
+ sc.writingFrame = true
+
+ st := wm.stream
+ if st != nil {
+ switch st.state {
+ case stateHalfClosedLocal:
+ panic("internal error: attempt to send frame on half-closed-local stream")
+ case stateClosed:
+ if st.sentReset || st.gotReset {
+ // Skip this frame. But fake the frame write to reschedule:
+ sc.wroteFrameCh <- struct{}{}
+ return
+ }
+ panic(fmt.Sprintf("internal error: attempt to send a write %v on a closed stream", wm))
+ }
+ }
+
+ sc.needsFrameFlush = true
+ if endsStream(wm.write) {
+ if st == nil {
+ panic("internal error: expecting non-nil stream")
+ }
+ switch st.state {
+ case stateOpen:
+ // Here we would go to stateHalfClosedLocal in
+ // theory, but since our handler is done and
+ // the net/http package provides no mechanism
+ // for finishing writing to a ResponseWriter
+ // while still reading data (see possible TODO
+ // at top of this file), we go into closed
+ // state here anyway, after telling the peer
+ // we're hanging up on them.
+ st.state = stateHalfClosedLocal // won't last long, but necessary for closeStream via resetStream
+ errCancel := StreamError{st.id, ErrCodeCancel}
+ sc.resetStream(errCancel)
+ case stateHalfClosedRemote:
+ sc.closeStream(st, nil)
+ }
+ }
+ go sc.writeFrameAsync(wm)
+}
+
+// scheduleFrameWrite tickles the frame writing scheduler.
+//
+// If a frame is already being written, nothing happens. This will be called again
+// when the frame is done being written.
+//
+// If a frame isn't being written we need to send one, the best frame
+// to send is selected, preferring first things that aren't
+// stream-specific (e.g. ACKing settings), and then finding the
+// highest priority stream.
+//
+// If a frame isn't being written and there's nothing else to send, we
+// flush the write buffer.
+func (sc *serverConn) scheduleFrameWrite() {
+ sc.serveG.check()
+ if sc.writingFrame {
+ return
+ }
+ if sc.needToSendGoAway {
+ sc.needToSendGoAway = false
+ sc.startFrameWrite(frameWriteMsg{
+ write: &writeGoAway{
+ maxStreamID: sc.maxStreamID,
+ code: sc.goAwayCode,
+ },
+ })
+ return
+ }
+ if sc.needToSendSettingsAck {
+ sc.needToSendSettingsAck = false
+ sc.startFrameWrite(frameWriteMsg{write: writeSettingsAck{}})
+ return
+ }
+ if !sc.inGoAway {
+ if wm, ok := sc.writeSched.take(); ok {
+ sc.startFrameWrite(wm)
+ return
+ }
+ }
+ if sc.needsFrameFlush {
+ sc.startFrameWrite(frameWriteMsg{write: flushFrameWriter{}})
+ sc.needsFrameFlush = false // after startFrameWrite, since it sets this true
+ return
+ }
+}
+
+func (sc *serverConn) goAway(code ErrCode) {
+ sc.serveG.check()
+ if sc.inGoAway {
+ return
+ }
+ if code != ErrCodeNo {
+ sc.shutDownIn(250 * time.Millisecond)
+ } else {
+ // TODO: configurable
+ sc.shutDownIn(1 * time.Second)
+ }
+ sc.inGoAway = true
+ sc.needToSendGoAway = true
+ sc.goAwayCode = code
+ sc.scheduleFrameWrite()
+}
+
+func (sc *serverConn) shutDownIn(d time.Duration) {
+ sc.serveG.check()
+ sc.shutdownTimer = time.NewTimer(d)
+ sc.shutdownTimerCh = sc.shutdownTimer.C
+}
+
+func (sc *serverConn) resetStream(se StreamError) {
+ sc.serveG.check()
+ sc.writeFrame(frameWriteMsg{write: se})
+ if st, ok := sc.streams[se.StreamID]; ok {
+ st.sentReset = true
+ sc.closeStream(st, se)
+ }
+}
+
+// curHeaderStreamID returns the stream ID of the header block we're
+// currently in the middle of reading. If this returns non-zero, the
+// next frame must be a CONTINUATION with this stream id.
+func (sc *serverConn) curHeaderStreamID() uint32 {
+ sc.serveG.check()
+ st := sc.req.stream
+ if st == nil {
+ return 0
+ }
+ return st.id
+}
+
+// processFrameFromReader processes the serve loop's read from readFrameCh from the
+// frame-reading goroutine.
+// processFrameFromReader returns whether the connection should be kept open.
+func (sc *serverConn) processFrameFromReader(fg frameAndGate, fgValid bool) bool {
+ sc.serveG.check()
+ var clientGone bool
+ var err error
+ if !fgValid {
+ err = <-sc.readFrameErrCh
+ if err == ErrFrameTooLarge {
+ sc.goAway(ErrCodeFrameSize)
+ return true // goAway will close the loop
+ }
+ clientGone = err == io.EOF || strings.Contains(err.Error(), "use of closed network connection")
+ if clientGone {
+ // TODO: could we also get into this state if
+ // the peer does a half close
+ // (e.g. CloseWrite) because they're done
+ // sending frames but they're still wanting
+ // our open replies? Investigate.
+ // TODO: add CloseWrite to crypto/tls.Conn first
+ // so we have a way to test this? I suppose
+ // just for testing we could have a non-TLS mode.
+ return false
+ }
+ }
+
+ if fgValid {
+ f := fg.f
+ sc.vlogf("got %v: %#v", f.Header(), f)
+ err = sc.processFrame(f)
+ fg.g.Done() // unblock the readFrames goroutine
+ if err == nil {
+ return true
+ }
+ }
+
+ switch ev := err.(type) {
+ case StreamError:
+ sc.resetStream(ev)
+ return true
+ case goAwayFlowError:
+ sc.goAway(ErrCodeFlowControl)
+ return true
+ case ConnectionError:
+ sc.logf("%v: %v", sc.conn.RemoteAddr(), ev)
+ sc.goAway(ErrCode(ev))
+ return true // goAway will handle shutdown
+ default:
+ if !fgValid {
+ sc.logf("disconnecting; error reading frame from client %s: %v", sc.conn.RemoteAddr(), err)
+ } else {
+ sc.logf("disconnection due to other error: %v", err)
+ }
+ }
+ return false
+}
+
+func (sc *serverConn) processFrame(f Frame) error {
+ sc.serveG.check()
+
+ // First frame received must be SETTINGS.
+ if !sc.sawFirstSettings {
+ if _, ok := f.(*SettingsFrame); !ok {
+ return ConnectionError(ErrCodeProtocol)
+ }
+ sc.sawFirstSettings = true
+ }
+
+ if s := sc.curHeaderStreamID(); s != 0 {
+ if cf, ok := f.(*ContinuationFrame); !ok {
+ return ConnectionError(ErrCodeProtocol)
+ } else if cf.Header().StreamID != s {
+ return ConnectionError(ErrCodeProtocol)
+ }
+ }
+
+ switch f := f.(type) {
+ case *SettingsFrame:
+ return sc.processSettings(f)
+ case *HeadersFrame:
+ return sc.processHeaders(f)
+ case *ContinuationFrame:
+ return sc.processContinuation(f)
+ case *WindowUpdateFrame:
+ return sc.processWindowUpdate(f)
+ case *PingFrame:
+ return sc.processPing(f)
+ case *DataFrame:
+ return sc.processData(f)
+ case *RSTStreamFrame:
+ return sc.processResetStream(f)
+ case *PriorityFrame:
+ return sc.processPriority(f)
+ case *PushPromiseFrame:
+ // A client cannot push. Thus, servers MUST treat the receipt of a PUSH_PROMISE
+ // frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
+ return ConnectionError(ErrCodeProtocol)
+ default:
+ log.Printf("Ignoring frame: %v", f.Header())
+ return nil
+ }
+}
+
+func (sc *serverConn) processPing(f *PingFrame) error {
+ sc.serveG.check()
+ if f.Flags.Has(FlagSettingsAck) {
+ // 6.7 PING: " An endpoint MUST NOT respond to PING frames
+ // containing this flag."
+ return nil
+ }
+ if f.StreamID != 0 {
+ // "PING frames are not associated with any individual
+ // stream. If a PING frame is received with a stream
+ // identifier field value other than 0x0, the recipient MUST
+ // respond with a connection error (Section 5.4.1) of type
+ // PROTOCOL_ERROR."
+ return ConnectionError(ErrCodeProtocol)
+ }
+ sc.writeFrame(frameWriteMsg{write: writePingAck{f}})
+ return nil
+}
+
+func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error {
+ sc.serveG.check()
+ switch {
+ case f.StreamID != 0: // stream-level flow control
+ st := sc.streams[f.StreamID]
+ if st == nil {
+ // "WINDOW_UPDATE can be sent by a peer that has sent a
+ // frame bearing the END_STREAM flag. This means that a
+ // receiver could receive a WINDOW_UPDATE frame on a "half
+ // closed (remote)" or "closed" stream. A receiver MUST
+ // NOT treat this as an error, see Section 5.1."
+ return nil
+ }
+ if !st.flow.add(int32(f.Increment)) {
+ return StreamError{f.StreamID, ErrCodeFlowControl}
+ }
+ default: // connection-level flow control
+ if !sc.flow.add(int32(f.Increment)) {
+ return goAwayFlowError{}
+ }
+ }
+ sc.scheduleFrameWrite()
+ return nil
+}
+
+func (sc *serverConn) processResetStream(f *RSTStreamFrame) error {
+ sc.serveG.check()
+
+ state, st := sc.state(f.StreamID)
+ if state == stateIdle {
+ // 6.4 "RST_STREAM frames MUST NOT be sent for a
+ // stream in the "idle" state. If a RST_STREAM frame
+ // identifying an idle stream is received, the
+ // recipient MUST treat this as a connection error
+ // (Section 5.4.1) of type PROTOCOL_ERROR.
+ return ConnectionError(ErrCodeProtocol)
+ }
+ if st != nil {
+ st.gotReset = true
+ sc.closeStream(st, StreamError{f.StreamID, f.ErrCode})
+ }
+ return nil
+}
+
+func (sc *serverConn) closeStream(st *stream, err error) {
+ sc.serveG.check()
+ if st.state == stateIdle || st.state == stateClosed {
+ panic(fmt.Sprintf("invariant; can't close stream in state %v", st.state))
+ }
+ st.state = stateClosed
+ sc.curOpenStreams--
+ delete(sc.streams, st.id)
+ if p := st.body; p != nil {
+ p.Close(err)
+ }
+ st.cw.Close() // signals Handler's CloseNotifier, unblocks writes, etc
+ sc.writeSched.forgetStream(st.id)
+}
+
+func (sc *serverConn) processSettings(f *SettingsFrame) error {
+ sc.serveG.check()
+ if f.IsAck() {
+ sc.unackedSettings--
+ if sc.unackedSettings < 0 {
+ // Why is the peer ACKing settings we never sent?
+ // The spec doesn't mention this case, but
+ // hang up on them anyway.
+ return ConnectionError(ErrCodeProtocol)
+ }
+ return nil
+ }
+ if err := f.ForeachSetting(sc.processSetting); err != nil {
+ return err
+ }
+ sc.needToSendSettingsAck = true
+ sc.scheduleFrameWrite()
+ return nil
+}
+
+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
+ sc.hpackEncoder.SetMaxDynamicTableSize(s.Val)
+ case SettingEnablePush:
+ sc.pushEnabled = s.Val != 0
+ case SettingMaxConcurrentStreams:
+ sc.clientMaxStreams = s.Val
+ case SettingInitialWindowSize:
+ return sc.processSettingInitialWindowSize(s.Val)
+ case SettingMaxFrameSize:
+ sc.writeSched.maxFrameSize = s.Val
+ case SettingMaxHeaderListSize:
+ sc.maxHeaderListSize = s.Val
+ default:
+ // 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()
+ // 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
+ // SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST
+ // adjust the size of all stream flow control windows that it
+ // maintains by the difference between the new value and the
+ // old value."
+ old := sc.initialWindowSize
+ sc.initialWindowSize = int32(val)
+ growth := sc.initialWindowSize - old // may be negative
+ for _, st := range sc.streams {
+ if !st.flow.add(growth) {
+ // 6.9.2 Initial Flow Control Window Size
+ // "An endpoint MUST treat a change to
+ // SETTINGS_INITIAL_WINDOW_SIZE that causes any flow
+ // control window to exceed the maximum size as a
+ // connection error (Section 5.4.1) of type
+ // FLOW_CONTROL_ERROR."
+ return ConnectionError(ErrCodeFlowControl)
+ }
+ }
+ return nil
+}
+
+func (sc *serverConn) processData(f *DataFrame) error {
+ sc.serveG.check()
+ // "If a DATA frame is received whose stream is not in "open"
+ // or "half closed (local)" state, the recipient MUST respond
+ // with a stream error (Section 5.4.2) of type STREAM_CLOSED."
+ id := f.Header().StreamID
+ st, ok := sc.streams[id]
+ if !ok || st.state != stateOpen {
+ // This includes sending a RST_STREAM if the stream is
+ // in stateHalfClosedLocal (which currently means that
+ // the http.Handler returned, so it's done reading &
+ // done writing). Try to stop the client from sending
+ // more DATA.
+ return StreamError{id, ErrCodeStreamClosed}
+ }
+ if st.body == nil {
+ panic("internal error: should have a body in this state")
+ }
+ data := f.Data()
+
+ // Sender sending more than they'd declared?
+ if st.declBodyBytes != -1 && st.bodyBytes+int64(len(data)) > st.declBodyBytes {
+ st.body.Close(fmt.Errorf("sender tried to send more than declared Content-Length of %d bytes", st.declBodyBytes))
+ return StreamError{id, ErrCodeStreamClosed}
+ }
+ if len(data) > 0 {
+ // Check whether the client has flow control quota.
+ if int(st.inflow.available()) < len(data) {
+ return StreamError{id, ErrCodeFlowControl}
+ }
+ st.inflow.take(int32(len(data)))
+ wrote, err := st.body.Write(data)
+ if err != nil {
+ return StreamError{id, ErrCodeStreamClosed}
+ }
+ if wrote != len(data) {
+ panic("internal error: bad Writer")
+ }
+ st.bodyBytes += int64(len(data))
+ }
+ if f.StreamEnded() {
+ if st.declBodyBytes != -1 && st.declBodyBytes != st.bodyBytes {
+ st.body.Close(fmt.Errorf("request declared a Content-Length of %d but only wrote %d bytes",
+ st.declBodyBytes, st.bodyBytes))
+ } else {
+ st.body.Close(io.EOF)
+ }
+ st.state = stateHalfClosedRemote
+ }
+ return nil
+}
+
+func (sc *serverConn) processHeaders(f *HeadersFrame) error {
+ sc.serveG.check()
+ id := f.Header().StreamID
+ if sc.inGoAway {
+ // Ignore.
+ return nil
+ }
+ // http://http2.github.io/http2-spec/#rfc.section.5.1.1
+ if id%2 != 1 || id <= sc.maxStreamID || sc.req.stream != nil {
+ // Streams initiated by a client MUST use odd-numbered
+ // stream identifiers. [...] The identifier of a newly
+ // established stream MUST be numerically greater than all
+ // streams that the initiating endpoint has opened or
+ // reserved. [...] An endpoint that receives an unexpected
+ // stream identifier MUST respond with a connection error
+ // (Section 5.4.1) of type PROTOCOL_ERROR.
+ return ConnectionError(ErrCodeProtocol)
+ }
+ if id > sc.maxStreamID {
+ sc.maxStreamID = id
+ }
+ st := &stream{
+ id: id,
+ state: stateOpen,
+ }
+ if f.StreamEnded() {
+ st.state = stateHalfClosedRemote
+ }
+ st.cw.Init()
+
+ st.flow.conn = &sc.flow // link to conn-level counter
+ st.flow.add(sc.initialWindowSize)
+ st.inflow.conn = &sc.inflow // link to conn-level counter
+ st.inflow.add(initialWindowSize) // TODO: update this when we send a higher initial window size in the initial settings
+
+ sc.streams[id] = st
+ if f.HasPriority() {
+ adjustStreamPriority(sc.streams, st.id, f.Priority)
+ }
+ sc.curOpenStreams++
+ sc.req = requestParam{
+ stream: st,
+ header: make(http.Header),
+ }
+ return sc.processHeaderBlockFragment(st, f.HeaderBlockFragment(), f.HeadersEnded())
+}
+
+func (sc *serverConn) processContinuation(f *ContinuationFrame) error {
+ sc.serveG.check()
+ st := sc.streams[f.Header().StreamID]
+ if st == nil || sc.curHeaderStreamID() != st.id {
+ return ConnectionError(ErrCodeProtocol)
+ }
+ return sc.processHeaderBlockFragment(st, f.HeaderBlockFragment(), f.HeadersEnded())
+}
+
+func (sc *serverConn) processHeaderBlockFragment(st *stream, frag []byte, end bool) error {
+ sc.serveG.check()
+ if _, err := sc.hpackDecoder.Write(frag); err != nil {
+ // TODO: convert to stream error I assume?
+ return err
+ }
+ if !end {
+ return nil
+ }
+ if err := sc.hpackDecoder.Close(); err != nil {
+ // TODO: convert to stream error I assume?
+ return err
+ }
+ defer sc.resetPendingRequest()
+ if sc.curOpenStreams > sc.advMaxStreams {
+ // "Endpoints MUST NOT exceed the limit set by their
+ // peer. An endpoint that receives a HEADERS frame
+ // that causes their advertised concurrent stream
+ // limit to be exceeded MUST treat this as a stream
+ // error (Section 5.4.2) of type PROTOCOL_ERROR or
+ // REFUSED_STREAM."
+ if sc.unackedSettings == 0 {
+ // They should know better.
+ return StreamError{st.id, ErrCodeProtocol}
+ }
+ // Assume it's a network race, where they just haven't
+ // received our last SETTINGS update. But actually
+ // this can't happen yet, because we don't yet provide
+ // a way for users to adjust server parameters at
+ // runtime.
+ return StreamError{st.id, ErrCodeRefusedStream}
+ }
+
+ rw, req, err := sc.newWriterAndRequest()
+ if err != nil {
+ return err
+ }
+ st.body = req.Body.(*requestBody).pipe // may be nil
+ st.declBodyBytes = req.ContentLength
+ go sc.runHandler(rw, req)
+ return nil
+}
+
+func (sc *serverConn) processPriority(f *PriorityFrame) error {
+ adjustStreamPriority(sc.streams, f.StreamID, f.PriorityParam)
+ return nil
+}
+
+func adjustStreamPriority(streams map[uint32]*stream, streamID uint32, priority PriorityParam) {
+ st, ok := streams[streamID]
+ if !ok {
+ // TODO: not quite correct (this streamID might
+ // already exist in the dep tree, but be closed), but
+ // close enough for now.
+ return
+ }
+ st.weight = priority.Weight
+ parent := streams[priority.StreamDep] // might be nil
+ if parent == st {
+ // if client tries to set this stream to be the parent of itself
+ // ignore and keep going
+ return
+ }
+
+ // section 5.3.3: If a stream is made dependent on one of its
+ // own dependencies, the formerly dependent stream is first
+ // moved to be dependent on the reprioritized stream's previous
+ // parent. The moved dependency retains its weight.
+ for piter := parent; piter != nil; piter = piter.parent {
+ if piter == st {
+ parent.parent = st.parent
+ break
+ }
+ }
+ st.parent = parent
+ if priority.Exclusive && (st.parent != nil || priority.StreamDep == 0) {
+ for _, openStream := range streams {
+ if openStream != st && openStream.parent == st.parent {
+ openStream.parent = st
+ }
+ }
+ }
+}
+
+// resetPendingRequest zeros out all state related to a HEADERS frame
+// and its zero or more CONTINUATION frames sent to start a new
+// request.
+func (sc *serverConn) resetPendingRequest() {
+ sc.serveG.check()
+ sc.req = requestParam{}
+}
+
+func (sc *serverConn) newWriterAndRequest() (*responseWriter, *http.Request, error) {
+ sc.serveG.check()
+ rp := &sc.req
+ if rp.invalidHeader || rp.method == "" || rp.path == "" ||
+ (rp.scheme != "https" && rp.scheme != "http") {
+ // See 8.1.2.6 Malformed Requests and Responses:
+ //
+ // Malformed requests or responses that are detected
+ // MUST be treated as a stream error (Section 5.4.2)
+ // of type PROTOCOL_ERROR."
+ //
+ // 8.1.2.3 Request Pseudo-Header Fields
+ // "All HTTP/2 requests MUST include exactly one valid
+ // value for the :method, :scheme, and :path
+ // pseudo-header fields"
+ return nil, nil, StreamError{rp.stream.id, ErrCodeProtocol}
+ }
+ var tlsState *tls.ConnectionState // nil if not scheme https
+ if rp.scheme == "https" {
+ tlsState = sc.tlsState
+ }
+ authority := rp.authority
+ if authority == "" {
+ authority = rp.header.Get("Host")
+ }
+ needsContinue := rp.header.Get("Expect") == "100-continue"
+ if needsContinue {
+ rp.header.Del("Expect")
+ }
+ bodyOpen := rp.stream.state == stateOpen
+ body := &requestBody{
+ conn: sc,
+ stream: rp.stream,
+ needsContinue: needsContinue,
+ }
+ // TODO: handle asterisk '*' requests + test
+ url, err := url.ParseRequestURI(rp.path)
+ if err != nil {
+ // TODO: find the right error code?
+ return nil, nil, StreamError{rp.stream.id, ErrCodeProtocol}
+ }
+ req := &http.Request{
+ Method: rp.method,
+ URL: url,
+ RemoteAddr: sc.remoteAddrStr,
+ Header: rp.header,
+ RequestURI: rp.path,
+ Proto: "HTTP/2.0",
+ ProtoMajor: 2,
+ ProtoMinor: 0,
+ TLS: tlsState,
+ Host: authority,
+ Body: body,
+ }
+ if bodyOpen {
+ body.pipe = &pipe{
+ b: buffer{buf: make([]byte, initialWindowSize)}, // TODO: share/remove XXX
+ }
+ body.pipe.c.L = &body.pipe.m
+
+ if vv, ok := rp.header["Content-Length"]; ok {
+ req.ContentLength, _ = strconv.ParseInt(vv[0], 10, 64)
+ } else {
+ req.ContentLength = -1
+ }
+ }
+
+ rws := responseWriterStatePool.Get().(*responseWriterState)
+ bwSave := rws.bw
+ *rws = responseWriterState{} // zero all the fields
+ rws.conn = sc
+ rws.bw = bwSave
+ rws.bw.Reset(chunkWriter{rws})
+ rws.stream = rp.stream
+ rws.req = req
+ rws.body = body
+ rws.frameWriteCh = make(chan error, 1)
+
+ rw := &responseWriter{rws: rws}
+ return rw, req, nil
+}
+
+// Run on its own goroutine.
+func (sc *serverConn) runHandler(rw *responseWriter, req *http.Request) {
+ defer rw.handlerDone()
+ // TODO: catch panics like net/http.Server
+ sc.handler.ServeHTTP(rw, req)
+}
+
+// called from handler goroutines.
+// h may be nil.
+func (sc *serverConn) writeHeaders(st *stream, headerData *writeResHeaders, tempCh chan error) {
+ sc.serveG.checkNotOn() // NOT on
+ var errc chan error
+ if headerData.h != nil {
+ // If there's a header map (which we don't own), so we have to block on
+ // waiting for this frame to be written, so an http.Flush mid-handler
+ // writes out the correct value of keys, before a handler later potentially
+ // mutates it.
+ errc = tempCh
+ }
+ sc.writeFrameFromHandler(frameWriteMsg{
+ write: headerData,
+ stream: st,
+ done: errc,
+ })
+ if errc != nil {
+ select {
+ case <-errc:
+ // Ignore. Just for synchronization.
+ // Any error will be handled in the writing goroutine.
+ case <-sc.doneServing:
+ // Client has closed the connection.
+ }
+ }
+}
+
+// called from handler goroutines.
+func (sc *serverConn) write100ContinueHeaders(st *stream) {
+ sc.writeFrameFromHandler(frameWriteMsg{
+ write: write100ContinueHeadersFrame{st.id},
+ stream: st,
+ })
+}
+
+// A bodyReadMsg tells the server loop that the http.Handler read n
+// bytes of the DATA from the client on the given stream.
+type bodyReadMsg struct {
+ st *stream
+ n int
+}
+
+// called from handler goroutines.
+// Notes that the handler for the given stream ID read n bytes of its body
+// and schedules flow control tokens to be sent.
+func (sc *serverConn) noteBodyReadFromHandler(st *stream, n int) {
+ sc.serveG.checkNotOn() // NOT on
+ sc.bodyReadCh <- bodyReadMsg{st, n}
+}
+
+func (sc *serverConn) noteBodyRead(st *stream, n int) {
+ sc.serveG.check()
+ sc.sendWindowUpdate(nil, n) // conn-level
+ if st.state != stateHalfClosedRemote && st.state != stateClosed {
+ // Don't send this WINDOW_UPDATE if the stream is closed
+ // remotely.
+ sc.sendWindowUpdate(st, n)
+ }
+}
+
+// st may be nil for conn-level
+func (sc *serverConn) sendWindowUpdate(st *stream, n int) {
+ sc.serveG.check()
+ // "The legal range for the increment to the flow control
+ // window is 1 to 2^31-1 (2,147,483,647) octets."
+ // A Go Read call on 64-bit machines could in theory read
+ // a larger Read than this. Very unlikely, but we handle it here
+ // rather than elsewhere for now.
+ const maxUint31 = 1<<31 - 1
+ for n >= maxUint31 {
+ sc.sendWindowUpdate32(st, maxUint31)
+ n -= maxUint31
+ }
+ sc.sendWindowUpdate32(st, int32(n))
+}
+
+// st may be nil for conn-level
+func (sc *serverConn) sendWindowUpdate32(st *stream, n int32) {
+ sc.serveG.check()
+ if n == 0 {
+ return
+ }
+ if n < 0 {
+ panic("negative update")
+ }
+ var streamID uint32
+ if st != nil {
+ streamID = st.id
+ }
+ sc.writeFrame(frameWriteMsg{
+ write: writeWindowUpdate{streamID: streamID, n: uint32(n)},
+ stream: st,
+ })
+ var ok bool
+ if st == nil {
+ ok = sc.inflow.add(n)
+ } else {
+ ok = st.inflow.add(n)
+ }
+ if !ok {
+ panic("internal error; sent too many window updates without decrements?")
+ }
+}
+
+type requestBody struct {
+ stream *stream
+ conn *serverConn
+ closed bool
+ pipe *pipe // non-nil if we have a HTTP entity message body
+ needsContinue bool // need to send a 100-continue
+}
+
+func (b *requestBody) Close() error {
+ if b.pipe != nil {
+ b.pipe.Close(errClosedBody)
+ }
+ b.closed = true
+ return nil
+}
+
+func (b *requestBody) Read(p []byte) (n int, err error) {
+ if b.needsContinue {
+ b.needsContinue = false
+ b.conn.write100ContinueHeaders(b.stream)
+ }
+ if b.pipe == nil {
+ return 0, io.EOF
+ }
+ n, err = b.pipe.Read(p)
+ if n > 0 {
+ b.conn.noteBodyReadFromHandler(b.stream, n)
+ }
+ return
+}
+
+// responseWriter is the http.ResponseWriter implementation. It's
+// intentionally small (1 pointer wide) to minimize garbage. The
+// responseWriterState pointer inside is zeroed at the end of a
+// request (in handlerDone) and calls on the responseWriter thereafter
+// simply crash (caller's mistake), but the much larger responseWriterState
+// and buffers are reused between multiple requests.
+type responseWriter struct {
+ rws *responseWriterState
+}
+
+// Optional http.ResponseWriter interfaces implemented.
+var (
+ _ http.CloseNotifier = (*responseWriter)(nil)
+ _ http.Flusher = (*responseWriter)(nil)
+ _ stringWriter = (*responseWriter)(nil)
+)
+
+type responseWriterState struct {
+ // immutable within a request:
+ stream *stream
+ req *http.Request
+ body *requestBody // to close at end of request, if DATA frames didn't
+ conn *serverConn
+
+ // TODO: adjust buffer writing sizes based on server config, frame size updates from peer, etc
+ bw *bufio.Writer // writing to a chunkWriter{this *responseWriterState}
+
+ // mutated by http.Handler goroutine:
+ handlerHeader http.Header // nil until called
+ snapHeader http.Header // snapshot of handlerHeader at WriteHeader time
+ status int // status code passed to WriteHeader
+ wroteHeader bool // WriteHeader called (explicitly or implicitly). Not necessarily sent to user yet.
+ sentHeader bool // have we sent the header frame?
+ handlerDone bool // handler has finished
+ curWrite writeData
+ frameWriteCh chan error // re-used whenever we need to block on a frame being written
+
+ closeNotifierMu sync.Mutex // guards closeNotifierCh
+ closeNotifierCh chan bool // nil until first used
+}
+
+type chunkWriter struct{ rws *responseWriterState }
+
+func (cw chunkWriter) Write(p []byte) (n int, err error) { return cw.rws.writeChunk(p) }
+
+// writeChunk writes chunks from the bufio.Writer. But because
+// bufio.Writer may bypass its chunking, sometimes p may be
+// arbitrarily large.
+//
+// writeChunk is also responsible (on the first chunk) for sending the
+// HEADER response.
+func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
+ if !rws.wroteHeader {
+ rws.writeHeader(200)
+ }
+ if !rws.sentHeader {
+ rws.sentHeader = true
+ var ctype, clen string // implicit ones, if we can calculate it
+ if rws.handlerDone && rws.snapHeader.Get("Content-Length") == "" {
+ clen = strconv.Itoa(len(p))
+ }
+ if rws.snapHeader.Get("Content-Type") == "" {
+ ctype = http.DetectContentType(p)
+ }
+ endStream := rws.handlerDone && len(p) == 0
+ rws.conn.writeHeaders(rws.stream, &writeResHeaders{
+ streamID: rws.stream.id,
+ httpResCode: rws.status,
+ h: rws.snapHeader,
+ endStream: endStream,
+ contentType: ctype,
+ contentLength: clen,
+ }, rws.frameWriteCh)
+ if endStream {
+ return 0, nil
+ }
+ }
+ if len(p) == 0 && !rws.handlerDone {
+ return 0, nil
+ }
+ curWrite := &rws.curWrite
+ curWrite.streamID = rws.stream.id
+ curWrite.p = p
+ curWrite.endStream = rws.handlerDone
+ if err := rws.conn.writeDataFromHandler(rws.stream, curWrite, rws.frameWriteCh); err != nil {
+ return 0, err
+ }
+ return len(p), nil
+}
+
+func (w *responseWriter) Flush() {
+ rws := w.rws
+ if rws == nil {
+ panic("Header called after Handler finished")
+ }
+ if rws.bw.Buffered() > 0 {
+ if err := rws.bw.Flush(); err != nil {
+ // Ignore the error. The frame writer already knows.
+ return
+ }
+ } else {
+ // The bufio.Writer won't call chunkWriter.Write
+ // (writeChunk with zero bytes, so we have to do it
+ // ourselves to force the HTTP response header and/or
+ // final DATA frame (with END_STREAM) to be sent.
+ rws.writeChunk(nil)
+ }
+}
+
+func (w *responseWriter) CloseNotify() <-chan bool {
+ rws := w.rws
+ if rws == nil {
+ panic("CloseNotify called after Handler finished")
+ }
+ rws.closeNotifierMu.Lock()
+ ch := rws.closeNotifierCh
+ if ch == nil {
+ ch = make(chan bool, 1)
+ rws.closeNotifierCh = ch
+ go func() {
+ rws.stream.cw.Wait() // wait for close
+ ch <- true
+ }()
+ }
+ rws.closeNotifierMu.Unlock()
+ return ch
+}
+
+func (w *responseWriter) Header() http.Header {
+ rws := w.rws
+ if rws == nil {
+ panic("Header called after Handler finished")
+ }
+ if rws.handlerHeader == nil {
+ rws.handlerHeader = make(http.Header)
+ }
+ return rws.handlerHeader
+}
+
+func (w *responseWriter) WriteHeader(code int) {
+ rws := w.rws
+ if rws == nil {
+ panic("WriteHeader called after Handler finished")
+ }
+ rws.writeHeader(code)
+}
+
+func (rws *responseWriterState) writeHeader(code int) {
+ if !rws.wroteHeader {
+ rws.wroteHeader = true
+ rws.status = code
+ if len(rws.handlerHeader) > 0 {
+ rws.snapHeader = cloneHeader(rws.handlerHeader)
+ }
+ }
+}
+
+func cloneHeader(h http.Header) http.Header {
+ h2 := make(http.Header, len(h))
+ for k, vv := range h {
+ vv2 := make([]string, len(vv))
+ copy(vv2, vv)
+ h2[k] = vv2
+ }
+ return h2
+}
+
+// The Life Of A Write is like this:
+//
+// * Handler calls w.Write or w.WriteString ->
+// * -> rws.bw (*bufio.Writer) ->
+// * (Handler migth call Flush)
+// * -> chunkWriter{rws}
+// * -> responseWriterState.writeChunk(p []byte)
+// * -> responseWriterState.writeChunk (most of the magic; see comment there)
+func (w *responseWriter) Write(p []byte) (n int, err error) {
+ return w.write(len(p), p, "")
+}
+
+func (w *responseWriter) WriteString(s string) (n int, err error) {
+ return w.write(len(s), nil, s)
+}
+
+// either dataB or dataS is non-zero.
+func (w *responseWriter) write(lenData int, dataB []byte, dataS string) (n int, err error) {
+ rws := w.rws
+ if rws == nil {
+ panic("Write called after Handler finished")
+ }
+ if !rws.wroteHeader {
+ w.WriteHeader(200)
+ }
+ if dataB != nil {
+ return rws.bw.Write(dataB)
+ } else {
+ return rws.bw.WriteString(dataS)
+ }
+}
+
+func (w *responseWriter) handlerDone() {
+ rws := w.rws
+ if rws == nil {
+ panic("handlerDone called twice")
+ }
+ rws.handlerDone = true
+ w.Flush()
+ w.rws = nil
+ responseWriterStatePool.Put(rws)
+}
diff --git a/http2/server_test.go b/http2/server_test.go
new file mode 100644
index 0000000..5876a6a
--- /dev/null
+++ b/http2/server_test.go
@@ -0,0 +1,2252 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "bytes"
+ "crypto/tls"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "log"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "reflect"
+ "runtime"
+ "strconv"
+ "strings"
+ "sync"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "golang.org/x/net/http2/hpack"
+)
+
+var stderrVerbose = flag.Bool("stderr_verbose", false, "Mirror verbosity to stderr, unbuffered")
+
+type serverTester struct {
+ cc net.Conn // client conn
+ t testing.TB
+ ts *httptest.Server
+ fr *Framer
+ logBuf *bytes.Buffer
+ logFilter []string // substrings to filter out
+ scMu sync.Mutex // guards sc
+ sc *serverConn
+
+ // writing headers:
+ headerBuf bytes.Buffer
+ hpackEnc *hpack.Encoder
+
+ // reading frames:
+ frc chan Frame
+ frErrc chan error
+ readTimer *time.Timer
+}
+
+func init() {
+ testHookOnPanicMu = new(sync.Mutex)
+}
+
+func resetHooks() {
+ testHookOnPanicMu.Lock()
+ testHookOnPanic = nil
+ testHookOnPanicMu.Unlock()
+}
+
+type serverTesterOpt string
+
+var optOnlyServer = serverTesterOpt("only_server")
+
+func newServerTester(t testing.TB, handler http.HandlerFunc, opts ...interface{}) *serverTester {
+ resetHooks()
+
+ logBuf := new(bytes.Buffer)
+ ts := httptest.NewUnstartedServer(handler)
+
+ tlsConfig := &tls.Config{
+ InsecureSkipVerify: true,
+ // The h2-14 is temporary, until curl is updated. (as used by unit tests
+ // in Docker)
+ NextProtos: []string{NextProtoTLS, "h2-14"},
+ }
+
+ onlyServer := false
+ for _, opt := range opts {
+ switch v := opt.(type) {
+ case func(*tls.Config):
+ v(tlsConfig)
+ case func(*httptest.Server):
+ v(ts)
+ case serverTesterOpt:
+ onlyServer = (v == optOnlyServer)
+ default:
+ t.Fatalf("unknown newServerTester option type %T", v)
+ }
+ }
+
+ ConfigureServer(ts.Config, &Server{})
+
+ st := &serverTester{
+ t: t,
+ ts: ts,
+ logBuf: logBuf,
+ frc: make(chan Frame, 1),
+ frErrc: make(chan error, 1),
+ }
+ st.hpackEnc = hpack.NewEncoder(&st.headerBuf)
+
+ var stderrv io.Writer = ioutil.Discard
+ if *stderrVerbose {
+ stderrv = os.Stderr
+ }
+
+ ts.TLS = ts.Config.TLSConfig // the httptest.Server has its own copy of this TLS config
+ ts.Config.ErrorLog = log.New(io.MultiWriter(stderrv, twriter{t: t, st: st}, logBuf), "", log.LstdFlags)
+ ts.StartTLS()
+
+ if VerboseLogs {
+ t.Logf("Running test server at: %s", ts.URL)
+ }
+ testHookGetServerConn = func(v *serverConn) {
+ st.scMu.Lock()
+ defer st.scMu.Unlock()
+ st.sc = v
+ st.sc.testHookCh = make(chan func())
+ }
+ log.SetOutput(io.MultiWriter(stderrv, twriter{t: t, st: st}))
+ if !onlyServer {
+ cc, err := tls.Dial("tcp", ts.Listener.Addr().String(), tlsConfig)
+ if err != nil {
+ t.Fatal(err)
+ }
+ st.cc = cc
+ st.fr = NewFramer(cc, cc)
+ }
+
+ return st
+}
+
+func (st *serverTester) closeConn() {
+ st.scMu.Lock()
+ defer st.scMu.Unlock()
+ st.sc.conn.Close()
+}
+
+func (st *serverTester) addLogFilter(phrase string) {
+ st.logFilter = append(st.logFilter, phrase)
+}
+
+func (st *serverTester) stream(id uint32) *stream {
+ ch := make(chan *stream, 1)
+ st.sc.testHookCh <- func() {
+ ch <- st.sc.streams[id]
+ }
+ return <-ch
+}
+
+func (st *serverTester) streamState(id uint32) streamState {
+ ch := make(chan streamState, 1)
+ st.sc.testHookCh <- func() {
+ state, _ := st.sc.state(id)
+ ch <- state
+ }
+ return <-ch
+}
+
+func (st *serverTester) Close() {
+ st.ts.Close()
+ if st.cc != nil {
+ st.cc.Close()
+ }
+ log.SetOutput(os.Stderr)
+}
+
+// greet initiates the client's HTTP/2 connection into a state where
+// frames may be sent.
+func (st *serverTester) greet() {
+ st.writePreface()
+ st.writeInitialSettings()
+ st.wantSettings()
+ st.writeSettingsAck()
+ st.wantSettingsAck()
+}
+
+func (st *serverTester) writePreface() {
+ n, err := st.cc.Write(clientPreface)
+ if err != nil {
+ st.t.Fatalf("Error writing client preface: %v", err)
+ }
+ if n != len(clientPreface) {
+ st.t.Fatalf("Writing client preface, wrote %d bytes; want %d", n, len(clientPreface))
+ }
+}
+
+func (st *serverTester) writeInitialSettings() {
+ if err := st.fr.WriteSettings(); err != nil {
+ st.t.Fatalf("Error writing initial SETTINGS frame from client to server: %v", err)
+ }
+}
+
+func (st *serverTester) writeSettingsAck() {
+ if err := st.fr.WriteSettingsAck(); err != nil {
+ st.t.Fatalf("Error writing ACK of server's SETTINGS: %v", err)
+ }
+}
+
+func (st *serverTester) writeHeaders(p HeadersFrameParam) {
+ if err := st.fr.WriteHeaders(p); err != nil {
+ st.t.Fatalf("Error writing HEADERS: %v", err)
+ }
+}
+
+func (st *serverTester) encodeHeaderField(k, v string) {
+ err := st.hpackEnc.WriteField(hpack.HeaderField{Name: k, Value: v})
+ if err != nil {
+ st.t.Fatalf("HPACK encoding error for %q/%q: %v", k, v, err)
+ }
+}
+
+// encodeHeader encodes headers and returns their HPACK bytes. headers
+// must contain an even number of key/value pairs. There may be
+// multiple pairs for keys (e.g. "cookie"). The :method, :path, and
+// :scheme headers default to GET, / and https.
+func (st *serverTester) encodeHeader(headers ...string) []byte {
+ if len(headers)%2 == 1 {
+ panic("odd number of kv args")
+ }
+
+ st.headerBuf.Reset()
+
+ if len(headers) == 0 {
+ // Fast path, mostly for benchmarks, so test code doesn't pollute
+ // profiles when we're looking to improve server allocations.
+ st.encodeHeaderField(":method", "GET")
+ st.encodeHeaderField(":path", "/")
+ st.encodeHeaderField(":scheme", "https")
+ return st.headerBuf.Bytes()
+ }
+
+ if len(headers) == 2 && headers[0] == ":method" {
+ // Another fast path for benchmarks.
+ st.encodeHeaderField(":method", headers[1])
+ st.encodeHeaderField(":path", "/")
+ st.encodeHeaderField(":scheme", "https")
+ return st.headerBuf.Bytes()
+ }
+
+ pseudoCount := map[string]int{}
+ keys := []string{":method", ":path", ":scheme"}
+ vals := map[string][]string{
+ ":method": {"GET"},
+ ":path": {"/"},
+ ":scheme": {"https"},
+ }
+ for len(headers) > 0 {
+ k, v := headers[0], headers[1]
+ headers = headers[2:]
+ if _, ok := vals[k]; !ok {
+ keys = append(keys, k)
+ }
+ if strings.HasPrefix(k, ":") {
+ pseudoCount[k]++
+ if pseudoCount[k] == 1 {
+ vals[k] = []string{v}
+ } else {
+ // Allows testing of invalid headers w/ dup pseudo fields.
+ vals[k] = append(vals[k], v)
+ }
+ } else {
+ vals[k] = append(vals[k], v)
+ }
+ }
+ st.headerBuf.Reset()
+ for _, k := range keys {
+ for _, v := range vals[k] {
+ st.encodeHeaderField(k, v)
+ }
+ }
+ return st.headerBuf.Bytes()
+}
+
+// bodylessReq1 writes a HEADERS frames with StreamID 1 and EndStream and EndHeaders set.
+func (st *serverTester) bodylessReq1(headers ...string) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(headers...),
+ EndStream: true,
+ EndHeaders: true,
+ })
+}
+
+func (st *serverTester) writeData(streamID uint32, endStream bool, data []byte) {
+ if err := st.fr.WriteData(streamID, endStream, data); err != nil {
+ st.t.Fatalf("Error writing DATA: %v", err)
+ }
+}
+
+func (st *serverTester) readFrame() (Frame, error) {
+ go func() {
+ fr, err := st.fr.ReadFrame()
+ if err != nil {
+ st.frErrc <- err
+ } else {
+ st.frc <- fr
+ }
+ }()
+ t := st.readTimer
+ if t == nil {
+ t = time.NewTimer(2 * time.Second)
+ st.readTimer = t
+ }
+ t.Reset(2 * time.Second)
+ defer t.Stop()
+ select {
+ case f := <-st.frc:
+ return f, nil
+ case err := <-st.frErrc:
+ return nil, err
+ case <-t.C:
+ return nil, errors.New("timeout waiting for frame")
+ }
+}
+
+func (st *serverTester) wantHeaders() *HeadersFrame {
+ f, err := st.readFrame()
+ if err != nil {
+ st.t.Fatalf("Error while expecting a HEADERS frame: %v", err)
+ }
+ hf, ok := f.(*HeadersFrame)
+ if !ok {
+ st.t.Fatalf("got a %T; want *HeadersFrame", f)
+ }
+ return hf
+}
+
+func (st *serverTester) wantContinuation() *ContinuationFrame {
+ f, err := st.readFrame()
+ if err != nil {
+ st.t.Fatalf("Error while expecting a CONTINUATION frame: %v", err)
+ }
+ cf, ok := f.(*ContinuationFrame)
+ if !ok {
+ st.t.Fatalf("got a %T; want *ContinuationFrame", f)
+ }
+ return cf
+}
+
+func (st *serverTester) wantData() *DataFrame {
+ f, err := st.readFrame()
+ if err != nil {
+ st.t.Fatalf("Error while expecting a DATA frame: %v", err)
+ }
+ df, ok := f.(*DataFrame)
+ if !ok {
+ st.t.Fatalf("got a %T; want *DataFrame", f)
+ }
+ return df
+}
+
+func (st *serverTester) wantSettings() *SettingsFrame {
+ f, err := st.readFrame()
+ if err != nil {
+ st.t.Fatalf("Error while expecting a SETTINGS frame: %v", err)
+ }
+ sf, ok := f.(*SettingsFrame)
+ if !ok {
+ st.t.Fatalf("got a %T; want *SettingsFrame", f)
+ }
+ return sf
+}
+
+func (st *serverTester) wantPing() *PingFrame {
+ f, err := st.readFrame()
+ if err != nil {
+ st.t.Fatalf("Error while expecting a PING frame: %v", err)
+ }
+ pf, ok := f.(*PingFrame)
+ if !ok {
+ st.t.Fatalf("got a %T; want *PingFrame", f)
+ }
+ return pf
+}
+
+func (st *serverTester) wantGoAway() *GoAwayFrame {
+ f, err := st.readFrame()
+ if err != nil {
+ st.t.Fatalf("Error while expecting a GOAWAY frame: %v", err)
+ }
+ gf, ok := f.(*GoAwayFrame)
+ if !ok {
+ st.t.Fatalf("got a %T; want *GoAwayFrame", f)
+ }
+ return gf
+}
+
+func (st *serverTester) wantRSTStream(streamID uint32, errCode ErrCode) {
+ f, err := st.readFrame()
+ if err != nil {
+ st.t.Fatalf("Error while expecting an RSTStream frame: %v", err)
+ }
+ rs, ok := f.(*RSTStreamFrame)
+ if !ok {
+ st.t.Fatalf("got a %T; want *RSTStreamFrame", f)
+ }
+ if rs.FrameHeader.StreamID != streamID {
+ st.t.Fatalf("RSTStream StreamID = %d; want %d", rs.FrameHeader.StreamID, streamID)
+ }
+ if rs.ErrCode != errCode {
+ st.t.Fatalf("RSTStream ErrCode = %d (%s); want %d (%s)", rs.ErrCode, rs.ErrCode, errCode, errCode)
+ }
+}
+
+func (st *serverTester) wantWindowUpdate(streamID, incr uint32) {
+ f, err := st.readFrame()
+ if err != nil {
+ st.t.Fatalf("Error while expecting a WINDOW_UPDATE frame: %v", err)
+ }
+ wu, ok := f.(*WindowUpdateFrame)
+ if !ok {
+ st.t.Fatalf("got a %T; want *WindowUpdateFrame", f)
+ }
+ if wu.FrameHeader.StreamID != streamID {
+ st.t.Fatalf("WindowUpdate StreamID = %d; want %d", wu.FrameHeader.StreamID, streamID)
+ }
+ if wu.Increment != incr {
+ st.t.Fatalf("WindowUpdate increment = %d; want %d", wu.Increment, incr)
+ }
+}
+
+func (st *serverTester) wantSettingsAck() {
+ f, err := st.readFrame()
+ if err != nil {
+ st.t.Fatal(err)
+ }
+ sf, ok := f.(*SettingsFrame)
+ if !ok {
+ st.t.Fatalf("Wanting a settings ACK, received a %T", f)
+ }
+ if !sf.Header().Flags.Has(FlagSettingsAck) {
+ st.t.Fatal("Settings Frame didn't have ACK set")
+ }
+
+}
+
+func TestServer(t *testing.T) {
+ gotReq := make(chan bool, 1)
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Foo", "Bar")
+ gotReq <- true
+ })
+ defer st.Close()
+
+ covers("3.5", `
+ The server connection preface consists of a potentially empty
+ SETTINGS frame ([SETTINGS]) that MUST be the first frame the
+ server sends in the HTTP/2 connection.
+ `)
+
+ st.writePreface()
+ st.writeInitialSettings()
+ st.wantSettings()
+ st.writeSettingsAck()
+ st.wantSettingsAck()
+
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(),
+ EndStream: true, // no DATA frames
+ EndHeaders: true,
+ })
+
+ select {
+ case <-gotReq:
+ case <-time.After(2 * time.Second):
+ t.Error("timeout waiting for request")
+ }
+}
+
+func TestServer_Request_Get(t *testing.T) {
+ testServerRequest(t, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader("foo-bar", "some-value"),
+ EndStream: true, // no DATA frames
+ EndHeaders: true,
+ })
+ }, func(r *http.Request) {
+ if r.Method != "GET" {
+ t.Errorf("Method = %q; want GET", r.Method)
+ }
+ if r.URL.Path != "/" {
+ t.Errorf("URL.Path = %q; want /", r.URL.Path)
+ }
+ if r.ContentLength != 0 {
+ t.Errorf("ContentLength = %v; want 0", r.ContentLength)
+ }
+ if r.Close {
+ t.Error("Close = true; want false")
+ }
+ if !strings.Contains(r.RemoteAddr, ":") {
+ t.Errorf("RemoteAddr = %q; want something with a colon", r.RemoteAddr)
+ }
+ if r.Proto != "HTTP/2.0" || r.ProtoMajor != 2 || r.ProtoMinor != 0 {
+ t.Errorf("Proto = %q Major=%v,Minor=%v; want HTTP/2.0", r.Proto, r.ProtoMajor, r.ProtoMinor)
+ }
+ wantHeader := http.Header{
+ "Foo-Bar": []string{"some-value"},
+ }
+ if !reflect.DeepEqual(r.Header, wantHeader) {
+ t.Errorf("Header = %#v; want %#v", r.Header, wantHeader)
+ }
+ if n, err := r.Body.Read([]byte(" ")); err != io.EOF || n != 0 {
+ t.Errorf("Read = %d, %v; want 0, EOF", n, err)
+ }
+ })
+}
+
+func TestServer_Request_Get_PathSlashes(t *testing.T) {
+ testServerRequest(t, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(":path", "/%2f/"),
+ EndStream: true, // no DATA frames
+ EndHeaders: true,
+ })
+ }, func(r *http.Request) {
+ if r.RequestURI != "/%2f/" {
+ t.Errorf("RequestURI = %q; want /%%2f/", r.RequestURI)
+ }
+ if r.URL.Path != "///" {
+ t.Errorf("URL.Path = %q; want ///", r.URL.Path)
+ }
+ })
+}
+
+// TODO: add a test with EndStream=true on the HEADERS but setting a
+// Content-Length anyway. Should we just omit it and force it to
+// zero?
+
+func TestServer_Request_Post_NoContentLength_EndStream(t *testing.T) {
+ testServerRequest(t, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(":method", "POST"),
+ EndStream: true,
+ EndHeaders: true,
+ })
+ }, func(r *http.Request) {
+ if r.Method != "POST" {
+ t.Errorf("Method = %q; want POST", r.Method)
+ }
+ if r.ContentLength != 0 {
+ t.Errorf("ContentLength = %v; want 0", r.ContentLength)
+ }
+ if n, err := r.Body.Read([]byte(" ")); err != io.EOF || n != 0 {
+ t.Errorf("Read = %d, %v; want 0, EOF", n, err)
+ }
+ })
+}
+
+func TestServer_Request_Post_Body_ImmediateEOF(t *testing.T) {
+ testBodyContents(t, -1, "", func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(":method", "POST"),
+ EndStream: false, // to say DATA frames are coming
+ EndHeaders: true,
+ })
+ st.writeData(1, true, nil) // just kidding. empty body.
+ })
+}
+
+func TestServer_Request_Post_Body_OneData(t *testing.T) {
+ const content = "Some content"
+ testBodyContents(t, -1, content, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(":method", "POST"),
+ EndStream: false, // to say DATA frames are coming
+ EndHeaders: true,
+ })
+ st.writeData(1, true, []byte(content))
+ })
+}
+
+func TestServer_Request_Post_Body_TwoData(t *testing.T) {
+ const content = "Some content"
+ testBodyContents(t, -1, content, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(":method", "POST"),
+ EndStream: false, // to say DATA frames are coming
+ EndHeaders: true,
+ })
+ st.writeData(1, false, []byte(content[:5]))
+ st.writeData(1, true, []byte(content[5:]))
+ })
+}
+
+func TestServer_Request_Post_Body_ContentLength_Correct(t *testing.T) {
+ const content = "Some content"
+ testBodyContents(t, int64(len(content)), content, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(
+ ":method", "POST",
+ "content-length", strconv.Itoa(len(content)),
+ ),
+ EndStream: false, // to say DATA frames are coming
+ EndHeaders: true,
+ })
+ st.writeData(1, true, []byte(content))
+ })
+}
+
+func TestServer_Request_Post_Body_ContentLength_TooLarge(t *testing.T) {
+ testBodyContentsFail(t, 3, "request declared a Content-Length of 3 but only wrote 2 bytes",
+ func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(
+ ":method", "POST",
+ "content-length", "3",
+ ),
+ EndStream: false, // to say DATA frames are coming
+ EndHeaders: true,
+ })
+ st.writeData(1, true, []byte("12"))
+ })
+}
+
+func TestServer_Request_Post_Body_ContentLength_TooSmall(t *testing.T) {
+ testBodyContentsFail(t, 4, "sender tried to send more than declared Content-Length of 4 bytes",
+ func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(
+ ":method", "POST",
+ "content-length", "4",
+ ),
+ EndStream: false, // to say DATA frames are coming
+ EndHeaders: true,
+ })
+ st.writeData(1, true, []byte("12345"))
+ })
+}
+
+func testBodyContents(t *testing.T, wantContentLength int64, wantBody string, write func(st *serverTester)) {
+ testServerRequest(t, write, func(r *http.Request) {
+ if r.Method != "POST" {
+ t.Errorf("Method = %q; want POST", r.Method)
+ }
+ if r.ContentLength != wantContentLength {
+ t.Errorf("ContentLength = %v; want %d", r.ContentLength, wantContentLength)
+ }
+ all, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(all) != wantBody {
+ t.Errorf("Read = %q; want %q", all, wantBody)
+ }
+ if err := r.Body.Close(); err != nil {
+ t.Fatalf("Close: %v", err)
+ }
+ })
+}
+
+func testBodyContentsFail(t *testing.T, wantContentLength int64, wantReadError string, write func(st *serverTester)) {
+ testServerRequest(t, write, func(r *http.Request) {
+ if r.Method != "POST" {
+ t.Errorf("Method = %q; want POST", r.Method)
+ }
+ if r.ContentLength != wantContentLength {
+ t.Errorf("ContentLength = %v; want %d", r.ContentLength, wantContentLength)
+ }
+ all, err := ioutil.ReadAll(r.Body)
+ if err == nil {
+ t.Fatalf("expected an error (%q) reading from the body. Successfully read %q instead.",
+ wantReadError, all)
+ }
+ if !strings.Contains(err.Error(), wantReadError) {
+ t.Fatalf("Body.Read = %v; want substring %q", err, wantReadError)
+ }
+ if err := r.Body.Close(); err != nil {
+ t.Fatalf("Close: %v", err)
+ }
+ })
+}
+
+// Using a Host header, instead of :authority
+func TestServer_Request_Get_Host(t *testing.T) {
+ const host = "example.com"
+ testServerRequest(t, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader("host", host),
+ EndStream: true,
+ EndHeaders: true,
+ })
+ }, func(r *http.Request) {
+ if r.Host != host {
+ t.Errorf("Host = %q; want %q", r.Host, host)
+ }
+ })
+}
+
+// Using an :authority pseudo-header, instead of Host
+func TestServer_Request_Get_Authority(t *testing.T) {
+ const host = "example.com"
+ testServerRequest(t, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(":authority", host),
+ EndStream: true,
+ EndHeaders: true,
+ })
+ }, func(r *http.Request) {
+ if r.Host != host {
+ t.Errorf("Host = %q; want %q", r.Host, host)
+ }
+ })
+}
+
+func TestServer_Request_WithContinuation(t *testing.T) {
+ wantHeader := http.Header{
+ "Foo-One": []string{"value-one"},
+ "Foo-Two": []string{"value-two"},
+ "Foo-Three": []string{"value-three"},
+ }
+ testServerRequest(t, func(st *serverTester) {
+ fullHeaders := st.encodeHeader(
+ "foo-one", "value-one",
+ "foo-two", "value-two",
+ "foo-three", "value-three",
+ )
+ remain := fullHeaders
+ chunks := 0
+ for len(remain) > 0 {
+ const maxChunkSize = 5
+ chunk := remain
+ if len(chunk) > maxChunkSize {
+ chunk = chunk[:maxChunkSize]
+ }
+ remain = remain[len(chunk):]
+
+ if chunks == 0 {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: chunk,
+ EndStream: true, // no DATA frames
+ EndHeaders: false, // we'll have continuation frames
+ })
+ } else {
+ err := st.fr.WriteContinuation(1, len(remain) == 0, chunk)
+ if err != nil {
+ t.Fatal(err)
+ }
+ }
+ chunks++
+ }
+ if chunks < 2 {
+ t.Fatal("too few chunks")
+ }
+ }, func(r *http.Request) {
+ if !reflect.DeepEqual(r.Header, wantHeader) {
+ t.Errorf("Header = %#v; want %#v", r.Header, wantHeader)
+ }
+ })
+}
+
+// Concatenated cookie headers. ("8.1.2.5 Compressing the Cookie Header Field")
+func TestServer_Request_CookieConcat(t *testing.T) {
+ const host = "example.com"
+ testServerRequest(t, func(st *serverTester) {
+ st.bodylessReq1(
+ ":authority", host,
+ "cookie", "a=b",
+ "cookie", "c=d",
+ "cookie", "e=f",
+ )
+ }, func(r *http.Request) {
+ const want = "a=b; c=d; e=f"
+ if got := r.Header.Get("Cookie"); got != want {
+ t.Errorf("Cookie = %q; want %q", got, want)
+ }
+ })
+}
+
+func TestServer_Request_Reject_CapitalHeader(t *testing.T) {
+ testRejectRequest(t, func(st *serverTester) { st.bodylessReq1("UPPER", "v") })
+}
+
+func TestServer_Request_Reject_Pseudo_Missing_method(t *testing.T) {
+ testRejectRequest(t, func(st *serverTester) { st.bodylessReq1(":method", "") })
+}
+
+func TestServer_Request_Reject_Pseudo_ExactlyOne(t *testing.T) {
+ // 8.1.2.3 Request Pseudo-Header Fields
+ // "All HTTP/2 requests MUST include exactly one valid value" ...
+ testRejectRequest(t, func(st *serverTester) {
+ st.addLogFilter("duplicate pseudo-header")
+ st.bodylessReq1(":method", "GET", ":method", "POST")
+ })
+}
+
+func TestServer_Request_Reject_Pseudo_AfterRegular(t *testing.T) {
+ // 8.1.2.3 Request Pseudo-Header Fields
+ // "All pseudo-header fields MUST appear in the header block
+ // before regular header fields. Any request or response that
+ // contains a pseudo-header field that appears in a header
+ // block after a regular header field MUST be treated as
+ // malformed (Section 8.1.2.6)."
+ testRejectRequest(t, func(st *serverTester) {
+ st.addLogFilter("pseudo-header after regular header")
+ var buf bytes.Buffer
+ enc := hpack.NewEncoder(&buf)
+ enc.WriteField(hpack.HeaderField{Name: ":method", Value: "GET"})
+ enc.WriteField(hpack.HeaderField{Name: "regular", Value: "foobar"})
+ enc.WriteField(hpack.HeaderField{Name: ":path", Value: "/"})
+ enc.WriteField(hpack.HeaderField{Name: ":scheme", Value: "https"})
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: buf.Bytes(),
+ EndStream: true,
+ EndHeaders: true,
+ })
+ })
+}
+
+func TestServer_Request_Reject_Pseudo_Missing_path(t *testing.T) {
+ testRejectRequest(t, func(st *serverTester) { st.bodylessReq1(":path", "") })
+}
+
+func TestServer_Request_Reject_Pseudo_Missing_scheme(t *testing.T) {
+ testRejectRequest(t, func(st *serverTester) { st.bodylessReq1(":scheme", "") })
+}
+
+func TestServer_Request_Reject_Pseudo_scheme_invalid(t *testing.T) {
+ testRejectRequest(t, func(st *serverTester) { st.bodylessReq1(":scheme", "bogus") })
+}
+
+func TestServer_Request_Reject_Pseudo_Unknown(t *testing.T) {
+ testRejectRequest(t, func(st *serverTester) {
+ st.addLogFilter(`invalid pseudo-header ":unknown_thing"`)
+ st.bodylessReq1(":unknown_thing", "")
+ })
+}
+
+func testRejectRequest(t *testing.T, send func(*serverTester)) {
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("server request made it to handler; should've been rejected")
+ })
+ defer st.Close()
+
+ st.greet()
+ send(st)
+ st.wantRSTStream(1, ErrCodeProtocol)
+}
+
+func TestServer_Ping(t *testing.T) {
+ st := newServerTester(t, nil)
+ defer st.Close()
+ st.greet()
+
+ // Server should ignore this one, since it has ACK set.
+ ackPingData := [8]byte{1, 2, 4, 8, 16, 32, 64, 128}
+ if err := st.fr.WritePing(true, ackPingData); err != nil {
+ t.Fatal(err)
+ }
+
+ // But the server should reply to this one, since ACK is false.
+ pingData := [8]byte{1, 2, 3, 4, 5, 6, 7, 8}
+ if err := st.fr.WritePing(false, pingData); err != nil {
+ t.Fatal(err)
+ }
+
+ pf := st.wantPing()
+ if !pf.Flags.Has(FlagPingAck) {
+ t.Error("response ping doesn't have ACK set")
+ }
+ if pf.Data != pingData {
+ t.Errorf("response ping has data %q; want %q", pf.Data, pingData)
+ }
+}
+
+func TestServer_RejectsLargeFrames(t *testing.T) {
+ st := newServerTester(t, nil)
+ defer st.Close()
+ st.greet()
+
+ // Write too large of a frame (too large by one byte)
+ // We ignore the return value because it's expected that the server
+ // will only read the first 9 bytes (the headre) and then disconnect.
+ st.fr.WriteRawFrame(0xff, 0, 0, make([]byte, defaultMaxReadFrameSize+1))
+
+ gf := st.wantGoAway()
+ if gf.ErrCode != ErrCodeFrameSize {
+ t.Errorf("GOAWAY err = %v; want %v", gf.ErrCode, ErrCodeFrameSize)
+ }
+}
+
+func TestServer_Handler_Sends_WindowUpdate(t *testing.T) {
+ puppet := newHandlerPuppet()
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ puppet.act(w, r)
+ })
+ defer st.Close()
+ defer puppet.done()
+
+ st.greet()
+
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(":method", "POST"),
+ EndStream: false, // data coming
+ EndHeaders: true,
+ })
+ st.writeData(1, false, []byte("abcdef"))
+ puppet.do(readBodyHandler(t, "abc"))
+ st.wantWindowUpdate(0, 3)
+ st.wantWindowUpdate(1, 3)
+
+ puppet.do(readBodyHandler(t, "def"))
+ st.wantWindowUpdate(0, 3)
+ st.wantWindowUpdate(1, 3)
+
+ st.writeData(1, true, []byte("ghijkl")) // END_STREAM here
+ puppet.do(readBodyHandler(t, "ghi"))
+ puppet.do(readBodyHandler(t, "jkl"))
+ st.wantWindowUpdate(0, 3)
+ st.wantWindowUpdate(0, 3) // no more stream-level, since END_STREAM
+}
+
+func TestServer_Send_GoAway_After_Bogus_WindowUpdate(t *testing.T) {
+ st := newServerTester(t, nil)
+ defer st.Close()
+ st.greet()
+ if err := st.fr.WriteWindowUpdate(0, 1<<31-1); err != nil {
+ t.Fatal(err)
+ }
+ gf := st.wantGoAway()
+ if gf.ErrCode != ErrCodeFlowControl {
+ t.Errorf("GOAWAY err = %v; want %v", gf.ErrCode, ErrCodeFlowControl)
+ }
+ if gf.LastStreamID != 0 {
+ t.Errorf("GOAWAY last stream ID = %v; want %v", gf.LastStreamID, 0)
+ }
+}
+
+func TestServer_Send_RstStream_After_Bogus_WindowUpdate(t *testing.T) {
+ inHandler := make(chan bool)
+ blockHandler := make(chan bool)
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ inHandler <- true
+ <-blockHandler
+ })
+ defer st.Close()
+ defer close(blockHandler)
+ st.greet()
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1,
+ BlockFragment: st.encodeHeader(":method", "POST"),
+ EndStream: false, // keep it open
+ EndHeaders: true,
+ })
+ <-inHandler
+ // Send a bogus window update:
+ if err := st.fr.WriteWindowUpdate(1, 1<<31-1); err != nil {
+ t.Fatal(err)
+ }
+ st.wantRSTStream(1, ErrCodeFlowControl)
+}
+
+// testServerPostUnblock sends a hanging POST with unsent data to handler,
+// then runs fn once in the handler, and verifies that the error returned from
+// handler is acceptable. It fails if takes over 5 seconds for handler to exit.
+func testServerPostUnblock(t *testing.T,
+ handler func(http.ResponseWriter, *http.Request) error,
+ fn func(*serverTester),
+ checkErr func(error),
+ otherHeaders ...string) {
+ inHandler := make(chan bool)
+ errc := make(chan error, 1)
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ inHandler <- true
+ errc <- handler(w, r)
+ })
+ st.greet()
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1,
+ BlockFragment: st.encodeHeader(append([]string{":method", "POST"}, otherHeaders...)...),
+ EndStream: false, // keep it open
+ EndHeaders: true,
+ })
+ <-inHandler
+ fn(st)
+ select {
+ case err := <-errc:
+ if checkErr != nil {
+ checkErr(err)
+ }
+ case <-time.After(5 * time.Second):
+ t.Fatal("timeout waiting for Handler to return")
+ }
+ st.Close()
+}
+
+func TestServer_RSTStream_Unblocks_Read(t *testing.T) {
+ testServerPostUnblock(t,
+ func(w http.ResponseWriter, r *http.Request) (err error) {
+ _, err = r.Body.Read(make([]byte, 1))
+ return
+ },
+ func(st *serverTester) {
+ if err := st.fr.WriteRSTStream(1, ErrCodeCancel); err != nil {
+ t.Fatal(err)
+ }
+ },
+ func(err error) {
+ if err == nil {
+ t.Error("unexpected nil error from Request.Body.Read")
+ }
+ },
+ )
+}
+
+func TestServer_DeadConn_Unblocks_Read(t *testing.T) {
+ testServerPostUnblock(t,
+ func(w http.ResponseWriter, r *http.Request) (err error) {
+ _, err = r.Body.Read(make([]byte, 1))
+ return
+ },
+ func(st *serverTester) { st.cc.Close() },
+ func(err error) {
+ if err == nil {
+ t.Error("unexpected nil error from Request.Body.Read")
+ }
+ },
+ )
+}
+
+var blockUntilClosed = func(w http.ResponseWriter, r *http.Request) error {
+ <-w.(http.CloseNotifier).CloseNotify()
+ return nil
+}
+
+func TestServer_CloseNotify_After_RSTStream(t *testing.T) {
+ testServerPostUnblock(t, blockUntilClosed, func(st *serverTester) {
+ if err := st.fr.WriteRSTStream(1, ErrCodeCancel); err != nil {
+ t.Fatal(err)
+ }
+ }, nil)
+}
+
+func TestServer_CloseNotify_After_ConnClose(t *testing.T) {
+ testServerPostUnblock(t, blockUntilClosed, func(st *serverTester) { st.cc.Close() }, nil)
+}
+
+// that CloseNotify unblocks after a stream error due to the client's
+// problem that's unrelated to them explicitly canceling it (which is
+// TestServer_CloseNotify_After_RSTStream above)
+func TestServer_CloseNotify_After_StreamError(t *testing.T) {
+ testServerPostUnblock(t, blockUntilClosed, func(st *serverTester) {
+ // data longer than declared Content-Length => stream error
+ st.writeData(1, true, []byte("1234"))
+ }, nil, "content-length", "3")
+}
+
+func TestServer_StateTransitions(t *testing.T) {
+ var st *serverTester
+ inHandler := make(chan bool)
+ writeData := make(chan bool)
+ leaveHandler := make(chan bool)
+ st = newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ inHandler <- true
+ if st.stream(1) == nil {
+ t.Errorf("nil stream 1 in handler")
+ }
+ if got, want := st.streamState(1), stateOpen; got != want {
+ t.Errorf("in handler, state is %v; want %v", got, want)
+ }
+ writeData <- true
+ if n, err := r.Body.Read(make([]byte, 1)); n != 0 || err != io.EOF {
+ t.Errorf("body read = %d, %v; want 0, EOF", n, err)
+ }
+ if got, want := st.streamState(1), stateHalfClosedRemote; got != want {
+ t.Errorf("in handler, state is %v; want %v", got, want)
+ }
+
+ <-leaveHandler
+ })
+ st.greet()
+ if st.stream(1) != nil {
+ t.Fatal("stream 1 should be empty")
+ }
+ if got := st.streamState(1); got != stateIdle {
+ t.Fatalf("stream 1 should be idle; got %v", got)
+ }
+
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1,
+ BlockFragment: st.encodeHeader(":method", "POST"),
+ EndStream: false, // keep it open
+ EndHeaders: true,
+ })
+ <-inHandler
+ <-writeData
+ st.writeData(1, true, nil)
+
+ leaveHandler <- true
+ hf := st.wantHeaders()
+ if !hf.StreamEnded() {
+ t.Fatal("expected END_STREAM flag")
+ }
+
+ if got, want := st.streamState(1), stateClosed; got != want {
+ t.Errorf("at end, state is %v; want %v", got, want)
+ }
+ if st.stream(1) != nil {
+ t.Fatal("at end, stream 1 should be gone")
+ }
+}
+
+// test HEADERS w/o EndHeaders + another HEADERS (should get rejected)
+func TestServer_Rejects_HeadersNoEnd_Then_Headers(t *testing.T) {
+ testServerRejects(t, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1,
+ BlockFragment: st.encodeHeader(),
+ EndStream: true,
+ EndHeaders: false,
+ })
+ st.writeHeaders(HeadersFrameParam{ // Not a continuation.
+ StreamID: 3, // different stream.
+ BlockFragment: st.encodeHeader(),
+ EndStream: true,
+ EndHeaders: true,
+ })
+ })
+}
+
+// test HEADERS w/o EndHeaders + PING (should get rejected)
+func TestServer_Rejects_HeadersNoEnd_Then_Ping(t *testing.T) {
+ testServerRejects(t, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1,
+ BlockFragment: st.encodeHeader(),
+ EndStream: true,
+ EndHeaders: false,
+ })
+ if err := st.fr.WritePing(false, [8]byte{}); err != nil {
+ t.Fatal(err)
+ }
+ })
+}
+
+// test HEADERS w/ EndHeaders + a continuation HEADERS (should get rejected)
+func TestServer_Rejects_HeadersEnd_Then_Continuation(t *testing.T) {
+ testServerRejects(t, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1,
+ BlockFragment: st.encodeHeader(),
+ EndStream: true,
+ EndHeaders: true,
+ })
+ st.wantHeaders()
+ if err := st.fr.WriteContinuation(1, true, encodeHeaderNoImplicit(t, "foo", "bar")); err != nil {
+ t.Fatal(err)
+ }
+ })
+}
+
+// test HEADERS w/o EndHeaders + a continuation HEADERS on wrong stream ID
+func TestServer_Rejects_HeadersNoEnd_Then_ContinuationWrongStream(t *testing.T) {
+ testServerRejects(t, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1,
+ BlockFragment: st.encodeHeader(),
+ EndStream: true,
+ EndHeaders: false,
+ })
+ if err := st.fr.WriteContinuation(3, true, encodeHeaderNoImplicit(t, "foo", "bar")); err != nil {
+ t.Fatal(err)
+ }
+ })
+}
+
+// No HEADERS on stream 0.
+func TestServer_Rejects_Headers0(t *testing.T) {
+ testServerRejects(t, func(st *serverTester) {
+ st.fr.AllowIllegalWrites = true
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 0,
+ BlockFragment: st.encodeHeader(),
+ EndStream: true,
+ EndHeaders: true,
+ })
+ })
+}
+
+// No CONTINUATION on stream 0.
+func TestServer_Rejects_Continuation0(t *testing.T) {
+ testServerRejects(t, func(st *serverTester) {
+ st.fr.AllowIllegalWrites = true
+ if err := st.fr.WriteContinuation(0, true, st.encodeHeader()); err != nil {
+ t.Fatal(err)
+ }
+ })
+}
+
+func TestServer_Rejects_PushPromise(t *testing.T) {
+ testServerRejects(t, func(st *serverTester) {
+ pp := PushPromiseParam{
+ StreamID: 1,
+ PromiseID: 3,
+ }
+ if err := st.fr.WritePushPromise(pp); err != nil {
+ t.Fatal(err)
+ }
+ })
+}
+
+// testServerRejects tests that the server hangs up with a GOAWAY
+// frame and a server close after the client does something
+// deserving a CONNECTION_ERROR.
+func testServerRejects(t *testing.T, writeReq func(*serverTester)) {
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {})
+ st.addLogFilter("connection error: PROTOCOL_ERROR")
+ defer st.Close()
+ st.greet()
+ writeReq(st)
+
+ st.wantGoAway()
+ errc := make(chan error, 1)
+ go func() {
+ fr, err := st.fr.ReadFrame()
+ if err == nil {
+ err = fmt.Errorf("got frame of type %T", fr)
+ }
+ errc <- err
+ }()
+ select {
+ case err := <-errc:
+ if err != io.EOF {
+ t.Errorf("ReadFrame = %v; want io.EOF", err)
+ }
+ case <-time.After(2 * time.Second):
+ t.Error("timeout waiting for disconnect")
+ }
+}
+
+// testServerRequest sets up an idle HTTP/2 connection and lets you
+// write a single request with writeReq, and then verify that the
+// *http.Request is built correctly in checkReq.
+func testServerRequest(t *testing.T, writeReq func(*serverTester), checkReq func(*http.Request)) {
+ gotReq := make(chan bool, 1)
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ if r.Body == nil {
+ t.Fatal("nil Body")
+ }
+ checkReq(r)
+ gotReq <- true
+ })
+ defer st.Close()
+
+ st.greet()
+ writeReq(st)
+
+ select {
+ case <-gotReq:
+ case <-time.After(2 * time.Second):
+ t.Error("timeout waiting for request")
+ }
+}
+
+func getSlash(st *serverTester) { st.bodylessReq1() }
+
+func TestServer_Response_NoData(t *testing.T) {
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ // Nothing.
+ return nil
+ }, func(st *serverTester) {
+ getSlash(st)
+ hf := st.wantHeaders()
+ if !hf.StreamEnded() {
+ t.Fatal("want END_STREAM flag")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+ })
+}
+
+func TestServer_Response_NoData_Header_FooBar(t *testing.T) {
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ w.Header().Set("Foo-Bar", "some-value")
+ return nil
+ }, func(st *serverTester) {
+ getSlash(st)
+ hf := st.wantHeaders()
+ if !hf.StreamEnded() {
+ t.Fatal("want END_STREAM flag")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+ goth := decodeHeader(t, hf.HeaderBlockFragment())
+ wanth := [][2]string{
+ {":status", "200"},
+ {"foo-bar", "some-value"},
+ {"content-type", "text/plain; charset=utf-8"},
+ {"content-length", "0"},
+ }
+ if !reflect.DeepEqual(goth, wanth) {
+ t.Errorf("Got headers %v; want %v", goth, wanth)
+ }
+ })
+}
+
+func TestServer_Response_Data_Sniff_DoesntOverride(t *testing.T) {
+ const msg = "<html>this is HTML."
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ w.Header().Set("Content-Type", "foo/bar")
+ io.WriteString(w, msg)
+ return nil
+ }, func(st *serverTester) {
+ getSlash(st)
+ hf := st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("don't want END_STREAM, expecting data")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+ goth := decodeHeader(t, hf.HeaderBlockFragment())
+ wanth := [][2]string{
+ {":status", "200"},
+ {"content-type", "foo/bar"},
+ {"content-length", strconv.Itoa(len(msg))},
+ }
+ if !reflect.DeepEqual(goth, wanth) {
+ t.Errorf("Got headers %v; want %v", goth, wanth)
+ }
+ df := st.wantData()
+ if !df.StreamEnded() {
+ t.Error("expected DATA to have END_STREAM flag")
+ }
+ if got := string(df.Data()); got != msg {
+ t.Errorf("got DATA %q; want %q", got, msg)
+ }
+ })
+}
+
+func TestServer_Response_TransferEncoding_chunked(t *testing.T) {
+ const msg = "hi"
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ w.Header().Set("Transfer-Encoding", "chunked") // should be stripped
+ io.WriteString(w, msg)
+ return nil
+ }, func(st *serverTester) {
+ getSlash(st)
+ hf := st.wantHeaders()
+ goth := decodeHeader(t, hf.HeaderBlockFragment())
+ wanth := [][2]string{
+ {":status", "200"},
+ {"content-type", "text/plain; charset=utf-8"},
+ {"content-length", strconv.Itoa(len(msg))},
+ }
+ if !reflect.DeepEqual(goth, wanth) {
+ t.Errorf("Got headers %v; want %v", goth, wanth)
+ }
+ })
+}
+
+// Header accessed only after the initial write.
+func TestServer_Response_Data_IgnoreHeaderAfterWrite_After(t *testing.T) {
+ const msg = "<html>this is HTML."
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ io.WriteString(w, msg)
+ w.Header().Set("foo", "should be ignored")
+ return nil
+ }, func(st *serverTester) {
+ getSlash(st)
+ hf := st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("unexpected END_STREAM")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+ goth := decodeHeader(t, hf.HeaderBlockFragment())
+ wanth := [][2]string{
+ {":status", "200"},
+ {"content-type", "text/html; charset=utf-8"},
+ {"content-length", strconv.Itoa(len(msg))},
+ }
+ if !reflect.DeepEqual(goth, wanth) {
+ t.Errorf("Got headers %v; want %v", goth, wanth)
+ }
+ })
+}
+
+// Header accessed before the initial write and later mutated.
+func TestServer_Response_Data_IgnoreHeaderAfterWrite_Overwrite(t *testing.T) {
+ const msg = "<html>this is HTML."
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ w.Header().Set("foo", "proper value")
+ io.WriteString(w, msg)
+ w.Header().Set("foo", "should be ignored")
+ return nil
+ }, func(st *serverTester) {
+ getSlash(st)
+ hf := st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("unexpected END_STREAM")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+ goth := decodeHeader(t, hf.HeaderBlockFragment())
+ wanth := [][2]string{
+ {":status", "200"},
+ {"foo", "proper value"},
+ {"content-type", "text/html; charset=utf-8"},
+ {"content-length", strconv.Itoa(len(msg))},
+ }
+ if !reflect.DeepEqual(goth, wanth) {
+ t.Errorf("Got headers %v; want %v", goth, wanth)
+ }
+ })
+}
+
+func TestServer_Response_Data_SniffLenType(t *testing.T) {
+ const msg = "<html>this is HTML."
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ io.WriteString(w, msg)
+ return nil
+ }, func(st *serverTester) {
+ getSlash(st)
+ hf := st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("don't want END_STREAM, expecting data")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+ goth := decodeHeader(t, hf.HeaderBlockFragment())
+ wanth := [][2]string{
+ {":status", "200"},
+ {"content-type", "text/html; charset=utf-8"},
+ {"content-length", strconv.Itoa(len(msg))},
+ }
+ if !reflect.DeepEqual(goth, wanth) {
+ t.Errorf("Got headers %v; want %v", goth, wanth)
+ }
+ df := st.wantData()
+ if !df.StreamEnded() {
+ t.Error("expected DATA to have END_STREAM flag")
+ }
+ if got := string(df.Data()); got != msg {
+ t.Errorf("got DATA %q; want %q", got, msg)
+ }
+ })
+}
+
+func TestServer_Response_Header_Flush_MidWrite(t *testing.T) {
+ const msg = "<html>this is HTML"
+ const msg2 = ", and this is the next chunk"
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ io.WriteString(w, msg)
+ w.(http.Flusher).Flush()
+ io.WriteString(w, msg2)
+ return nil
+ }, func(st *serverTester) {
+ getSlash(st)
+ hf := st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("unexpected END_STREAM flag")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+ goth := decodeHeader(t, hf.HeaderBlockFragment())
+ wanth := [][2]string{
+ {":status", "200"},
+ {"content-type", "text/html; charset=utf-8"}, // sniffed
+ // and no content-length
+ }
+ if !reflect.DeepEqual(goth, wanth) {
+ t.Errorf("Got headers %v; want %v", goth, wanth)
+ }
+ {
+ df := st.wantData()
+ if df.StreamEnded() {
+ t.Error("unexpected END_STREAM flag")
+ }
+ if got := string(df.Data()); got != msg {
+ t.Errorf("got DATA %q; want %q", got, msg)
+ }
+ }
+ {
+ df := st.wantData()
+ if !df.StreamEnded() {
+ t.Error("wanted END_STREAM flag on last data chunk")
+ }
+ if got := string(df.Data()); got != msg2 {
+ t.Errorf("got DATA %q; want %q", got, msg2)
+ }
+ }
+ })
+}
+
+func TestServer_Response_LargeWrite(t *testing.T) {
+ const size = 1 << 20
+ const maxFrameSize = 16 << 10
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ n, err := w.Write(bytes.Repeat([]byte("a"), size))
+ if err != nil {
+ return fmt.Errorf("Write error: %v", err)
+ }
+ if n != size {
+ return fmt.Errorf("wrong size %d from Write", n)
+ }
+ return nil
+ }, func(st *serverTester) {
+ if err := st.fr.WriteSettings(
+ Setting{SettingInitialWindowSize, 0},
+ Setting{SettingMaxFrameSize, maxFrameSize},
+ ); err != nil {
+ t.Fatal(err)
+ }
+ st.wantSettingsAck()
+
+ getSlash(st) // make the single request
+
+ // Give the handler quota to write:
+ if err := st.fr.WriteWindowUpdate(1, size); err != nil {
+ t.Fatal(err)
+ }
+ // Give the handler quota to write to connection-level
+ // window as well
+ if err := st.fr.WriteWindowUpdate(0, size); err != nil {
+ t.Fatal(err)
+ }
+ hf := st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("unexpected END_STREAM flag")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+ goth := decodeHeader(t, hf.HeaderBlockFragment())
+ wanth := [][2]string{
+ {":status", "200"},
+ {"content-type", "text/plain; charset=utf-8"}, // sniffed
+ // and no content-length
+ }
+ if !reflect.DeepEqual(goth, wanth) {
+ t.Errorf("Got headers %v; want %v", goth, wanth)
+ }
+ var bytes, frames int
+ for {
+ df := st.wantData()
+ bytes += len(df.Data())
+ frames++
+ for _, b := range df.Data() {
+ if b != 'a' {
+ t.Fatal("non-'a' byte seen in DATA")
+ }
+ }
+ if df.StreamEnded() {
+ break
+ }
+ }
+ if bytes != size {
+ t.Errorf("Got %d bytes; want %d", bytes, size)
+ }
+ if want := int(size / maxFrameSize); frames < want || frames > want*2 {
+ t.Errorf("Got %d frames; want %d", frames, size)
+ }
+ })
+}
+
+// Test that the handler can't write more than the client allows
+func TestServer_Response_LargeWrite_FlowControlled(t *testing.T) {
+ const size = 1 << 20
+ const maxFrameSize = 16 << 10
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ w.(http.Flusher).Flush()
+ n, err := w.Write(bytes.Repeat([]byte("a"), size))
+ if err != nil {
+ return fmt.Errorf("Write error: %v", err)
+ }
+ if n != size {
+ return fmt.Errorf("wrong size %d from Write", n)
+ }
+ return nil
+ }, func(st *serverTester) {
+ // Set the window size to something explicit for this test.
+ // It's also how much initial data we expect.
+ const initWindowSize = 123
+ if err := st.fr.WriteSettings(
+ Setting{SettingInitialWindowSize, initWindowSize},
+ Setting{SettingMaxFrameSize, maxFrameSize},
+ ); err != nil {
+ t.Fatal(err)
+ }
+ st.wantSettingsAck()
+
+ getSlash(st) // make the single request
+ defer func() { st.fr.WriteRSTStream(1, ErrCodeCancel) }()
+
+ hf := st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("unexpected END_STREAM flag")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+
+ df := st.wantData()
+ if got := len(df.Data()); got != initWindowSize {
+ t.Fatalf("Initial window size = %d but got DATA with %d bytes", initWindowSize, got)
+ }
+
+ for _, quota := range []int{1, 13, 127} {
+ if err := st.fr.WriteWindowUpdate(1, uint32(quota)); err != nil {
+ t.Fatal(err)
+ }
+ df := st.wantData()
+ if int(quota) != len(df.Data()) {
+ t.Fatalf("read %d bytes after giving %d quota", len(df.Data()), quota)
+ }
+ }
+
+ if err := st.fr.WriteRSTStream(1, ErrCodeCancel); err != nil {
+ t.Fatal(err)
+ }
+ })
+}
+
+// Test that the handler blocked in a Write is unblocked if the server sends a RST_STREAM.
+func TestServer_Response_RST_Unblocks_LargeWrite(t *testing.T) {
+ const size = 1 << 20
+ const maxFrameSize = 16 << 10
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ w.(http.Flusher).Flush()
+ errc := make(chan error, 1)
+ go func() {
+ _, err := w.Write(bytes.Repeat([]byte("a"), size))
+ errc <- err
+ }()
+ select {
+ case err := <-errc:
+ if err == nil {
+ return errors.New("unexpected nil error from Write in handler")
+ }
+ return nil
+ case <-time.After(2 * time.Second):
+ return errors.New("timeout waiting for Write in handler")
+ }
+ }, func(st *serverTester) {
+ if err := st.fr.WriteSettings(
+ Setting{SettingInitialWindowSize, 0},
+ Setting{SettingMaxFrameSize, maxFrameSize},
+ ); err != nil {
+ t.Fatal(err)
+ }
+ st.wantSettingsAck()
+
+ getSlash(st) // make the single request
+
+ hf := st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("unexpected END_STREAM flag")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+
+ if err := st.fr.WriteRSTStream(1, ErrCodeCancel); err != nil {
+ t.Fatal(err)
+ }
+ })
+}
+
+func TestServer_Response_Empty_Data_Not_FlowControlled(t *testing.T) {
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ w.(http.Flusher).Flush()
+ // Nothing; send empty DATA
+ return nil
+ }, func(st *serverTester) {
+ // Handler gets no data quota:
+ if err := st.fr.WriteSettings(Setting{SettingInitialWindowSize, 0}); err != nil {
+ t.Fatal(err)
+ }
+ st.wantSettingsAck()
+
+ getSlash(st) // make the single request
+
+ hf := st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("unexpected END_STREAM flag")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+
+ df := st.wantData()
+ if got := len(df.Data()); got != 0 {
+ t.Fatalf("unexpected %d DATA bytes; want 0", got)
+ }
+ if !df.StreamEnded() {
+ t.Fatal("DATA didn't have END_STREAM")
+ }
+ })
+}
+
+func TestServer_Response_Automatic100Continue(t *testing.T) {
+ const msg = "foo"
+ const reply = "bar"
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ if v := r.Header.Get("Expect"); v != "" {
+ t.Errorf("Expect header = %q; want empty", v)
+ }
+ buf := make([]byte, len(msg))
+ // This read should trigger the 100-continue being sent.
+ if n, err := io.ReadFull(r.Body, buf); err != nil || n != len(msg) || string(buf) != msg {
+ return fmt.Errorf("ReadFull = %q, %v; want %q, nil", buf[:n], err, msg)
+ }
+ _, err := io.WriteString(w, reply)
+ return err
+ }, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1, // clients send odd numbers
+ BlockFragment: st.encodeHeader(":method", "POST", "expect", "100-continue"),
+ EndStream: false,
+ EndHeaders: true,
+ })
+ hf := st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("unexpected END_STREAM flag")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+ goth := decodeHeader(t, hf.HeaderBlockFragment())
+ wanth := [][2]string{
+ {":status", "100"},
+ }
+ if !reflect.DeepEqual(goth, wanth) {
+ t.Fatalf("Got headers %v; want %v", goth, wanth)
+ }
+
+ // Okay, they sent status 100, so we can send our
+ // gigantic and/or sensitive "foo" payload now.
+ st.writeData(1, true, []byte(msg))
+
+ st.wantWindowUpdate(0, uint32(len(msg)))
+
+ hf = st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("expected data to follow")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+ goth = decodeHeader(t, hf.HeaderBlockFragment())
+ wanth = [][2]string{
+ {":status", "200"},
+ {"content-type", "text/plain; charset=utf-8"},
+ {"content-length", strconv.Itoa(len(reply))},
+ }
+ if !reflect.DeepEqual(goth, wanth) {
+ t.Errorf("Got headers %v; want %v", goth, wanth)
+ }
+
+ df := st.wantData()
+ if string(df.Data()) != reply {
+ t.Errorf("Client read %q; want %q", df.Data(), reply)
+ }
+ if !df.StreamEnded() {
+ t.Errorf("expect data stream end")
+ }
+ })
+}
+
+func TestServer_HandlerWriteErrorOnDisconnect(t *testing.T) {
+ errc := make(chan error, 1)
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ p := []byte("some data.\n")
+ for {
+ _, err := w.Write(p)
+ if err != nil {
+ errc <- err
+ return nil
+ }
+ }
+ }, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1,
+ BlockFragment: st.encodeHeader(),
+ EndStream: false,
+ EndHeaders: true,
+ })
+ hf := st.wantHeaders()
+ if hf.StreamEnded() {
+ t.Fatal("unexpected END_STREAM flag")
+ }
+ if !hf.HeadersEnded() {
+ t.Fatal("want END_HEADERS flag")
+ }
+ // Close the connection and wait for the handler to (hopefully) notice.
+ st.cc.Close()
+ select {
+ case <-errc:
+ case <-time.After(5 * time.Second):
+ t.Error("timeout")
+ }
+ })
+}
+
+func TestServer_Rejects_Too_Many_Streams(t *testing.T) {
+ const testPath = "/some/path"
+
+ inHandler := make(chan uint32)
+ leaveHandler := make(chan bool)
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ id := w.(*responseWriter).rws.stream.id
+ inHandler <- id
+ if id == 1+(defaultMaxStreams+1)*2 && r.URL.Path != testPath {
+ t.Errorf("decoded final path as %q; want %q", r.URL.Path, testPath)
+ }
+ <-leaveHandler
+ })
+ defer st.Close()
+ st.greet()
+ nextStreamID := uint32(1)
+ streamID := func() uint32 {
+ defer func() { nextStreamID += 2 }()
+ return nextStreamID
+ }
+ sendReq := func(id uint32, headers ...string) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: id,
+ BlockFragment: st.encodeHeader(headers...),
+ EndStream: true,
+ EndHeaders: true,
+ })
+ }
+ for i := 0; i < defaultMaxStreams; i++ {
+ sendReq(streamID())
+ <-inHandler
+ }
+ defer func() {
+ for i := 0; i < defaultMaxStreams; i++ {
+ leaveHandler <- true
+ }
+ }()
+
+ // And this one should cross the limit:
+ // (It's also sent as a CONTINUATION, to verify we still track the decoder context,
+ // even if we're rejecting it)
+ rejectID := streamID()
+ headerBlock := st.encodeHeader(":path", testPath)
+ frag1, frag2 := headerBlock[:3], headerBlock[3:]
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: rejectID,
+ BlockFragment: frag1,
+ EndStream: true,
+ EndHeaders: false, // CONTINUATION coming
+ })
+ if err := st.fr.WriteContinuation(rejectID, true, frag2); err != nil {
+ t.Fatal(err)
+ }
+ st.wantRSTStream(rejectID, ErrCodeProtocol)
+
+ // But let a handler finish:
+ leaveHandler <- true
+ st.wantHeaders()
+
+ // And now another stream should be able to start:
+ goodID := streamID()
+ sendReq(goodID, ":path", testPath)
+ select {
+ case got := <-inHandler:
+ if got != goodID {
+ t.Errorf("Got stream %d; want %d", got, goodID)
+ }
+ case <-time.After(3 * time.Second):
+ t.Error("timeout waiting for handler")
+ }
+}
+
+// So many response headers that the server needs to use CONTINUATION frames:
+func TestServer_Response_ManyHeaders_With_Continuation(t *testing.T) {
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ h := w.Header()
+ for i := 0; i < 5000; i++ {
+ h.Set(fmt.Sprintf("x-header-%d", i), fmt.Sprintf("x-value-%d", i))
+ }
+ return nil
+ }, func(st *serverTester) {
+ getSlash(st)
+ hf := st.wantHeaders()
+ if hf.HeadersEnded() {
+ t.Fatal("got unwanted END_HEADERS flag")
+ }
+ n := 0
+ for {
+ n++
+ cf := st.wantContinuation()
+ if cf.HeadersEnded() {
+ break
+ }
+ }
+ if n < 5 {
+ t.Errorf("Only got %d CONTINUATION frames; expected 5+ (currently 6)", n)
+ }
+ })
+}
+
+// This previously crashed (reported by Mathieu Lonjaret as observed
+// while using Camlistore) because we got a DATA frame from the client
+// after the handler exited and our logic at the time was wrong,
+// keeping a stream in the map in stateClosed, which tickled an
+// invariant check later when we tried to remove that stream (via
+// defer sc.closeAllStreamsOnConnClose) when the serverConn serve loop
+// ended.
+func TestServer_NoCrash_HandlerClose_Then_ClientClose(t *testing.T) {
+ testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+ // nothing
+ return nil
+ }, func(st *serverTester) {
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: 1,
+ BlockFragment: st.encodeHeader(),
+ EndStream: false, // DATA is coming
+ EndHeaders: true,
+ })
+ hf := st.wantHeaders()
+ if !hf.HeadersEnded() || !hf.StreamEnded() {
+ t.Fatalf("want END_HEADERS+END_STREAM, got %v", hf)
+ }
+
+ // Sent when the a Handler closes while a client has
+ // indicated it's still sending DATA:
+ st.wantRSTStream(1, ErrCodeCancel)
+
+ // Now the handler has ended, so it's ended its
+ // stream, but the client hasn't closed its side
+ // (stateClosedLocal). So send more data and verify
+ // it doesn't crash with an internal invariant panic, like
+ // it did before.
+ st.writeData(1, true, []byte("foo"))
+
+ // Sent after a peer sends data anyway (admittedly the
+ // previous RST_STREAM might've still been in-flight),
+ // but they'll get the more friendly 'cancel' code
+ // first.
+ st.wantRSTStream(1, ErrCodeStreamClosed)
+
+ // Set up a bunch of machinery to record the panic we saw
+ // previously.
+ var (
+ panMu sync.Mutex
+ panicVal interface{}
+ )
+
+ testHookOnPanicMu.Lock()
+ testHookOnPanic = func(sc *serverConn, pv interface{}) bool {
+ panMu.Lock()
+ panicVal = pv
+ panMu.Unlock()
+ return true
+ }
+ testHookOnPanicMu.Unlock()
+
+ // Now force the serve loop to end, via closing the connection.
+ st.cc.Close()
+ select {
+ case <-st.sc.doneServing:
+ // Loop has exited.
+ panMu.Lock()
+ got := panicVal
+ panMu.Unlock()
+ if got != nil {
+ t.Errorf("Got panic: %v", got)
+ }
+ case <-time.After(5 * time.Second):
+ t.Error("timeout")
+ }
+ })
+}
+
+func TestServer_Rejects_TLS10(t *testing.T) { testRejectTLS(t, tls.VersionTLS10) }
+func TestServer_Rejects_TLS11(t *testing.T) { testRejectTLS(t, tls.VersionTLS11) }
+
+func testRejectTLS(t *testing.T, max uint16) {
+ st := newServerTester(t, nil, func(c *tls.Config) {
+ c.MaxVersion = max
+ })
+ defer st.Close()
+ gf := st.wantGoAway()
+ if got, want := gf.ErrCode, ErrCodeInadequateSecurity; got != want {
+ t.Errorf("Got error code %v; want %v", got, want)
+ }
+}
+
+func TestServer_Rejects_TLSBadCipher(t *testing.T) {
+ st := newServerTester(t, nil, func(c *tls.Config) {
+ // Only list bad ones:
+ c.CipherSuites = []uint16{
+ tls.TLS_RSA_WITH_RC4_128_SHA,
+ tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
+ tls.TLS_RSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
+ tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
+ tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
+ tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+ tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+ }
+ })
+ defer st.Close()
+ gf := st.wantGoAway()
+ if got, want := gf.ErrCode, ErrCodeInadequateSecurity; got != want {
+ t.Errorf("Got error code %v; want %v", got, want)
+ }
+}
+
+func TestServer_Advertises_Common_Cipher(t *testing.T) {
+ const requiredSuite = tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
+ st := newServerTester(t, nil, func(c *tls.Config) {
+ // Have the client only support the one required by the spec.
+ c.CipherSuites = []uint16{requiredSuite}
+ }, func(ts *httptest.Server) {
+ var srv *http.Server = ts.Config
+ // Have the server configured with one specific cipher suite
+ // which is banned. This tests that ConfigureServer ends up
+ // adding the good one to this list.
+ srv.TLSConfig = &tls.Config{
+ CipherSuites: []uint16{tls.TLS_RSA_WITH_AES_128_CBC_SHA}, // just a banned one
+ }
+ })
+ defer st.Close()
+ st.greet()
+}
+
+// TODO: move this onto *serverTester, and re-use the same hpack
+// decoding context throughout. We're just getting lucky here with
+// creating a new decoder each time.
+func decodeHeader(t *testing.T, headerBlock []byte) (pairs [][2]string) {
+ d := hpack.NewDecoder(initialHeaderTableSize, func(f hpack.HeaderField) {
+ pairs = append(pairs, [2]string{f.Name, f.Value})
+ })
+ if _, err := d.Write(headerBlock); err != nil {
+ t.Fatalf("hpack decoding error: %v", err)
+ }
+ if err := d.Close(); err != nil {
+ t.Fatalf("hpack decoding error: %v", err)
+ }
+ return
+}
+
+// testServerResponse sets up an idle HTTP/2 connection and lets you
+// write a single request with writeReq, and then reply to it in some way with the provided handler,
+// and then verify the output with the serverTester again (assuming the handler returns nil)
+func testServerResponse(t testing.TB,
+ handler func(http.ResponseWriter, *http.Request) error,
+ client func(*serverTester),
+) {
+ errc := make(chan error, 1)
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ if r.Body == nil {
+ t.Fatal("nil Body")
+ }
+ errc <- handler(w, r)
+ })
+ defer st.Close()
+
+ donec := make(chan bool)
+ go func() {
+ defer close(donec)
+ st.greet()
+ client(st)
+ }()
+
+ select {
+ case <-donec:
+ return
+ case <-time.After(5 * time.Second):
+ t.Fatal("timeout")
+ }
+
+ select {
+ case err := <-errc:
+ if err != nil {
+ t.Fatalf("Error in handler: %v", err)
+ }
+ case <-time.After(2 * time.Second):
+ t.Error("timeout waiting for handler to finish")
+ }
+}
+
+// readBodyHandler returns an http Handler func that reads len(want)
+// bytes from r.Body and fails t if the contents read were not
+// the value of want.
+func readBodyHandler(t *testing.T, want string) func(w http.ResponseWriter, r *http.Request) {
+ return func(w http.ResponseWriter, r *http.Request) {
+ buf := make([]byte, len(want))
+ _, err := io.ReadFull(r.Body, buf)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ if string(buf) != want {
+ t.Errorf("read %q; want %q", buf, want)
+ }
+ }
+}
+
+// TestServerWithCurl currently fails, hence the LenientCipherSuites test. See:
+// https://github.com/tatsuhiro-t/nghttp2/issues/140 &
+// http://sourceforge.net/p/curl/bugs/1472/
+func TestServerWithCurl(t *testing.T) { testServerWithCurl(t, false) }
+func TestServerWithCurl_LenientCipherSuites(t *testing.T) { testServerWithCurl(t, true) }
+
+func testServerWithCurl(t *testing.T, permitProhibitedCipherSuites bool) {
+ if runtime.GOOS != "linux" {
+ t.Skip("skipping Docker test when not on Linux; requires --net which won't work with boot2docker anyway")
+ }
+ requireCurl(t)
+ const msg = "Hello from curl!\n"
+ ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Foo", "Bar")
+ w.Header().Set("Client-Proto", r.Proto)
+ io.WriteString(w, msg)
+ }))
+ ConfigureServer(ts.Config, &Server{
+ PermitProhibitedCipherSuites: permitProhibitedCipherSuites,
+ })
+ ts.TLS = ts.Config.TLSConfig // the httptest.Server has its own copy of this TLS config
+ ts.StartTLS()
+ defer ts.Close()
+
+ var gotConn int32
+ testHookOnConn = func() { atomic.StoreInt32(&gotConn, 1) }
+
+ t.Logf("Running test server for curl to hit at: %s", ts.URL)
+ container := curl(t, "--silent", "--http2", "--insecure", "-v", ts.URL)
+ defer kill(container)
+ resc := make(chan interface{}, 1)
+ go func() {
+ res, err := dockerLogs(container)
+ if err != nil {
+ resc <- err
+ } else {
+ resc <- res
+ }
+ }()
+ select {
+ case res := <-resc:
+ if err, ok := res.(error); ok {
+ t.Fatal(err)
+ }
+ if !strings.Contains(string(res.([]byte)), "foo: Bar") {
+ t.Errorf("didn't see foo: Bar header")
+ t.Logf("Got: %s", res)
+ }
+ if !strings.Contains(string(res.([]byte)), "client-proto: HTTP/2") {
+ t.Errorf("didn't see client-proto: HTTP/2 header")
+ t.Logf("Got: %s", res)
+ }
+ if !strings.Contains(string(res.([]byte)), msg) {
+ t.Errorf("didn't see %q content", msg)
+ t.Logf("Got: %s", res)
+ }
+ case <-time.After(3 * time.Second):
+ t.Errorf("timeout waiting for curl")
+ }
+
+ if atomic.LoadInt32(&gotConn) == 0 {
+ t.Error("never saw an http2 connection")
+ }
+}
+
+func BenchmarkServerGets(b *testing.B) {
+ b.ReportAllocs()
+
+ const msg = "Hello, world"
+ st := newServerTester(b, func(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, msg)
+ })
+ defer st.Close()
+ st.greet()
+
+ // Give the server quota to reply. (plus it has the the 64KB)
+ if err := st.fr.WriteWindowUpdate(0, uint32(b.N*len(msg))); err != nil {
+ b.Fatal(err)
+ }
+
+ for i := 0; i < b.N; i++ {
+ id := 1 + uint32(i)*2
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: id,
+ BlockFragment: st.encodeHeader(),
+ EndStream: true,
+ EndHeaders: true,
+ })
+ st.wantHeaders()
+ df := st.wantData()
+ if !df.StreamEnded() {
+ b.Fatalf("DATA didn't have END_STREAM; got %v", df)
+ }
+ }
+}
+
+func BenchmarkServerPosts(b *testing.B) {
+ b.ReportAllocs()
+
+ const msg = "Hello, world"
+ st := newServerTester(b, func(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, msg)
+ })
+ defer st.Close()
+ st.greet()
+
+ // Give the server quota to reply. (plus it has the the 64KB)
+ if err := st.fr.WriteWindowUpdate(0, uint32(b.N*len(msg))); err != nil {
+ b.Fatal(err)
+ }
+
+ for i := 0; i < b.N; i++ {
+ id := 1 + uint32(i)*2
+ st.writeHeaders(HeadersFrameParam{
+ StreamID: id,
+ BlockFragment: st.encodeHeader(":method", "POST"),
+ EndStream: false,
+ EndHeaders: true,
+ })
+ st.writeData(id, true, nil)
+ st.wantHeaders()
+ df := st.wantData()
+ if !df.StreamEnded() {
+ b.Fatalf("DATA didn't have END_STREAM; got %v", df)
+ }
+ }
+}
diff --git a/http2/testdata/draft-ietf-httpbis-http2.xml b/http2/testdata/draft-ietf-httpbis-http2.xml
new file mode 100644
index 0000000..31a84be
--- /dev/null
+++ b/http2/testdata/draft-ietf-httpbis-http2.xml
@@ -0,0 +1,5021 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="lib/rfc2629.xslt"?>
+<?rfc toc="yes" ?>
+<?rfc symrefs="yes" ?>
+<?rfc sortrefs="yes" ?>
+<?rfc compact="yes"?>
+<?rfc subcompact="no" ?>
+<?rfc linkmailto="no" ?>
+<?rfc editing="no" ?>
+<?rfc comments="yes" ?>
+<?rfc inline="yes"?>
+<?rfc rfcedstyle="yes"?>
+<?rfc-ext allow-markup-in-artwork="yes" ?>
+<?rfc-ext include-index="no" ?>
+
+<rfc ipr="trust200902"
+ category="std"
+ docName="draft-ietf-httpbis-http2-latest"
+ x:maturity-level="proposed"
+ xmlns:x="http://purl.org/net/xml2rfc/ext">
+ <x:feedback template="mailto:ietf-http-wg@w3.org?subject={docname},%20%22{section}%22&body=<{ref}>:"/>
+ <front>
+ <title abbrev="HTTP/2">Hypertext Transfer Protocol version 2</title>
+
+ <author initials="M." surname="Belshe" fullname="Mike Belshe">
+ <organization>Twist</organization>
+ <address>
+ <email>mbelshe@chromium.org</email>
+ </address>
+ </author>
+
+ <author initials="R." surname="Peon" fullname="Roberto Peon">
+ <organization>Google, Inc</organization>
+ <address>
+ <email>fenix@google.com</email>
+ </address>
+ </author>
+
+ <author initials="M." surname="Thomson" fullname="Martin Thomson" role="editor">
+ <organization>Mozilla</organization>
+ <address>
+ <postal>
+ <street>331 E Evelyn Street</street>
+ <city>Mountain View</city>
+ <region>CA</region>
+ <code>94041</code>
+ <country>US</country>
+ </postal>
+ <email>martin.thomson@gmail.com</email>
+ </address>
+ </author>
+
+ <date year="2014" />
+ <area>Applications</area>
+ <workgroup>HTTPbis</workgroup>
+ <keyword>HTTP</keyword>
+ <keyword>SPDY</keyword>
+ <keyword>Web</keyword>
+
+ <abstract>
+ <t>
+ This specification describes an optimized expression of the semantics of the Hypertext
+ Transfer Protocol (HTTP). HTTP/2 enables a more efficient use of network resources and a
+ reduced perception of latency by introducing header field compression and allowing multiple
+ concurrent messages on the same connection. It also introduces unsolicited push of
+ representations from servers to clients.
+ </t>
+ <t>
+ This specification is an alternative to, but does not obsolete, the HTTP/1.1 message syntax.
+ HTTP's existing semantics remain unchanged.
+ </t>
+ </abstract>
+
+ <note title="Editorial Note (To be removed by RFC Editor)">
+ <t>
+ Discussion of this draft takes place on the HTTPBIS working group mailing list
+ (ietf-http-wg@w3.org), which is archived at <eref
+ target="https://lists.w3.org/Archives/Public/ietf-http-wg/"/>.
+ </t>
+ <t>
+ Working Group information can be found at <eref
+ target="https://tools.ietf.org/wg/httpbis/"/>; that specific to HTTP/2 are at <eref
+ target="https://http2.github.io/"/>.
+ </t>
+ <t>
+ The changes in this draft are summarized in <xref
+ target="change.log"/>.
+ </t>
+ </note>
+
+ </front>
+
+ <middle>
+ <section anchor="intro" title="Introduction">
+
+ <t>
+ The Hypertext Transfer Protocol (HTTP) is a wildly successful protocol. However, the
+ HTTP/1.1 message format (<xref target="RFC7230" x:fmt="," x:rel="#http.message"/>) has
+ several characteristics that have a negative overall effect on application performance
+ today.
+ </t>
+ <t>
+ In particular, HTTP/1.0 allowed only one request to be outstanding at a time on a given
+ TCP connection. HTTP/1.1 added request pipelining, but this only partially addressed
+ request concurrency and still suffers from head-of-line blocking. Therefore, HTTP/1.1
+ clients that need to make many requests typically use multiple connections to a server in
+ order to achieve concurrency and thereby reduce latency.
+ </t>
+ <t>
+ Furthermore, HTTP header fields are often repetitive and verbose, causing unnecessary
+ network traffic, as well as causing the initial <xref target="TCP">TCP</xref> congestion
+ window to quickly fill. This can result in excessive latency when multiple requests are
+ made on a new TCP connection.
+ </t>
+ <t>
+ HTTP/2 addresses these issues by defining an optimized mapping of HTTP's semantics to an
+ underlying connection. Specifically, it allows interleaving of request and response
+ messages on the same connection and uses an efficient coding for HTTP header fields. It
+ also allows prioritization of requests, letting more important requests complete more
+ quickly, further improving performance.
+ </t>
+ <t>
+ The resulting protocol is more friendly to the network, because fewer TCP connections can
+ be used in comparison to HTTP/1.x. This means less competition with other flows, and
+ longer-lived connections, which in turn leads to better utilization of available network
+ capacity.
+ </t>
+ <t>
+ Finally, HTTP/2 also enables more efficient processing of messages through use of binary
+ message framing.
+ </t>
+ </section>
+
+ <section anchor="Overview" title="HTTP/2 Protocol Overview">
+ <t>
+ HTTP/2 provides an optimized transport for HTTP semantics. HTTP/2 supports all of the core
+ features of HTTP/1.1, but aims to be more efficient in several ways.
+ </t>
+ <t>
+ The basic protocol unit in HTTP/2 is a <xref target="FrameHeader">frame</xref>. Each frame
+ type serves a different purpose. For example, <x:ref>HEADERS</x:ref> and
+ <x:ref>DATA</x:ref> frames form the basis of <xref target="HttpSequence">HTTP requests and
+ responses</xref>; other frame types like <x:ref>SETTINGS</x:ref>,
+ <x:ref>WINDOW_UPDATE</x:ref>, and <x:ref>PUSH_PROMISE</x:ref> are used in support of other
+ HTTP/2 features.
+ </t>
+ <t>
+ Multiplexing of requests is achieved by having each HTTP request-response exchange
+ associated with its own <xref target="StreamsLayer">stream</xref>. Streams are largely
+ independent of each other, so a blocked or stalled request or response does not prevent
+ progress on other streams.
+ </t>
+ <t>
+ Flow control and prioritization ensure that it is possible to efficiently use multiplexed
+ streams. <xref target="FlowControl">Flow control</xref> helps to ensure that only data that
+ can be used by a receiver is transmitted. <xref
+ target="StreamPriority">Prioritization</xref> ensures that limited resources can be directed
+ to the most important streams first.
+ </t>
+ <t>
+ HTTP/2 adds a new interaction mode, whereby a server can <xref target="PushResources">push
+ responses to a client</xref>. Server push allows a server to speculatively send a client
+ data that the server anticipates the client will need, trading off some network usage
+ against a potential latency gain. The server does this by synthesizing a request, which it
+ sends as a <x:ref>PUSH_PROMISE</x:ref> frame. The server is then able to send a response to
+ the synthetic request on a separate stream.
+ </t>
+ <t>
+ Frames that contain HTTP header fields are <xref target="HeaderBlock">compressed</xref>.
+ HTTP requests can be highly redundant, so compression can reduce the size of requests and
+ responses significantly.
+ </t>
+
+ <section title="Document Organization">
+ <t>
+ The HTTP/2 specification is split into four parts:
+ <list style="symbols">
+ <t>
+ <xref target="starting">Starting HTTP/2</xref> covers how an HTTP/2 connection is
+ initiated.
+ </t>
+ <t>
+ The <xref target="FramingLayer">framing</xref> and <xref
+ target="StreamsLayer">streams</xref> layers describe the way HTTP/2 frames are
+ structured and formed into multiplexed streams.
+ </t>
+ <t>
+ <xref target="FrameTypes">Frame</xref> and <xref target="ErrorCodes">error</xref>
+ definitions include details of the frame and error types used in HTTP/2.
+ </t>
+ <t>
+ <xref target="HTTPLayer">HTTP mappings</xref> and <xref target="HttpExtra">additional
+ requirements</xref> describe how HTTP semantics are expressed using frames and
+ streams.
+ </t>
+ </list>
+ </t>
+ <t>
+ While some of the frame and stream layer concepts are isolated from HTTP, this
+ specification does not define a completely generic framing layer. The framing and streams
+ layers are tailored to the needs of the HTTP protocol and server push.
+ </t>
+ </section>
+
+ <section title="Conventions and Terminology">
+ <t>
+ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD
+ NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as
+ described in <xref target="RFC2119">RFC 2119</xref>.
+ </t>
+ <t>
+ All numeric values are in network byte order. Values are unsigned unless otherwise
+ indicated. Literal values are provided in decimal or hexadecimal as appropriate.
+ Hexadecimal literals are prefixed with <spanx style="verb">0x</spanx> to distinguish them
+ from decimal literals.
+ </t>
+ <t>
+ The following terms are used:
+ <list style="hanging">
+ <t hangText="client:">
+ The endpoint initiating the HTTP/2 connection.
+ </t>
+ <t hangText="connection:">
+ A transport-layer connection between two endpoints.
+ </t>
+ <t hangText="connection error:">
+ An error that affects the entire HTTP/2 connection.
+ </t>
+ <t hangText="endpoint:">
+ Either the client or server of the connection.
+ </t>
+ <t hangText="frame:">
+ The smallest unit of communication within an HTTP/2 connection, consisting of a header
+ and a variable-length sequence of octets structured according to the frame type.
+ </t>
+ <t hangText="peer:">
+ An endpoint. When discussing a particular endpoint, "peer" refers to the endpoint
+ that is remote to the primary subject of discussion.
+ </t>
+ <t hangText="receiver:">
+ An endpoint that is receiving frames.
+ </t>
+ <t hangText="sender:">
+ An endpoint that is transmitting frames.
+ </t>
+ <t hangText="server:">
+ The endpoint which did not initiate the HTTP/2 connection.
+ </t>
+ <t hangText="stream:">
+ A bi-directional flow of frames across a virtual channel within the HTTP/2 connection.
+ </t>
+ <t hangText="stream error:">
+ An error on the individual HTTP/2 stream.
+ </t>
+ </list>
+ </t>
+ <t>
+ Finally, the terms "gateway", "intermediary", "proxy", and "tunnel" are defined
+ in <xref target="RFC7230" x:fmt="of" x:rel="#intermediaries"/>.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="starting" title="Starting HTTP/2">
+ <t>
+ An HTTP/2 connection is an application layer protocol running on top of a TCP connection
+ (<xref target="TCP"/>). The client is the TCP connection initiator.
+ </t>
+ <t>
+ HTTP/2 uses the same "http" and "https" URI schemes used by HTTP/1.1. HTTP/2 shares the same
+ default port numbers: 80 for "http" URIs and 443 for "https" URIs. As a result,
+ implementations processing requests for target resource URIs like <spanx
+ style="verb">http://example.org/foo</spanx> or <spanx
+ style="verb">https://example.com/bar</spanx> are required to first discover whether the
+ upstream server (the immediate peer to which the client wishes to establish a connection)
+ supports HTTP/2.
+ </t>
+
+ <t>
+ The means by which support for HTTP/2 is determined is different for "http" and "https"
+ URIs. Discovery for "http" URIs is described in <xref target="discover-http"/>. Discovery
+ for "https" URIs is described in <xref target="discover-https"/>.
+ </t>
+
+ <section anchor="versioning" title="HTTP/2 Version Identification">
+ <t>
+ The protocol defined in this document has two identifiers.
+ <list style="symbols">
+ <x:lt>
+ <t>
+ The string "h2" identifies the protocol where HTTP/2 uses <xref
+ target="TLS12">TLS</xref>. This identifier is used in the <xref
+ target="TLS-ALPN">TLS application layer protocol negotiation extension (ALPN)</xref>
+ field and any place that HTTP/2 over TLS is identified.
+ </t>
+ <t>
+ The "h2" string is serialized into an ALPN protocol identifier as the two octet
+ sequence: 0x68, 0x32.
+ </t>
+ </x:lt>
+ <x:lt>
+ <t>
+ The string "h2c" identifies the protocol where HTTP/2 is run over cleartext TCP.
+ This identifier is used in the HTTP/1.1 Upgrade header field and any place that
+ HTTP/2 over TCP is identified.
+ </t>
+ </x:lt>
+ </list>
+ </t>
+ <t>
+ Negotiating "h2" or "h2c" implies the use of the transport, security, framing and message
+ semantics described in this document.
+ </t>
+ <t>
+ <cref>RFC Editor's Note: please remove the remainder of this section prior to the
+ publication of a final version of this document.</cref>
+ </t>
+ <t>
+ Only implementations of the final, published RFC can identify themselves as "h2" or "h2c".
+ Until such an RFC exists, implementations MUST NOT identify themselves using these
+ strings.
+ </t>
+ <t>
+ Examples and text throughout the rest of this document use "h2" as a matter of
+ editorial convenience only. Implementations of draft versions MUST NOT identify using
+ this string.
+ </t>
+ <t>
+ Implementations of draft versions of the protocol MUST add the string "-" and the
+ corresponding draft number to the identifier. For example, draft-ietf-httpbis-http2-11
+ over TLS is identified using the string "h2-11".
+ </t>
+ <t>
+ Non-compatible experiments that are based on these draft versions MUST append the string
+ "-" and an experiment name to the identifier. For example, an experimental implementation
+ of packet mood-based encoding based on draft-ietf-httpbis-http2-09 might identify itself
+ as "h2-09-emo". Note that any label MUST conform to the "token" syntax defined in
+ <xref target="RFC7230" x:fmt="of" x:rel="#field.components"/>. Experimenters are
+ encouraged to coordinate their experiments on the ietf-http-wg@w3.org mailing list.
+ </t>
+ </section>
+
+ <section anchor="discover-http" title="Starting HTTP/2 for "http" URIs">
+ <t>
+ A client that makes a request for an "http" URI without prior knowledge about support for
+ HTTP/2 uses the HTTP Upgrade mechanism (<xref target="RFC7230" x:fmt="of"
+ x:rel="#header.upgrade"/>). The client makes an HTTP/1.1 request that includes an Upgrade
+ header field identifying HTTP/2 with the "h2c" token. The HTTP/1.1 request MUST include
+ exactly one <xref target="Http2SettingsHeader">HTTP2-Settings</xref> header field.
+ </t>
+ <figure>
+ <preamble>For example:</preamble>
+ <artwork type="message/http; msgtype="request"" x:indent-with=" "><![CDATA[
+GET / HTTP/1.1
+Host: server.example.com
+Connection: Upgrade, HTTP2-Settings
+Upgrade: h2c
+HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
+
+]]></artwork>
+ </figure>
+ <t>
+ Requests that contain an entity body MUST be sent in their entirety before the client can
+ send HTTP/2 frames. This means that a large request entity can block the use of the
+ connection until it is completely sent.
+ </t>
+ <t>
+ If concurrency of an initial request with subsequent requests is important, an OPTIONS
+ request can be used to perform the upgrade to HTTP/2, at the cost of an additional
+ round-trip.
+ </t>
+ <t>
+ A server that does not support HTTP/2 can respond to the request as though the Upgrade
+ header field were absent:
+ </t>
+ <figure>
+ <artwork type="message/http; msgtype="response"" x:indent-with=" ">
+HTTP/1.1 200 OK
+Content-Length: 243
+Content-Type: text/html
+
+...
+</artwork>
+ </figure>
+ <t>
+ A server MUST ignore a "h2" token in an Upgrade header field. Presence of a token with
+ "h2" implies HTTP/2 over TLS, which is instead negotiated as described in <xref
+ target="discover-https"/>.
+ </t>
+ <t>
+ A server that supports HTTP/2 can accept the upgrade with a 101 (Switching Protocols)
+ response. After the empty line that terminates the 101 response, the server can begin
+ sending HTTP/2 frames. These frames MUST include a response to the request that initiated
+ the Upgrade.
+ </t>
+
+ <figure>
+ <preamble>
+ For example:
+ </preamble>
+ <artwork type="message/http; msgtype="response"" x:indent-with=" ">
+HTTP/1.1 101 Switching Protocols
+Connection: Upgrade
+Upgrade: h2c
+
+[ HTTP/2 connection ...
+</artwork>
+ </figure>
+ <t>
+ The first HTTP/2 frame sent by the server is a <x:ref>SETTINGS</x:ref> frame (<xref
+ target="SETTINGS"/>) as the server connection preface (<xref
+ target="ConnectionHeader"/>). Upon receiving the 101 response, the client sends a <xref
+ target="ConnectionHeader">connection preface</xref>, which includes a
+ <x:ref>SETTINGS</x:ref> frame.
+ </t>
+ <t>
+ The HTTP/1.1 request that is sent prior to upgrade is assigned stream identifier 1 and is
+ assigned <xref target="pri-default">default priority values</xref>. Stream 1 is
+ implicitly half closed from the client toward the server, since the request is completed
+ as an HTTP/1.1 request. After commencing the HTTP/2 connection, stream 1 is used for the
+ response.
+ </t>
+
+ <section anchor="Http2SettingsHeader" title="HTTP2-Settings Header Field">
+ <t>
+ A request that upgrades from HTTP/1.1 to HTTP/2 MUST include exactly one <spanx
+ style="verb">HTTP2-Settings</spanx> header field. The <spanx
+ style="verb">HTTP2-Settings</spanx> header field is a connection-specific header field
+ that includes parameters that govern the HTTP/2 connection, provided in anticipation of
+ the server accepting the request to upgrade.
+ </t>
+ <figure>
+ <artwork type="abnf" x:indent-with=" "><![CDATA[
+HTTP2-Settings = token68
+]]></artwork>
+ </figure>
+ <t>
+ A server MUST NOT upgrade the connection to HTTP/2 if this header field is not present,
+ or if more than one is present. A server MUST NOT send this header field.
+ </t>
+
+ <t>
+ The content of the <spanx style="verb">HTTP2-Settings</spanx> header field is the
+ payload of a <x:ref>SETTINGS</x:ref> frame (<xref target="SETTINGS"/>), encoded as a
+ base64url string (that is, the URL- and filename-safe Base64 encoding described in <xref
+ target="RFC4648" x:fmt="of" x:sec="5"/>, with any trailing '=' characters omitted). The
+ <xref target="RFC5234">ABNF</xref> production for <spanx style="verb">token68</spanx> is
+ defined in <xref target="RFC7235" x:fmt="of" x:rel="#challenge.and.response"/>.
+ </t>
+ <t>
+ Since the upgrade is only intended to apply to the immediate connection, a client
+ sending <spanx style="verb">HTTP2-Settings</spanx> MUST also send <spanx
+ style="verb">HTTP2-Settings</spanx> as a connection option in the <spanx
+ style="verb">Connection</spanx> header field to prevent it from being forwarded
+ downstream.
+ </t>
+ <t>
+ A server decodes and interprets these values as it would any other
+ <x:ref>SETTINGS</x:ref> frame. <xref target="SettingsSync">Acknowledgement of the
+ SETTINGS parameters</xref> is not necessary, since a 101 response serves as implicit
+ acknowledgment. Providing these values in the Upgrade request gives a client an
+ opportunity to provide parameters prior to receiving any frames from the server.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="discover-https" title="Starting HTTP/2 for "https" URIs">
+ <t>
+ A client that makes a request to an "https" URI uses <xref target="TLS12">TLS</xref>
+ with the <xref target="TLS-ALPN">application layer protocol negotiation extension</xref>.
+ </t>
+ <t>
+ HTTP/2 over TLS uses the "h2" application token. The "h2c" token MUST NOT be sent by a
+ client or selected by a server.
+ </t>
+ <t>
+ Once TLS negotiation is complete, both the client and the server send a <xref
+ target="ConnectionHeader">connection preface</xref>.
+ </t>
+ </section>
+
+ <section anchor="known-http" title="Starting HTTP/2 with Prior Knowledge">
+ <t>
+ A client can learn that a particular server supports HTTP/2 by other means. For example,
+ <xref target="ALT-SVC"/> describes a mechanism for advertising this capability.
+ </t>
+ <t>
+ A client MAY immediately send HTTP/2 frames to a server that is known to support HTTP/2,
+ after the <xref target="ConnectionHeader">connection preface</xref>; a server can
+ identify such a connection by the presence of the connection preface. This only affects
+ the establishment of HTTP/2 connections over cleartext TCP; implementations that support
+ HTTP/2 over TLS MUST use <xref target="TLS-ALPN">protocol negotiation in TLS</xref>.
+ </t>
+ <t>
+ Without additional information, prior support for HTTP/2 is not a strong signal that a
+ given server will support HTTP/2 for future connections. For example, it is possible for
+ server configurations to change, for configurations to differ between instances in
+ clustered servers, or for network conditions to change.
+ </t>
+ </section>
+
+ <section anchor="ConnectionHeader" title="HTTP/2 Connection Preface">
+ <t>
+ Upon establishment of a TCP connection and determination that HTTP/2 will be used by both
+ peers, each endpoint MUST send a connection preface as a final confirmation and to
+ establish the initial SETTINGS parameters for the HTTP/2 connection. The client and
+ server each send a different connection preface.
+ </t>
+ <t>
+ The client connection preface starts with a sequence of 24 octets, which in hex notation
+ are:
+ </t>
+ <figure>
+ <artwork type="inline" x:indent-with=" "><![CDATA[
+0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a
+]]></artwork>
+ </figure>
+ <t>
+ (the string <spanx style="verb">PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n</spanx>). This sequence
+ is followed by a <x:ref>SETTINGS</x:ref> frame (<xref target="SETTINGS"/>). The
+ <x:ref>SETTINGS</x:ref> frame MAY be empty. The client sends the client connection
+ preface immediately upon receipt of a 101 Switching Protocols response (indicating a
+ successful upgrade), or as the first application data octets of a TLS connection. If
+ starting an HTTP/2 connection with prior knowledge of server support for the protocol, the
+ client connection preface is sent upon connection establishment.
+ </t>
+ <t>
+ <list>
+ <t>
+ The client connection preface is selected so that a large proportion of HTTP/1.1 or
+ HTTP/1.0 servers and intermediaries do not attempt to process further frames. Note
+ that this does not address the concerns raised in <xref target="TALKING"/>.
+ </t>
+ </list>
+ </t>
+ <t>
+ The server connection preface consists of a potentially empty <x:ref>SETTINGS</x:ref>
+ frame (<xref target="SETTINGS"/>) that MUST be the first frame the server sends in the
+ HTTP/2 connection.
+ </t>
+ <t>
+ The <x:ref>SETTINGS</x:ref> frames received from a peer as part of the connection preface
+ MUST be acknowledged (see <xref target="SettingsSync"/>) after sending the connection
+ preface.
+ </t>
+ <t>
+ To avoid unnecessary latency, clients are permitted to send additional frames to the
+ server immediately after sending the client connection preface, without waiting to receive
+ the server connection preface. It is important to note, however, that the server
+ connection preface <x:ref>SETTINGS</x:ref> frame might include parameters that necessarily
+ alter how a client is expected to communicate with the server. Upon receiving the
+ <x:ref>SETTINGS</x:ref> frame, the client is expected to honor any parameters established.
+ In some configurations, it is possible for the server to transmit <x:ref>SETTINGS</x:ref>
+ before the client sends additional frames, providing an opportunity to avoid this issue.
+ </t>
+ <t>
+ Clients and servers MUST treat an invalid connection preface as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>. A <x:ref>GOAWAY</x:ref> frame (<xref target="GOAWAY"/>)
+ MAY be omitted in this case, since an invalid preface indicates that the peer is not using
+ HTTP/2.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="FramingLayer" title="HTTP Frames">
+ <t>
+ Once the HTTP/2 connection is established, endpoints can begin exchanging frames.
+ </t>
+
+ <section anchor="FrameHeader" title="Frame Format">
+ <t>
+ All frames begin with a fixed 9-octet header followed by a variable-length payload.
+ </t>
+ <figure title="Frame Layout">
+ <artwork type="inline"><![CDATA[
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Length (24) |
+ +---------------+---------------+---------------+
+ | Type (8) | Flags (8) |
+ +-+-+-----------+---------------+-------------------------------+
+ |R| Stream Identifier (31) |
+ +=+=============================================================+
+ | Frame Payload (0...) ...
+ +---------------------------------------------------------------+
+]]></artwork>
+ </figure>
+ <t>
+ The fields of the frame header are defined as:
+ <list style="hanging">
+ <x:lt hangText="Length:">
+ <t>
+ The length of the frame payload expressed as an unsigned 24-bit integer. Values
+ greater than 2<x:sup>14</x:sup> (16,384) MUST NOT be sent unless the receiver has
+ set a larger value for <x:ref>SETTINGS_MAX_FRAME_SIZE</x:ref>.
+ </t>
+ <t>
+ The 9 octets of the frame header are not included in this value.
+ </t>
+ </x:lt>
+ <x:lt hangText="Type:">
+ <t>
+ The 8-bit type of the frame. The frame type determines the format and semantics of
+ the frame. Implementations MUST ignore and discard any frame that has a type that
+ is unknown.
+ </t>
+ </x:lt>
+ <x:lt hangText="Flags:">
+ <t>
+ An 8-bit field reserved for frame-type specific boolean flags.
+ </t>
+ <t>
+ Flags are assigned semantics specific to the indicated frame type. Flags that have
+ no defined semantics for a particular frame type MUST be ignored, and MUST be left
+ unset (0) when sending.
+ </t>
+ </x:lt>
+ <x:lt hangText="R:">
+ <t>
+ A reserved 1-bit field. The semantics of this bit are undefined and the bit MUST
+ remain unset (0) when sending and MUST be ignored when receiving.
+ </t>
+ </x:lt>
+ <x:lt hangText="Stream Identifier:">
+ <t>
+ A 31-bit stream identifier (see <xref target="StreamIdentifiers"/>). The value 0 is
+ reserved for frames that are associated with the connection as a whole as opposed to
+ an individual stream.
+ </t>
+ </x:lt>
+ </list>
+ </t>
+ <t>
+ The structure and content of the frame payload is dependent entirely on the frame type.
+ </t>
+ </section>
+
+ <section anchor="FrameSize" title="Frame Size">
+ <t>
+ The size of a frame payload is limited by the maximum size that a receiver advertises in
+ the <x:ref>SETTINGS_MAX_FRAME_SIZE</x:ref> setting. This setting can have any value
+ between 2<x:sup>14</x:sup> (16,384) and 2<x:sup>24</x:sup>-1 (16,777,215) octets,
+ inclusive.
+ </t>
+ <t>
+ All implementations MUST be capable of receiving and minimally processing frames up to
+ 2<x:sup>14</x:sup> octets in length, plus the 9 octet <xref target="FrameHeader">frame
+ header</xref>. The size of the frame header is not included when describing frame sizes.
+ <list style="hanging">
+ <t hangText="Note:">
+ Certain frame types, such as <xref target="PING">PING</xref>, impose additional limits
+ on the amount of payload data allowed.
+ </t>
+ </list>
+ </t>
+ <t>
+ If a frame size exceeds any defined limit, or is too small to contain mandatory frame
+ data, the endpoint MUST send a <x:ref>FRAME_SIZE_ERROR</x:ref> error. A frame size error
+ in a frame that could alter the state of the entire connection MUST be treated as a <xref
+ target="ConnectionErrorHandler">connection error</xref>; this includes any frame carrying
+ a <xref target="HeaderBlock">header block</xref> (that is, <x:ref>HEADERS</x:ref>,
+ <x:ref>PUSH_PROMISE</x:ref>, and <x:ref>CONTINUATION</x:ref>), <x:ref>SETTINGS</x:ref>,
+ and any <x:ref>WINDOW_UPDATE</x:ref> frame with a stream identifier of 0.
+ </t>
+ <t>
+ Endpoints are not obligated to use all available space in a frame. Responsiveness can be
+ improved by using frames that are smaller than the permitted maximum size. Sending large
+ frames can result in delays in sending time-sensitive frames (such
+ <x:ref>RST_STREAM</x:ref>, <x:ref>WINDOW_UPDATE</x:ref>, or <x:ref>PRIORITY</x:ref>)
+ which if blocked by the transmission of a large frame, could affect performance.
+ </t>
+ </section>
+
+ <section anchor="HeaderBlock" title="Header Compression and Decompression">
+ <t>
+ Just as in HTTP/1, a header field in HTTP/2 is a name with one or more associated values.
+ They are used within HTTP request and response messages as well as server push operations
+ (see <xref target="PushResources" />).
+ </t>
+ <t>
+ Header lists are collections of zero or more header fields. When transmitted over a
+ connection, a header list is serialized into a header block using <xref
+ target="COMPRESSION">HTTP Header Compression</xref>. The serialized header block is then
+ divided into one or more octet sequences, called header block fragments, and transmitted
+ within the payload of <xref target="HEADERS">HEADERS</xref>, <xref
+ target="PUSH_PROMISE">PUSH_PROMISE</xref> or <xref
+ target="CONTINUATION">CONTINUATION</xref> frames.
+ </t>
+ <t>
+ The <xref target="COOKIE">Cookie header field</xref> is treated specially by the HTTP
+ mapping (see <xref target="CompressCookie"/>).
+ </t>
+ <t>
+ A receiving endpoint reassembles the header block by concatenating its fragments, then
+ decompresses the block to reconstruct the header list.
+ </t>
+ <t>
+ A complete header block consists of either:
+ <list style="symbols">
+ <t>
+ a single <x:ref>HEADERS</x:ref> or <x:ref>PUSH_PROMISE</x:ref> frame,
+ with the END_HEADERS flag set, or
+ </t>
+ <t>
+ a <x:ref>HEADERS</x:ref> or <x:ref>PUSH_PROMISE</x:ref> frame with the END_HEADERS
+ flag cleared and one or more <x:ref>CONTINUATION</x:ref> frames,
+ where the last <x:ref>CONTINUATION</x:ref> frame has the END_HEADERS flag set.
+ </t>
+ </list>
+ </t>
+ <t>
+ Header compression is stateful. One compression context and one decompression context is
+ used for the entire connection. Each header block is processed as a discrete unit.
+ Header blocks MUST be transmitted as a contiguous sequence of frames, with no interleaved
+ frames of any other type or from any other stream. The last frame in a sequence of
+ <x:ref>HEADERS</x:ref> or <x:ref>CONTINUATION</x:ref> frames MUST have the END_HEADERS
+ flag set. The last frame in a sequence of <x:ref>PUSH_PROMISE</x:ref> or
+ <x:ref>CONTINUATION</x:ref> frames MUST have the END_HEADERS flag set. This allows a
+ header block to be logically equivalent to a single frame.
+ </t>
+ <t>
+ Header block fragments can only be sent as the payload of <x:ref>HEADERS</x:ref>,
+ <x:ref>PUSH_PROMISE</x:ref> or <x:ref>CONTINUATION</x:ref> frames, because these frames
+ carry data that can modify the compression context maintained by a receiver. An endpoint
+ receiving <x:ref>HEADERS</x:ref>, <x:ref>PUSH_PROMISE</x:ref> or
+ <x:ref>CONTINUATION</x:ref> frames MUST reassemble header blocks and perform decompression
+ even if the frames are to be discarded. A receiver MUST terminate the connection with a
+ <xref target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>COMPRESSION_ERROR</x:ref> if it does not decompress a header block.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="StreamsLayer" title="Streams and Multiplexing">
+ <t>
+ A "stream" is an independent, bi-directional sequence of frames exchanged between the client
+ and server within an HTTP/2 connection. Streams have several important characteristics:
+ <list style="symbols">
+ <t>
+ A single HTTP/2 connection can contain multiple concurrently open streams, with either
+ endpoint interleaving frames from multiple streams.
+ </t>
+ <t>
+ Streams can be established and used unilaterally or shared by either the client or
+ server.
+ </t>
+ <t>
+ Streams can be closed by either endpoint.
+ </t>
+ <t>
+ The order in which frames are sent on a stream is significant. Recipients process frames
+ in the order they are received. In particular, the order of <x:ref>HEADERS</x:ref>,
+ and <x:ref>DATA</x:ref> frames is semantically significant.
+ </t>
+ <t>
+ Streams are identified by an integer. Stream identifiers are assigned to streams by the
+ endpoint initiating the stream.
+ </t>
+ </list>
+ </t>
+
+ <section anchor="StreamStates" title="Stream States">
+ <t>
+ The lifecycle of a stream is shown in <xref target="StreamStatesFigure"/>.
+ </t>
+
+ <figure anchor="StreamStatesFigure" title="Stream States">
+ <artwork type="drawing">
+ <![CDATA[
+ +--------+
+ PP | | PP
+ ,--------| idle |--------.
+ / | | \
+ v +--------+ v
+ +----------+ | +----------+
+ | | | H | |
+ ,---| reserved | | | reserved |---.
+ | | (local) | v | (remote) | |
+ | +----------+ +--------+ +----------+ |
+ | | ES | | ES | |
+ | | H ,-------| open |-------. | H |
+ | | / | | \ | |
+ | v v +--------+ v v |
+ | +----------+ | +----------+ |
+ | | half | | | half | |
+ | | closed | | R | closed | |
+ | | (remote) | | | (local) | |
+ | +----------+ | +----------+ |
+ | | v | |
+ | | ES / R +--------+ ES / R | |
+ | `----------->| |<-----------' |
+ | R | closed | R |
+ `-------------------->| |<--------------------'
+ +--------+
+
+ H: HEADERS frame (with implied CONTINUATIONs)
+ PP: PUSH_PROMISE frame (with implied CONTINUATIONs)
+ ES: END_STREAM flag
+ R: RST_STREAM frame
+]]>
+ </artwork>
+ </figure>
+
+ <t>
+ Note that this diagram shows stream state transitions and the frames and flags that affect
+ those transitions only. In this regard, <x:ref>CONTINUATION</x:ref> frames do not result
+ in state transitions; they are effectively part of the <x:ref>HEADERS</x:ref> or
+ <x:ref>PUSH_PROMISE</x:ref> that they follow. For this purpose, the END_STREAM flag is
+ processed as a separate event to the frame that bears it; a <x:ref>HEADERS</x:ref> frame
+ with the END_STREAM flag set can cause two state transitions.
+ </t>
+ <t>
+ Both endpoints have a subjective view of the state of a stream that could be different
+ when frames are in transit. Endpoints do not coordinate the creation of streams; they are
+ created unilaterally by either endpoint. The negative consequences of a mismatch in
+ states are limited to the "closed" state after sending <x:ref>RST_STREAM</x:ref>, where
+ frames might be received for some time after closing.
+ </t>
+ <t>
+ Streams have the following states:
+ <list style="hanging">
+
+ <x:lt hangText="idle:">
+ <t>
+ <vspace blankLines="0"/>
+ All streams start in the "idle" state. In this state, no frames have been
+ exchanged.
+ </t>
+ <t>
+ The following transitions are valid from this state:
+ <list style="symbols">
+ <t>
+ Sending or receiving a <x:ref>HEADERS</x:ref> frame causes the stream to become
+ "open". The stream identifier is selected as described in <xref
+ target="StreamIdentifiers"/>. The same <x:ref>HEADERS</x:ref> frame can also
+ cause a stream to immediately become "half closed".
+ </t>
+ <t>
+ Sending a <x:ref>PUSH_PROMISE</x:ref> frame marks the associated stream for
+ later use. The stream state for the reserved stream transitions to "reserved
+ (local)".
+ </t>
+ <t>
+ Receiving a <x:ref>PUSH_PROMISE</x:ref> frame marks the associated stream as
+ reserved by the remote peer. The state of the stream becomes "reserved
+ (remote)".
+ </t>
+ </list>
+ </t>
+ <t>
+ Receiving any frames other than <x:ref>HEADERS</x:ref> or
+ <x:ref>PUSH_PROMISE</x:ref> on a stream in this state MUST be treated as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ </x:lt>
+
+ <x:lt hangText="reserved (local):">
+ <t>
+ <vspace blankLines="0"/>
+ A stream in the "reserved (local)" state is one that has been promised by sending a
+ <x:ref>PUSH_PROMISE</x:ref> frame. A <x:ref>PUSH_PROMISE</x:ref> frame reserves an
+ idle stream by associating the stream with an open stream that was initiated by the
+ remote peer (see <xref target="PushResources"/>).
+ </t>
+ <t>
+ In this state, only the following transitions are possible:
+ <list style="symbols">
+ <t>
+ The endpoint can send a <x:ref>HEADERS</x:ref> frame. This causes the stream to
+ open in a "half closed (remote)" state.
+ </t>
+ <t>
+ Either endpoint can send a <x:ref>RST_STREAM</x:ref> frame to cause the stream
+ to become "closed". This releases the stream reservation.
+ </t>
+ </list>
+ </t>
+ <t>
+ An endpoint MUST NOT send any type of frame other than <x:ref>HEADERS</x:ref> or
+ <x:ref>RST_STREAM</x:ref> in this state.
+ </t>
+ <t>
+ A <x:ref>PRIORITY</x:ref> frame MAY be received in this state. Receiving any type
+ of frame other than <x:ref>RST_STREAM</x:ref> or <x:ref>PRIORITY</x:ref> on a stream
+ in this state MUST be treated as a <xref target="ConnectionErrorHandler">connection
+ error</xref> of type <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ </x:lt>
+
+ <x:lt hangText="reserved (remote):">
+ <t>
+ <vspace blankLines="0"/>
+ A stream in the "reserved (remote)" state has been reserved by a remote peer.
+ </t>
+ <t>
+ In this state, only the following transitions are possible:
+ <list style="symbols">
+ <t>
+ Receiving a <x:ref>HEADERS</x:ref> frame causes the stream to transition to
+ "half closed (local)".
+ </t>
+ <t>
+ Either endpoint can send a <x:ref>RST_STREAM</x:ref> frame to cause the stream
+ to become "closed". This releases the stream reservation.
+ </t>
+ </list>
+ </t>
+ <t>
+ An endpoint MAY send a <x:ref>PRIORITY</x:ref> frame in this state to reprioritize
+ the reserved stream. An endpoint MUST NOT send any type of frame other than
+ <x:ref>RST_STREAM</x:ref>, <x:ref>WINDOW_UPDATE</x:ref>, or <x:ref>PRIORITY</x:ref>
+ in this state.
+ </t>
+ <t>
+ Receiving any type of frame other than <x:ref>HEADERS</x:ref> or
+ <x:ref>RST_STREAM</x:ref> on a stream in this state MUST be treated as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ </x:lt>
+
+ <x:lt hangText="open:">
+ <t>
+ <vspace blankLines="0"/>
+ A stream in the "open" state may be used by both peers to send frames of any type.
+ In this state, sending peers observe advertised <xref target="FlowControl">stream
+ level flow control limits</xref>.
+ </t>
+ <t>
+ From this state either endpoint can send a frame with an END_STREAM flag set, which
+ causes the stream to transition into one of the "half closed" states: an endpoint
+ sending an END_STREAM flag causes the stream state to become "half closed (local)";
+ an endpoint receiving an END_STREAM flag causes the stream state to become "half
+ closed (remote)".
+ </t>
+ <t>
+ Either endpoint can send a <x:ref>RST_STREAM</x:ref> frame from this state, causing
+ it to transition immediately to "closed".
+ </t>
+ </x:lt>
+
+ <x:lt hangText="half closed (local):">
+ <t>
+ <vspace blankLines="0"/>
+ A stream that is in the "half closed (local)" state cannot be used for sending
+ frames. Only <x:ref>WINDOW_UPDATE</x:ref>, <x:ref>PRIORITY</x:ref> and
+ <x:ref>RST_STREAM</x:ref> frames can be sent in this state.
+ </t>
+ <t>
+ A stream transitions from this state to "closed" when a frame that contains an
+ END_STREAM flag is received, or when either peer sends a <x:ref>RST_STREAM</x:ref>
+ frame.
+ </t>
+ <t>
+ A receiver can ignore <x:ref>WINDOW_UPDATE</x:ref> frames in this state, which might
+ arrive for a short period after a frame bearing the END_STREAM flag is sent.
+ </t>
+ <t>
+ <x:ref>PRIORITY</x:ref> frames received in this state are used to reprioritize
+ streams that depend on the current stream.
+ </t>
+ </x:lt>
+
+ <x:lt hangText="half closed (remote):">
+ <t>
+ <vspace blankLines="0"/>
+ A stream that is "half closed (remote)" is no longer being used by the peer to send
+ frames. In this state, an endpoint is no longer obligated to maintain a receiver
+ flow control window if it performs flow control.
+ </t>
+ <t>
+ If an endpoint receives additional frames for a stream that is in this state, other
+ than <x:ref>WINDOW_UPDATE</x:ref>, <x:ref>PRIORITY</x:ref> or
+ <x:ref>RST_STREAM</x:ref>, it MUST respond with a <xref
+ target="StreamErrorHandler">stream error</xref> of type
+ <x:ref>STREAM_CLOSED</x:ref>.
+ </t>
+ <t>
+ A stream that is "half closed (remote)" can be used by the endpoint to send frames
+ of any type. In this state, the endpoint continues to observe advertised <xref
+ target="FlowControl">stream level flow control limits</xref>.
+ </t>
+ <t>
+ A stream can transition from this state to "closed" by sending a frame that contains
+ an END_STREAM flag, or when either peer sends a <x:ref>RST_STREAM</x:ref> frame.
+ </t>
+ </x:lt>
+
+ <x:lt hangText="closed:">
+ <t>
+ <vspace blankLines="0"/>
+ The "closed" state is the terminal state.
+ </t>
+ <t>
+ An endpoint MUST NOT send frames other than <x:ref>PRIORITY</x:ref> on a closed
+ stream. An endpoint that receives any frame other than <x:ref>PRIORITY</x:ref>
+ after receiving a <x:ref>RST_STREAM</x:ref> MUST treat that as a <xref
+ target="StreamErrorHandler">stream error</xref> of type
+ <x:ref>STREAM_CLOSED</x:ref>. Similarly, an endpoint that receives any frames after
+ receiving a frame with the END_STREAM flag set MUST treat that as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>STREAM_CLOSED</x:ref>, unless the frame is permitted as described below.
+ </t>
+ <t>
+ <x:ref>WINDOW_UPDATE</x:ref> or <x:ref>RST_STREAM</x:ref> frames can be received in
+ this state for a short period after a <x:ref>DATA</x:ref> or <x:ref>HEADERS</x:ref>
+ frame containing an END_STREAM flag is sent. Until the remote peer receives and
+ processes <x:ref>RST_STREAM</x:ref> or the frame bearing the END_STREAM flag, it
+ might send frames of these types. Endpoints MUST ignore
+ <x:ref>WINDOW_UPDATE</x:ref> or <x:ref>RST_STREAM</x:ref> frames received in this
+ state, though endpoints MAY choose to treat frames that arrive a significant time
+ after sending END_STREAM as a <xref target="ConnectionErrorHandler">connection
+ error</xref> of type <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ <x:ref>PRIORITY</x:ref> frames can be sent on closed streams to prioritize streams
+ that are dependent on the closed stream. Endpoints SHOULD process
+ <x:ref>PRIORITY</x:ref> frame, though they can be ignored if the stream has been
+ removed from the dependency tree (see <xref target="priority-gc"/>).
+ </t>
+ <t>
+ If this state is reached as a result of sending a <x:ref>RST_STREAM</x:ref> frame,
+ the peer that receives the <x:ref>RST_STREAM</x:ref> might have already sent - or
+ enqueued for sending - frames on the stream that cannot be withdrawn. An endpoint
+ MUST ignore frames that it receives on closed streams after it has sent a
+ <x:ref>RST_STREAM</x:ref> frame. An endpoint MAY choose to limit the period over
+ which it ignores frames and treat frames that arrive after this time as being in
+ error.
+ </t>
+ <t>
+ Flow controlled frames (i.e., <x:ref>DATA</x:ref>) received after sending
+ <x:ref>RST_STREAM</x:ref> are counted toward the connection flow control window.
+ Even though these frames might be ignored, because they are sent before the sender
+ receives the <x:ref>RST_STREAM</x:ref>, the sender will consider the frames to count
+ against the flow control window.
+ </t>
+ <t>
+ An endpoint might receive a <x:ref>PUSH_PROMISE</x:ref> frame after it sends
+ <x:ref>RST_STREAM</x:ref>. <x:ref>PUSH_PROMISE</x:ref> causes a stream to become
+ "reserved" even if the associated stream has been reset. Therefore, a
+ <x:ref>RST_STREAM</x:ref> is needed to close an unwanted promised stream.
+ </t>
+ </x:lt>
+ </list>
+ </t>
+ <t>
+ In the absence of more specific guidance elsewhere in this document, implementations
+ SHOULD treat the receipt of a frame that is not expressly permitted in the description of
+ a state as a <xref target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>. Frame of unknown types are ignored.
+ </t>
+ <t>
+ An example of the state transitions for an HTTP request/response exchange can be found in
+ <xref target="HttpSequence"/>. An example of the state transitions for server push can be
+ found in <xref target="PushRequests"/> and <xref target="PushResponses"/>.
+ </t>
+
+ <section anchor="StreamIdentifiers" title="Stream Identifiers">
+ <t>
+ Streams are identified with an unsigned 31-bit integer. Streams initiated by a client
+ MUST use odd-numbered stream identifiers; those initiated by the server MUST use
+ even-numbered stream identifiers. A stream identifier of zero (0x0) is used for
+ connection control messages; the stream identifier zero cannot be used to establish a
+ new stream.
+ </t>
+ <t>
+ HTTP/1.1 requests that are upgraded to HTTP/2 (see <xref target="discover-http"/>) are
+ responded to with a stream identifier of one (0x1). After the upgrade
+ completes, stream 0x1 is "half closed (local)" to the client. Therefore, stream 0x1
+ cannot be selected as a new stream identifier by a client that upgrades from HTTP/1.1.
+ </t>
+ <t>
+ The identifier of a newly established stream MUST be numerically greater than all
+ streams that the initiating endpoint has opened or reserved. This governs streams that
+ are opened using a <x:ref>HEADERS</x:ref> frame and streams that are reserved using
+ <x:ref>PUSH_PROMISE</x:ref>. An endpoint that receives an unexpected stream identifier
+ MUST respond with a <xref target="ConnectionErrorHandler">connection error</xref> of
+ type <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ The first use of a new stream identifier implicitly closes all streams in the "idle"
+ state that might have been initiated by that peer with a lower-valued stream identifier.
+ For example, if a client sends a <x:ref>HEADERS</x:ref> frame on stream 7 without ever
+ sending a frame on stream 5, then stream 5 transitions to the "closed" state when the
+ first frame for stream 7 is sent or received.
+ </t>
+ <t>
+ Stream identifiers cannot be reused. Long-lived connections can result in an endpoint
+ exhausting the available range of stream identifiers. A client that is unable to
+ establish a new stream identifier can establish a new connection for new streams. A
+ server that is unable to establish a new stream identifier can send a
+ <x:ref>GOAWAY</x:ref> frame so that the client is forced to open a new connection for
+ new streams.
+ </t>
+ </section>
+
+ <section title="Stream Concurrency">
+ <t>
+ A peer can limit the number of concurrently active streams using the
+ <x:ref>SETTINGS_MAX_CONCURRENT_STREAMS</x:ref> parameter (see <xref
+ target="SettingValues"/>) within a <x:ref>SETTINGS</x:ref> frame. The maximum concurrent
+ streams setting is specific to each endpoint and applies only to the peer that receives
+ the setting. That is, clients specify the maximum number of concurrent streams the
+ server can initiate, and servers specify the maximum number of concurrent streams the
+ client can initiate.
+ </t>
+ <t>
+ Streams that are in the "open" state, or either of the "half closed" states count toward
+ the maximum number of streams that an endpoint is permitted to open. Streams in any of
+ these three states count toward the limit advertised in the
+ <x:ref>SETTINGS_MAX_CONCURRENT_STREAMS</x:ref> setting. Streams in either of the
+ "reserved" states do not count toward the stream limit.
+ </t>
+ <t>
+ Endpoints MUST NOT exceed the limit set by their peer. An endpoint that receives a
+ <x:ref>HEADERS</x:ref> frame that causes their advertised concurrent stream limit to be
+ exceeded MUST treat this as a <xref target="StreamErrorHandler">stream error</xref>. An
+ endpoint that wishes to reduce the value of
+ <x:ref>SETTINGS_MAX_CONCURRENT_STREAMS</x:ref> to a value that is below the current
+ number of open streams can either close streams that exceed the new value or allow
+ streams to complete.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="FlowControl" title="Flow Control">
+ <t>
+ Using streams for multiplexing introduces contention over use of the TCP connection,
+ resulting in blocked streams. A flow control scheme ensures that streams on the same
+ connection do not destructively interfere with each other. Flow control is used for both
+ individual streams and for the connection as a whole.
+ </t>
+ <t>
+ HTTP/2 provides for flow control through use of the <xref
+ target="WINDOW_UPDATE">WINDOW_UPDATE frame</xref>.
+ </t>
+
+ <section anchor="fc-principles" title="Flow Control Principles">
+ <t>
+ HTTP/2 stream flow control aims to allow a variety of flow control algorithms to be
+ used without requiring protocol changes. Flow control in HTTP/2 has the following
+ characteristics:
+ <list style="numbers">
+ <t>
+ Flow control is specific to a connection; i.e., it is "hop-by-hop", not
+ "end-to-end".
+ </t>
+ <t>
+ Flow control is based on window update frames. Receivers advertise how many octets
+ they are prepared to receive on a stream and for the entire connection. This is a
+ credit-based scheme.
+ </t>
+ <t>
+ Flow control is directional with overall control provided by the receiver. A
+ receiver MAY choose to set any window size that it desires for each stream and for
+ the entire connection. A sender MUST respect flow control limits imposed by a
+ receiver. Clients, servers and intermediaries all independently advertise their
+ flow control window as a receiver and abide by the flow control limits set by
+ their peer when sending.
+ </t>
+ <t>
+ The initial value for the flow control window is 65,535 octets for both new streams
+ and the overall connection.
+ </t>
+ <t>
+ The frame type determines whether flow control applies to a frame. Of the frames
+ specified in this document, only <x:ref>DATA</x:ref> frames are subject to flow
+ control; all other frame types do not consume space in the advertised flow control
+ window. This ensures that important control frames are not blocked by flow control.
+ </t>
+ <t>
+ Flow control cannot be disabled.
+ </t>
+ <t>
+ HTTP/2 defines only the format and semantics of the <x:ref>WINDOW_UPDATE</x:ref>
+ frame (<xref target="WINDOW_UPDATE"/>). This document does not stipulate how a
+ receiver decides when to send this frame or the value that it sends, nor does it
+ specify how a sender chooses to send packets. Implementations are able to select
+ any algorithm that suits their needs.
+ </t>
+ </list>
+ </t>
+ <t>
+ Implementations are also responsible for managing how requests and responses are sent
+ based on priority; choosing how to avoid head of line blocking for requests; and
+ managing the creation of new streams. Algorithm choices for these could interact with
+ any flow control algorithm.
+ </t>
+ </section>
+
+ <section anchor="DisableFlowControl" title="Appropriate Use of Flow Control">
+ <t>
+ Flow control is defined to protect endpoints that are operating under resource
+ constraints. For example, a proxy needs to share memory between many connections, and
+ also might have a slow upstream connection and a fast downstream one. Flow control
+ addresses cases where the receiver is unable process data on one stream, yet wants to
+ continue to process other streams in the same connection.
+ </t>
+ <t>
+ Deployments that do not require this capability can advertise a flow control window of
+ the maximum size, incrementing the available space when new data is received. This
+ effectively disables flow control for that receiver. Conversely, a sender is always
+ subject to the flow control window advertised by the receiver.
+ </t>
+ <t>
+ Deployments with constrained resources (for example, memory) can employ flow control to
+ limit the amount of memory a peer can consume. Note, however, that this can lead to
+ suboptimal use of available network resources if flow control is enabled without
+ knowledge of the bandwidth-delay product (see <xref target="RFC1323"/>).
+ </t>
+ <t>
+ Even with full awareness of the current bandwidth-delay product, implementation of flow
+ control can be difficult. When using flow control, the receiver MUST read from the TCP
+ receive buffer in a timely fashion. Failure to do so could lead to a deadlock when
+ critical frames, such as <x:ref>WINDOW_UPDATE</x:ref>, are not read and acted upon.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="StreamPriority" title="Stream priority">
+ <t>
+ A client can assign a priority for a new stream by including prioritization information in
+ the <xref target="HEADERS">HEADERS frame</xref> that opens the stream. For an existing
+ stream, the <xref target="PRIORITY">PRIORITY frame</xref> can be used to change the
+ priority.
+ </t>
+ <t>
+ The purpose of prioritization is to allow an endpoint to express how it would prefer its
+ peer allocate resources when managing concurrent streams. Most importantly, priority can
+ be used to select streams for transmitting frames when there is limited capacity for
+ sending.
+ </t>
+ <t>
+ Streams can be prioritized by marking them as dependent on the completion of other streams
+ (<xref target="pri-depend"/>). Each dependency is assigned a relative weight, a number
+ that is used to determine the relative proportion of available resources that are assigned
+ to streams dependent on the same stream.
+ </t>
+ <!--
+ Note that stream dependencies have not yet been validated in practice. The theory
+ might be fairly sound, but there are no implementations currently sending these. If it
+ turns out that they are not useful, or actively harmful, implementations will be requested
+ to avoid creating stream dependencies.
+ -->
+ <t>
+ Explicitly setting the priority for a stream is input to a prioritization process. It
+ does not guarantee any particular processing or transmission order for the stream relative
+ to any other stream. An endpoint cannot force a peer to process concurrent streams in a
+ particular order using priority. Expressing priority is therefore only ever a suggestion.
+ </t>
+ <t>
+ Providing prioritization information is optional, so default values are used if no
+ explicit indicator is provided (<xref target="pri-default"/>).
+ </t>
+
+ <section title="Stream Dependencies" anchor="pri-depend">
+ <t>
+ Each stream can be given an explicit dependency on another stream. Including a
+ dependency expresses a preference to allocate resources to the identified stream rather
+ than to the dependent stream.
+ </t>
+ <t>
+ A stream that is not dependent on any other stream is given a stream dependency of 0x0.
+ In other words, the non-existent stream 0 forms the root of the tree.
+ </t>
+ <t>
+ A stream that depends on another stream is a dependent stream. The stream upon which a
+ stream is dependent is a parent stream. A dependency on a stream that is not currently
+ in the tree - such as a stream in the "idle" state - results in that stream being given
+ a <xref target="pri-default">default priority</xref>.
+ </t>
+ <t>
+ When assigning a dependency on another stream, the stream is added as a new dependency
+ of the parent stream. Dependent streams that share the same parent are not ordered with
+ respect to each other. For example, if streams B and C are dependent on stream A, and
+ if stream D is created with a dependency on stream A, this results in a dependency order
+ of A followed by B, C, and D in any order.
+ </t>
+ <figure title="Example of Default Dependency Creation">
+ <artwork type="inline"><![CDATA[
+ A A
+ / \ ==> /|\
+ B C B D C
+]]></artwork>
+ </figure>
+ <t>
+ An exclusive flag allows for the insertion of a new level of dependencies. The
+ exclusive flag causes the stream to become the sole dependency of its parent stream,
+ causing other dependencies to become dependent on the exclusive stream. In the
+ previous example, if stream D is created with an exclusive dependency on stream A, this
+ results in D becoming the dependency parent of B and C.
+ </t>
+ <figure title="Example of Exclusive Dependency Creation">
+ <artwork type="inline"><![CDATA[
+ A
+ A |
+ / \ ==> D
+ B C / \
+ B C
+]]></artwork>
+ </figure>
+ <t>
+ Inside the dependency tree, a dependent stream SHOULD only be allocated resources if all
+ of the streams that it depends on (the chain of parent streams up to 0x0) are either
+ closed, or it is not possible to make progress on them.
+ </t>
+ <t>
+ A stream cannot depend on itself. An endpoint MUST treat this as a <xref
+ target="StreamErrorHandler">stream error</xref> of type <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ </section>
+
+ <section title="Dependency Weighting">
+ <t>
+ All dependent streams are allocated an integer weight between 1 and 256 (inclusive).
+ </t>
+ <t>
+ Streams with the same parent SHOULD be allocated resources proportionally based on their
+ weight. Thus, if stream B depends on stream A with weight 4, and C depends on stream A
+ with weight 12, and if no progress can be made on A, stream B ideally receives one third
+ of the resources allocated to stream C.
+ </t>
+ </section>
+
+ <section anchor="reprioritize" title="Reprioritization">
+ <t>
+ Stream priorities are changed using the <x:ref>PRIORITY</x:ref> frame. Setting a
+ dependency causes a stream to become dependent on the identified parent stream.
+ </t>
+ <t>
+ Dependent streams move with their parent stream if the parent is reprioritized. Setting
+ a dependency with the exclusive flag for a reprioritized stream moves all the
+ dependencies of the new parent stream to become dependent on the reprioritized stream.
+ </t>
+ <t>
+ If a stream is made dependent on one of its own dependencies, the formerly dependent
+ stream is first moved to be dependent on the reprioritized stream's previous parent.
+ The moved dependency retains its weight.
+ </t>
+ <figure title="Example of Dependency Reordering">
+ <preamble>
+ For example, consider an original dependency tree where B and C depend on A, D and E
+ depend on C, and F depends on D. If A is made dependent on D, then D takes the place
+ of A. All other dependency relationships stay the same, except for F, which becomes
+ dependent on A if the reprioritization is exclusive.
+ </preamble>
+ <artwork type="inline"><![CDATA[
+ ? ? ? ?
+ | / \ | |
+ A D A D D
+ / \ / / \ / \ |
+ B C ==> F B C ==> F A OR A
+ / \ | / \ /|\
+ D E E B C B C F
+ | | |
+ F E E
+ (intermediate) (non-exclusive) (exclusive)
+]]></artwork>
+ </figure>
+ </section>
+
+ <section anchor="priority-gc" title="Prioritization State Management">
+ <t>
+ When a stream is removed from the dependency tree, its dependencies can be moved to
+ become dependent on the parent of the closed stream. The weights of new dependencies
+ are recalculated by distributing the weight of the dependency of the closed stream
+ proportionally based on the weights of its dependencies.
+ </t>
+ <t>
+ Streams that are removed from the dependency tree cause some prioritization information
+ to be lost. Resources are shared between streams with the same parent stream, which
+ means that if a stream in that set closes or becomes blocked, any spare capacity
+ allocated to a stream is distributed to the immediate neighbors of the stream. However,
+ if the common dependency is removed from the tree, those streams share resources with
+ streams at the next highest level.
+ </t>
+ <t>
+ For example, assume streams A and B share a parent, and streams C and D both depend on
+ stream A. Prior to the removal of stream A, if streams A and D are unable to proceed,
+ then stream C receives all the resources dedicated to stream A. If stream A is removed
+ from the tree, the weight of stream A is divided between streams C and D. If stream D
+ is still unable to proceed, this results in stream C receiving a reduced proportion of
+ resources. For equal starting weights, C receives one third, rather than one half, of
+ available resources.
+ </t>
+ <t>
+ It is possible for a stream to become closed while prioritization information that
+ creates a dependency on that stream is in transit. If a stream identified in a
+ dependency has no associated priority information, then the dependent stream is instead
+ assigned a <xref target="pri-default">default priority</xref>. This potentially creates
+ suboptimal prioritization, since the stream could be given a priority that is different
+ to what is intended.
+ </t>
+ <t>
+ To avoid these problems, an endpoint SHOULD retain stream prioritization state for a
+ period after streams become closed. The longer state is retained, the lower the chance
+ that streams are assigned incorrect or default priority values.
+ </t>
+ <t>
+ This could create a large state burden for an endpoint, so this state MAY be limited.
+ An endpoint MAY apply a fixed upper limit on the number of closed streams for which
+ prioritization state is tracked to limit state exposure. The amount of additional state
+ an endpoint maintains could be dependent on load; under high load, prioritization state
+ can be discarded to limit resource commitments. In extreme cases, an endpoint could
+ even discard prioritization state for active or reserved streams. If a fixed limit is
+ applied, endpoints SHOULD maintain state for at least as many streams as allowed by
+ their setting for <x:ref>SETTINGS_MAX_CONCURRENT_STREAMS</x:ref>.
+ </t>
+ <t>
+ An endpoint receiving a <x:ref>PRIORITY</x:ref> frame that changes the priority of a
+ closed stream SHOULD alter the dependencies of the streams that depend on it, if it has
+ retained enough state to do so.
+ </t>
+ </section>
+
+ <section title="Default Priorities" anchor="pri-default">
+ <t>
+ Providing priority information is optional. Streams are assigned a non-exclusive
+ dependency on stream 0x0 by default. <xref target="PushResources">Pushed streams</xref>
+ initially depend on their associated stream. In both cases, streams are assigned a
+ default weight of 16.
+ </t>
+ </section>
+ </section>
+
+ <section title="Error Handling">
+ <t>
+ HTTP/2 framing permits two classes of error:
+ <list style="symbols">
+ <t>
+ An error condition that renders the entire connection unusable is a connection error.
+ </t>
+ <t>
+ An error in an individual stream is a stream error.
+ </t>
+ </list>
+ </t>
+ <t>
+ A list of error codes is included in <xref target="ErrorCodes"/>.
+ </t>
+
+ <section anchor="ConnectionErrorHandler" title="Connection Error Handling">
+ <t>
+ A connection error is any error which prevents further processing of the framing layer,
+ or which corrupts any connection state.
+ </t>
+ <t>
+ An endpoint that encounters a connection error SHOULD first send a <x:ref>GOAWAY</x:ref>
+ frame (<xref target="GOAWAY"/>) with the stream identifier of the last stream that it
+ successfully received from its peer. The <x:ref>GOAWAY</x:ref> frame includes an error
+ code that indicates why the connection is terminating. After sending the
+ <x:ref>GOAWAY</x:ref> frame, the endpoint MUST close the TCP connection.
+ </t>
+ <t>
+ It is possible that the <x:ref>GOAWAY</x:ref> will not be reliably received by the
+ receiving endpoint (see <xref target="RFC7230" x:fmt=","
+ x:rel="#persistent.tear-down"/>). In the event of a connection error,
+ <x:ref>GOAWAY</x:ref> only provides a best effort attempt to communicate with the peer
+ about why the connection is being terminated.
+ </t>
+ <t>
+ An endpoint can end a connection at any time. In particular, an endpoint MAY choose to
+ treat a stream error as a connection error. Endpoints SHOULD send a
+ <x:ref>GOAWAY</x:ref> frame when ending a connection, providing that circumstances
+ permit it.
+ </t>
+ </section>
+
+ <section anchor="StreamErrorHandler" title="Stream Error Handling">
+ <t>
+ A stream error is an error related to a specific stream that does not affect processing
+ of other streams.
+ </t>
+ <t>
+ An endpoint that detects a stream error sends a <x:ref>RST_STREAM</x:ref> frame (<xref
+ target="RST_STREAM"/>) that contains the stream identifier of the stream where the error
+ occurred. The <x:ref>RST_STREAM</x:ref> frame includes an error code that indicates the
+ type of error.
+ </t>
+ <t>
+ A <x:ref>RST_STREAM</x:ref> is the last frame that an endpoint can send on a stream.
+ The peer that sends the <x:ref>RST_STREAM</x:ref> frame MUST be prepared to receive any
+ frames that were sent or enqueued for sending by the remote peer. These frames can be
+ ignored, except where they modify connection state (such as the state maintained for
+ <xref target="HeaderBlock">header compression</xref>, or flow control).
+ </t>
+ <t>
+ Normally, an endpoint SHOULD NOT send more than one <x:ref>RST_STREAM</x:ref> frame for
+ any stream. However, an endpoint MAY send additional <x:ref>RST_STREAM</x:ref> frames if
+ it receives frames on a closed stream after more than a round-trip time. This behavior
+ is permitted to deal with misbehaving implementations.
+ </t>
+ <t>
+ An endpoint MUST NOT send a <x:ref>RST_STREAM</x:ref> in response to an
+ <x:ref>RST_STREAM</x:ref> frame, to avoid looping.
+ </t>
+ </section>
+
+ <section title="Connection Termination">
+ <t>
+ If the TCP connection is closed or reset while streams remain in open or half closed
+ states, then the endpoint MUST assume that those streams were abnormally interrupted and
+ could be incomplete.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="extensibility" title="Extending HTTP/2">
+ <t>
+ HTTP/2 permits extension of the protocol. Protocol extensions can be used to provide
+ additional services or alter any aspect of the protocol, within the limitations described
+ in this section. Extensions are effective only within the scope of a single HTTP/2
+ connection.
+ </t>
+ <t>
+ Extensions are permitted to use new <xref target="FrameHeader">frame types</xref>, new
+ <xref target="SettingValues">settings</xref>, or new <xref target="ErrorCodes">error
+ codes</xref>. Registries are established for managing these extension points: <xref
+ target="iana-frames">frame types</xref>, <xref target="iana-settings">settings</xref> and
+ <xref target="iana-errors">error codes</xref>.
+ </t>
+ <t>
+ Implementations MUST ignore unknown or unsupported values in all extensible protocol
+ elements. Implementations MUST discard frames that have unknown or unsupported types.
+ This means that any of these extension points can be safely used by extensions without
+ prior arrangement or negotiation. However, extension frames that appear in the middle of
+ a <xref target="HeaderBlock">header block</xref> are not permitted; these MUST be treated
+ as a <xref target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ However, extensions that could change the semantics of existing protocol components MUST
+ be negotiated before being used. For example, an extension that changes the layout of the
+ <x:ref>HEADERS</x:ref> frame cannot be used until the peer has given a positive signal
+ that this is acceptable. In this case, it could also be necessary to coordinate when the
+ revised layout comes into effect. Note that treating any frame other than
+ <x:ref>DATA</x:ref> frames as flow controlled is such a change in semantics, and can only
+ be done through negotiation.
+ </t>
+ <t>
+ This document doesn't mandate a specific method for negotiating the use of an extension,
+ but notes that a <xref target="SettingValues">setting</xref> could be used for that
+ purpose. If both peers set a value that indicates willingness to use the extension, then
+ the extension can be used. If a setting is used for extension negotiation, the initial
+ value MUST be defined so that the extension is initially disabled.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="FrameTypes" title="Frame Definitions">
+ <t>
+ This specification defines a number of frame types, each identified by a unique 8-bit type
+ code. Each frame type serves a distinct purpose either in the establishment and management
+ of the connection as a whole, or of individual streams.
+ </t>
+ <t>
+ The transmission of specific frame types can alter the state of a connection. If endpoints
+ fail to maintain a synchronized view of the connection state, successful communication
+ within the connection will no longer be possible. Therefore, it is important that endpoints
+ have a shared comprehension of how the state is affected by the use any given frame.
+ </t>
+
+ <section anchor="DATA" title="DATA">
+ <t>
+ DATA frames (type=0x0) convey arbitrary, variable-length sequences of octets associated
+ with a stream. One or more DATA frames are used, for instance, to carry HTTP request or
+ response payloads.
+ </t>
+ <t>
+ DATA frames MAY also contain arbitrary padding. Padding can be added to DATA frames to
+ obscure the size of messages.
+ </t>
+ <figure title="DATA Frame Payload">
+ <artwork type="inline"><![CDATA[
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Pad Length? (8)|
+ +---------------+-----------------------------------------------+
+ | Data (*) ...
+ +---------------------------------------------------------------+
+ | Padding (*) ...
+ +---------------------------------------------------------------+
+]]></artwork>
+ </figure>
+ <t>
+ The DATA frame contains the following fields:
+ <list style="hanging">
+ <t hangText="Pad Length:">
+ An 8-bit field containing the length of the frame padding in units of octets. This
+ field is optional and is only present if the PADDED flag is set.
+ </t>
+ <t hangText="Data:">
+ Application data. The amount of data is the remainder of the frame payload after
+ subtracting the length of the other fields that are present.
+ </t>
+ <t hangText="Padding:">
+ Padding octets that contain no application semantic value. Padding octets MUST be set
+ to zero when sending and ignored when receiving.
+ </t>
+ </list>
+ </t>
+
+ <t>
+ The DATA frame defines the following flags:
+ <list style="hanging">
+ <t hangText="END_STREAM (0x1):">
+ Bit 1 being set indicates that this frame is the last that the endpoint will send for
+ the identified stream. Setting this flag causes the stream to enter one of <xref
+ target="StreamStates">the "half closed" states or the "closed" state</xref>.
+ </t>
+ <t hangText="PADDED (0x8):">
+ Bit 4 being set indicates that the Pad Length field and any padding that it describes
+ is present.
+ </t>
+ </list>
+ </t>
+ <t>
+ DATA frames MUST be associated with a stream. If a DATA frame is received whose stream
+ identifier field is 0x0, the recipient MUST respond with a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ DATA frames are subject to flow control and can only be sent when a stream is in the
+ "open" or "half closed (remote)" states. The entire DATA frame payload is included in flow
+ control, including Pad Length and Padding fields if present. If a DATA frame is received
+ whose stream is not in "open" or "half closed (local)" state, the recipient MUST respond
+ with a <xref target="StreamErrorHandler">stream error</xref> of type
+ <x:ref>STREAM_CLOSED</x:ref>.
+ </t>
+ <t>
+ The total number of padding octets is determined by the value of the Pad Length field. If
+ the length of the padding is greater than the length of the frame payload, the recipient
+ MUST treat this as a <xref target="ConnectionErrorHandler">connection error</xref> of
+ type <x:ref>PROTOCOL_ERROR</x:ref>.
+ <list style="hanging">
+ <t hangText="Note:">
+ A frame can be increased in size by one octet by including a Pad Length field with a
+ value of zero.
+ </t>
+ </list>
+ </t>
+ <t>
+ Padding is a security feature; see <xref target="padding"/>.
+ </t>
+ </section>
+
+ <section anchor="HEADERS" title="HEADERS">
+ <t>
+ The HEADERS frame (type=0x1) is used to <xref target="StreamStates">open a stream</xref>,
+ and additionally carries a header block fragment. HEADERS frames can be sent on a stream
+ in the "open" or "half closed (remote)" states.
+ </t>
+ <figure title="HEADERS Frame Payload">
+ <artwork type="inline"><![CDATA[
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Pad Length? (8)|
+ +-+-------------+-----------------------------------------------+
+ |E| Stream Dependency? (31) |
+ +-+-------------+-----------------------------------------------+
+ | Weight? (8) |
+ +-+-------------+-----------------------------------------------+
+ | Header Block Fragment (*) ...
+ +---------------------------------------------------------------+
+ | Padding (*) ...
+ +---------------------------------------------------------------+
+]]></artwork>
+ </figure>
+ <t>
+ The HEADERS frame payload has the following fields:
+ <list style="hanging">
+ <t hangText="Pad Length:">
+ An 8-bit field containing the length of the frame padding in units of octets. This
+ field is only present if the PADDED flag is set.
+ </t>
+ <t hangText="E:">
+ A single bit flag indicates that the stream dependency is exclusive, see <xref
+ target="StreamPriority"/>. This field is only present if the PRIORITY flag is set.
+ </t>
+ <t hangText="Stream Dependency:">
+ A 31-bit stream identifier for the stream that this stream depends on, see <xref
+ target="StreamPriority"/>. This field is only present if the PRIORITY flag is set.
+ </t>
+ <t hangText="Weight:">
+ An 8-bit weight for the stream, see <xref target="StreamPriority"/>. Add one to the
+ value to obtain a weight between 1 and 256. This field is only present if the
+ PRIORITY flag is set.
+ </t>
+ <t hangText="Header Block Fragment:">
+ A <xref target="HeaderBlock">header block fragment</xref>.
+ </t>
+ <t hangText="Padding:">
+ Padding octets that contain no application semantic value. Padding octets MUST be set
+ to zero when sending and ignored when receiving.
+ </t>
+ </list>
+ </t>
+
+ <t>
+ The HEADERS frame defines the following flags:
+ <list style="hanging">
+ <x:lt hangText="END_STREAM (0x1):">
+ <t>
+ Bit 1 being set indicates that the <xref target="HeaderBlock">header block</xref> is
+ the last that the endpoint will send for the identified stream. Setting this flag
+ causes the stream to enter one of <xref target="StreamStates">"half closed"
+ states</xref>.
+ </t>
+ <t>
+ A HEADERS frame carries the END_STREAM flag that signals the end of a stream.
+ However, a HEADERS frame with the END_STREAM flag set can be followed by
+ <x:ref>CONTINUATION</x:ref> frames on the same stream. Logically, the
+ <x:ref>CONTINUATION</x:ref> frames are part of the HEADERS frame.
+ </t>
+ </x:lt>
+ <x:lt hangText="END_HEADERS (0x4):">
+ <t>
+ Bit 3 being set indicates that this frame contains an entire <xref
+ target="HeaderBlock">header block</xref> and is not followed by any
+ <x:ref>CONTINUATION</x:ref> frames.
+ </t>
+ <t>
+ A HEADERS frame without the END_HEADERS flag set MUST be followed by a
+ <x:ref>CONTINUATION</x:ref> frame for the same stream. A receiver MUST treat the
+ receipt of any other type of frame or a frame on a different stream as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ </x:lt>
+ <x:lt hangText="PADDED (0x8):">
+ <t>
+ Bit 4 being set indicates that the Pad Length field and any padding that it
+ describes is present.
+ </t>
+ </x:lt>
+ <x:lt hangText="PRIORITY (0x20):">
+ <t>
+ Bit 6 being set indicates that the Exclusive Flag (E), Stream Dependency, and Weight
+ fields are present; see <xref target="StreamPriority"/>.
+ </t>
+ </x:lt>
+ </list>
+ </t>
+
+ <t>
+ The payload of a HEADERS frame contains a <xref target="HeaderBlock">header block
+ fragment</xref>. A header block that does not fit within a HEADERS frame is continued in
+ a <xref target="CONTINUATION">CONTINUATION frame</xref>.
+ </t>
+
+ <t>
+ HEADERS frames MUST be associated with a stream. If a HEADERS frame is received whose
+ stream identifier field is 0x0, the recipient MUST respond with a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+
+ <t>
+ The HEADERS frame changes the connection state as described in <xref
+ target="HeaderBlock"/>.
+ </t>
+
+ <t>
+ The HEADERS frame includes optional padding. Padding fields and flags are identical to
+ those defined for <xref target="DATA">DATA frames</xref>.
+ </t>
+ <t>
+ Prioritization information in a HEADERS frame is logically equivalent to a separate
+ <x:ref>PRIORITY</x:ref> frame, but inclusion in HEADERS avoids the potential for churn in
+ stream prioritization when new streams are created. Priorization fields in HEADERS frames
+ subsequent to the first on a stream <xref target="reprioritize">reprioritize the
+ stream</xref>.
+ </t>
+ </section>
+
+ <section anchor="PRIORITY" title="PRIORITY">
+ <t>
+ The PRIORITY frame (type=0x2) specifies the <xref target="StreamPriority">sender-advised
+ priority of a stream</xref>. It can be sent at any time for an existing stream, including
+ closed streams. This enables reprioritization of existing streams.
+ </t>
+ <figure title="PRIORITY Frame Payload">
+ <artwork type="inline"><![CDATA[
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |E| Stream Dependency (31) |
+ +-+-------------+-----------------------------------------------+
+ | Weight (8) |
+ +-+-------------+
+]]></artwork>
+ </figure>
+ <t>
+ The payload of a PRIORITY frame contains the following fields:
+ <list style="hanging">
+ <t hangText="E:">
+ A single bit flag indicates that the stream dependency is exclusive, see <xref
+ target="StreamPriority"/>.
+ </t>
+ <t hangText="Stream Dependency:">
+ A 31-bit stream identifier for the stream that this stream depends on, see <xref
+ target="StreamPriority"/>.
+ </t>
+ <t hangText="Weight:">
+ An 8-bit weight for the identified stream dependency, see <xref
+ target="StreamPriority"/>. Add one to the value to obtain a weight between 1 and 256.
+ </t>
+ </list>
+ </t>
+
+ <t>
+ The PRIORITY frame does not define any flags.
+ </t>
+
+ <t>
+ The PRIORITY frame is associated with an existing stream. If a PRIORITY frame is received
+ with a stream identifier of 0x0, the recipient MUST respond with a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ The PRIORITY frame can be sent on a stream in any of the "reserved (remote)", "open",
+ "half closed (local)", "half closed (remote)", or "closed" states, though it cannot be
+ sent between consecutive frames that comprise a single <xref target="HeaderBlock">header
+ block</xref>. Note that this frame could arrive after processing or frame sending has
+ completed, which would cause it to have no effect on the current stream. For a stream
+ that is in the "half closed (remote)" or "closed" - state, this frame can only affect
+ processing of the current stream and not frame transmission.
+ </t>
+ <t>
+ The PRIORITY frame is the only frame that can be sent for a stream in the "closed" state.
+ This allows for the reprioritization of a group of dependent streams by altering the
+ priority of a parent stream, which might be closed. However, a PRIORITY frame sent on a
+ closed stream risks being ignored due to the peer having discarded priority state
+ information for that stream.
+ </t>
+ </section>
+
+ <section anchor="RST_STREAM" title="RST_STREAM">
+ <t>
+ The RST_STREAM frame (type=0x3) allows for abnormal termination of a stream. When sent by
+ the initiator of a stream, it indicates that they wish to cancel the stream or that an
+ error condition has occurred. When sent by the receiver of a stream, it indicates that
+ either the receiver is rejecting the stream, requesting that the stream be cancelled, or
+ that an error condition has occurred.
+ </t>
+ <figure title="RST_STREAM Frame Payload">
+ <artwork type="inline"><![CDATA[
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Error Code (32) |
+ +---------------------------------------------------------------+
+]]></artwork>
+ </figure>
+
+ <t>
+ The RST_STREAM frame contains a single unsigned, 32-bit integer identifying the <xref
+ target="ErrorCodes">error code</xref>. The error code indicates why the stream is being
+ terminated.
+ </t>
+
+ <t>
+ The RST_STREAM frame does not define any flags.
+ </t>
+
+ <t>
+ The RST_STREAM frame fully terminates the referenced stream and causes it to enter the
+ closed state. After receiving a RST_STREAM on a stream, the receiver MUST NOT send
+ additional frames for that stream, with the exception of <x:ref>PRIORITY</x:ref>. However,
+ after sending the RST_STREAM, the sending endpoint MUST be prepared to receive and process
+ additional frames sent on the stream that might have been sent by the peer prior to the
+ arrival of the RST_STREAM.
+ </t>
+
+ <t>
+ RST_STREAM frames MUST be associated with a stream. If a RST_STREAM frame is received
+ with a stream identifier of 0x0, the recipient MUST treat this as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+
+ <t>
+ RST_STREAM frames MUST NOT be sent for a stream in the "idle" state. If a RST_STREAM
+ frame identifying an idle stream is received, the recipient MUST treat this as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+
+ </section>
+
+ <section anchor="SETTINGS" title="SETTINGS">
+ <t>
+ The SETTINGS frame (type=0x4) conveys configuration parameters that affect how endpoints
+ communicate, such as preferences and constraints on peer behavior. The SETTINGS frame is
+ also used to acknowledge the receipt of those parameters. Individually, a SETTINGS
+ parameter can also be referred to as a "setting".
+ </t>
+ <t>
+ SETTINGS parameters are not negotiated; they describe characteristics of the sending peer,
+ which are used by the receiving peer. Different values for the same parameter can be
+ advertised by each peer. For example, a client might set a high initial flow control
+ window, whereas a server might set a lower value to conserve resources.
+ </t>
+
+ <t>
+ A SETTINGS frame MUST be sent by both endpoints at the start of a connection, and MAY be
+ sent at any other time by either endpoint over the lifetime of the connection.
+ Implementations MUST support all of the parameters defined by this specification.
+ </t>
+
+ <t>
+ Each parameter in a SETTINGS frame replaces any existing value for that parameter.
+ Parameters are processed in the order in which they appear, and a receiver of a SETTINGS
+ frame does not need to maintain any state other than the current value of its
+ parameters. Therefore, the value of a SETTINGS parameter is the last value that is seen by
+ a receiver.
+ </t>
+ <t>
+ SETTINGS parameters are acknowledged by the receiving peer. To enable this, the SETTINGS
+ frame defines the following flag:
+ <list style="hanging">
+ <t hangText="ACK (0x1):">
+ Bit 1 being set indicates that this frame acknowledges receipt and application of the
+ peer's SETTINGS frame. When this bit is set, the payload of the SETTINGS frame MUST
+ be empty. Receipt of a SETTINGS frame with the ACK flag set and a length field value
+ other than 0 MUST be treated as a <xref target="ConnectionErrorHandler">connection
+ error</xref> of type <x:ref>FRAME_SIZE_ERROR</x:ref>. For more info, see <xref
+ target="SettingsSync">Settings Synchronization</xref>.
+ </t>
+ </list>
+ </t>
+ <t>
+ SETTINGS frames always apply to a connection, never a single stream. The stream
+ identifier for a SETTINGS frame MUST be zero (0x0). If an endpoint receives a SETTINGS
+ frame whose stream identifier field is anything other than 0x0, the endpoint MUST respond
+ with a <xref target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ The SETTINGS frame affects connection state. A badly formed or incomplete SETTINGS frame
+ MUST be treated as a <xref target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+
+ <section title="SETTINGS Format" anchor="SettingFormat">
+ <t>
+ The payload of a SETTINGS frame consists of zero or more parameters, each consisting of
+ an unsigned 16-bit setting identifier and an unsigned 32-bit value.
+ </t>
+
+ <figure title="Setting Format">
+ <artwork type="inline"><![CDATA[
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Identifier (16) |
+ +-------------------------------+-------------------------------+
+ | Value (32) |
+ +---------------------------------------------------------------+
+]]></artwork>
+ </figure>
+ </section>
+
+ <section anchor="SettingValues" title="Defined SETTINGS Parameters">
+ <t>
+ The following parameters are defined:
+ <list style="hanging">
+ <x:lt hangText="SETTINGS_HEADER_TABLE_SIZE (0x1):"
+ anchor="SETTINGS_HEADER_TABLE_SIZE">
+ <t>
+ Allows the sender to inform the remote endpoint of the maximum size of the header
+ compression table used to decode header blocks, in octets. The encoder can select
+ any size equal to or less than this value by using signaling specific to the
+ header compression format inside a header block. The initial value is 4,096
+ octets.
+ </t>
+ </x:lt>
+ <x:lt hangText="SETTINGS_ENABLE_PUSH (0x2):"
+ anchor="SETTINGS_ENABLE_PUSH">
+ <t>
+ This setting can be use to disable <xref target="PushResources">server
+ push</xref>. An endpoint MUST NOT send a <x:ref>PUSH_PROMISE</x:ref> frame if it
+ receives this parameter set to a value of 0. An endpoint that has both set this
+ parameter to 0 and had it acknowledged MUST treat the receipt of a
+ <x:ref>PUSH_PROMISE</x:ref> frame as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ The initial value is 1, which indicates that server push is permitted. Any value
+ other than 0 or 1 MUST be treated as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ </x:lt>
+ <x:lt hangText="SETTINGS_MAX_CONCURRENT_STREAMS (0x3):"
+ anchor="SETTINGS_MAX_CONCURRENT_STREAMS">
+ <t>
+ Indicates the maximum number of concurrent streams that the sender will allow.
+ This limit is directional: it applies to the number of streams that the sender
+ permits the receiver to create. Initially there is no limit to this value. It is
+ recommended that this value be no smaller than 100, so as to not unnecessarily
+ limit parallelism.
+ </t>
+ <t>
+ A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special
+ by endpoints. A zero value does prevent the creation of new streams, however this
+ can also happen for any limit that is exhausted with active streams. Servers
+ SHOULD only set a zero value for short durations; if a server does not wish to
+ accept requests, closing the connection could be preferable.
+ </t>
+ </x:lt>
+ <x:lt hangText="SETTINGS_INITIAL_WINDOW_SIZE (0x4):"
+ anchor="SETTINGS_INITIAL_WINDOW_SIZE">
+ <t>
+ Indicates the sender's initial window size (in octets) for stream level flow
+ control. The initial value is 2<x:sup>16</x:sup>-1 (65,535) octets.
+ </t>
+ <t>
+ This setting affects the window size of all streams, including existing streams,
+ see <xref target="InitialWindowSize"/>.
+ </t>
+ <t>
+ Values above the maximum flow control window size of 2<x:sup>31</x:sup>-1 MUST
+ be treated as a <xref target="ConnectionErrorHandler">connection error</xref> of
+ type <x:ref>FLOW_CONTROL_ERROR</x:ref>.
+ </t>
+ </x:lt>
+ <x:lt hangText="SETTINGS_MAX_FRAME_SIZE (0x5):"
+ anchor="SETTINGS_MAX_FRAME_SIZE">
+ <t>
+ Indicates the size of the largest frame payload that the sender is willing to
+ receive, in octets.
+ </t>
+ <t>
+ The initial value is 2<x:sup>14</x:sup> (16,384) octets. The value advertised by
+ an endpoint MUST be between this initial value and the maximum allowed frame size
+ (2<x:sup>24</x:sup>-1 or 16,777,215 octets), inclusive. Values outside this range
+ MUST be treated as a <xref target="ConnectionErrorHandler">connection error</xref>
+ of type <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ </x:lt>
+ <x:lt hangText="SETTINGS_MAX_HEADER_LIST_SIZE (0x6):"
+ anchor="SETTINGS_MAX_HEADER_LIST_SIZE">
+ <t>
+ This advisory setting informs a peer of the maximum size of header list that the
+ sender is prepared to accept, in octets. The value is based on the uncompressed
+ size of header fields, including the length of the name and value in octets plus
+ an overhead of 32 octets for each header field.
+ </t>
+ <t>
+ For any given request, a lower limit than what is advertised MAY be enforced. The
+ initial value of this setting is unlimited.
+ </t>
+ </x:lt>
+ </list>
+ </t>
+ <t>
+ An endpoint that receives a SETTINGS frame with any unknown or unsupported identifier
+ MUST ignore that setting.
+ </t>
+ </section>
+
+ <section anchor="SettingsSync" title="Settings Synchronization">
+ <t>
+ Most values in SETTINGS benefit from or require an understanding of when the peer has
+ received and applied the changed parameter values. In order to provide
+ such synchronization timepoints, the recipient of a SETTINGS frame in which the ACK flag
+ is not set MUST apply the updated parameters as soon as possible upon receipt.
+ </t>
+ <t>
+ The values in the SETTINGS frame MUST be processed in the order they appear, with no
+ other frame processing between values. Unsupported parameters MUST be ignored. Once
+ all values have been processed, the recipient MUST immediately emit a SETTINGS frame
+ with the ACK flag set. Upon receiving a SETTINGS frame with the ACK flag set, the sender
+ of the altered parameters can rely on the setting having been applied.
+ </t>
+ <t>
+ If the sender of a SETTINGS frame does not receive an acknowledgement within a
+ reasonable amount of time, it MAY issue a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>SETTINGS_TIMEOUT</x:ref>.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="PUSH_PROMISE" title="PUSH_PROMISE">
+ <t>
+ The PUSH_PROMISE frame (type=0x5) is used to notify the peer endpoint in advance of
+ streams the sender intends to initiate. The PUSH_PROMISE frame includes the unsigned
+ 31-bit identifier of the stream the endpoint plans to create along with a set of headers
+ that provide additional context for the stream. <xref target="PushResources"/> contains a
+ thorough description of the use of PUSH_PROMISE frames.
+ </t>
+
+ <figure title="PUSH_PROMISE Payload Format">
+ <artwork type="inline"><![CDATA[
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |Pad Length? (8)|
+ +-+-------------+-----------------------------------------------+
+ |R| Promised Stream ID (31) |
+ +-+-----------------------------+-------------------------------+
+ | Header Block Fragment (*) ...
+ +---------------------------------------------------------------+
+ | Padding (*) ...
+ +---------------------------------------------------------------+
+]]></artwork>
+ </figure>
+ <t>
+ The PUSH_PROMISE frame payload has the following fields:
+ <list style="hanging">
+ <t hangText="Pad Length:">
+ An 8-bit field containing the length of the frame padding in units of octets. This
+ field is only present if the PADDED flag is set.
+ </t>
+ <t hangText="R:">
+ A single reserved bit.
+ </t>
+ <t hangText="Promised Stream ID:">
+ An unsigned 31-bit integer that identifies the stream that is reserved by the
+ PUSH_PROMISE. The promised stream identifier MUST be a valid choice for the next
+ stream sent by the sender (see <xref target="StreamIdentifiers">new stream
+ identifier</xref>).
+ </t>
+ <t hangText="Header Block Fragment:">
+ A <xref target="HeaderBlock">header block fragment</xref> containing request header
+ fields.
+ </t>
+ <t hangText="Padding:">
+ Padding octets.
+ </t>
+ </list>
+ </t>
+
+ <t>
+ The PUSH_PROMISE frame defines the following flags:
+ <list style="hanging">
+ <x:lt hangText="END_HEADERS (0x4):">
+ <t>
+ Bit 3 being set indicates that this frame contains an entire <xref
+ target="HeaderBlock">header block</xref> and is not followed by any
+ <x:ref>CONTINUATION</x:ref> frames.
+ </t>
+ <t>
+ A PUSH_PROMISE frame without the END_HEADERS flag set MUST be followed by a
+ CONTINUATION frame for the same stream. A receiver MUST treat the receipt of any
+ other type of frame or a frame on a different stream as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ </x:lt>
+ <x:lt hangText="PADDED (0x8):">
+ <t>
+ Bit 4 being set indicates that the Pad Length field and any padding that it
+ describes is present.
+ </t>
+ </x:lt>
+ </list>
+ </t>
+
+ <t>
+ PUSH_PROMISE frames MUST be associated with an existing, peer-initiated stream. The stream
+ identifier of a PUSH_PROMISE frame indicates the stream it is associated with. If the
+ stream identifier field specifies the value 0x0, a recipient MUST respond with a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+
+ <t>
+ Promised streams are not required to be used in the order they are promised. The
+ PUSH_PROMISE only reserves stream identifiers for later use.
+ </t>
+
+ <t>
+ PUSH_PROMISE MUST NOT be sent if the <x:ref>SETTINGS_ENABLE_PUSH</x:ref> setting of the
+ peer endpoint is set to 0. An endpoint that has set this setting and has received
+ acknowledgement MUST treat the receipt of a PUSH_PROMISE frame as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ Recipients of PUSH_PROMISE frames can choose to reject promised streams by returning a
+ <x:ref>RST_STREAM</x:ref> referencing the promised stream identifier back to the sender of
+ the PUSH_PROMISE.
+ </t>
+
+ <t>
+ A PUSH_PROMISE frame modifies the connection state in two ways. The inclusion of a <xref
+ target="HeaderBlock">header block</xref> potentially modifies the state maintained for
+ header compression. PUSH_PROMISE also reserves a stream for later use, causing the
+ promised stream to enter the "reserved" state. A sender MUST NOT send a PUSH_PROMISE on a
+ stream unless that stream is either "open" or "half closed (remote)"; the sender MUST
+ ensure that the promised stream is a valid choice for a <xref
+ target="StreamIdentifiers">new stream identifier</xref> (that is, the promised stream MUST
+ be in the "idle" state).
+ </t>
+ <t>
+ Since PUSH_PROMISE reserves a stream, ignoring a PUSH_PROMISE frame causes the stream
+ state to become indeterminate. A receiver MUST treat the receipt of a PUSH_PROMISE on a
+ stream that is neither "open" nor "half closed (local)" as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>. However, an endpoint that has sent
+ <x:ref>RST_STREAM</x:ref> on the associated stream MUST handle PUSH_PROMISE frames that
+ might have been created before the <x:ref>RST_STREAM</x:ref> frame is received and
+ processed.
+ </t>
+ <t>
+ A receiver MUST treat the receipt of a PUSH_PROMISE that promises an <xref
+ target="StreamIdentifiers">illegal stream identifier</xref> (that is, an identifier for a
+ stream that is not currently in the "idle" state) as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+
+ <t>
+ The PUSH_PROMISE frame includes optional padding. Padding fields and flags are identical
+ to those defined for <xref target="DATA">DATA frames</xref>.
+ </t>
+ </section>
+
+ <section anchor="PING" title="PING">
+ <t>
+ The PING frame (type=0x6) is a mechanism for measuring a minimal round trip time from the
+ sender, as well as determining whether an idle connection is still functional. PING
+ frames can be sent from any endpoint.
+ </t>
+ <figure title="PING Payload Format">
+ <artwork type="inline"><![CDATA[
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | |
+ | Opaque Data (64) |
+ | |
+ +---------------------------------------------------------------+
+]]></artwork>
+ </figure>
+
+ <t>
+ In addition to the frame header, PING frames MUST contain 8 octets of data in the payload.
+ A sender can include any value it chooses and use those bytes in any fashion.
+ </t>
+ <t>
+ Receivers of a PING frame that does not include an ACK flag MUST send a PING frame with
+ the ACK flag set in response, with an identical payload. PING responses SHOULD be given
+ higher priority than any other frame.
+ </t>
+
+ <t>
+ The PING frame defines the following flags:
+ <list style="hanging">
+ <t hangText="ACK (0x1):">
+ Bit 1 being set indicates that this PING frame is a PING response. An endpoint MUST
+ set this flag in PING responses. An endpoint MUST NOT respond to PING frames
+ containing this flag.
+ </t>
+ </list>
+ </t>
+ <t>
+ PING frames are not associated with any individual stream. If a PING frame is received
+ with a stream identifier field value other than 0x0, the recipient MUST respond with a
+ <xref target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ Receipt of a PING frame with a length field value other than 8 MUST be treated as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>FRAME_SIZE_ERROR</x:ref>.
+ </t>
+
+ </section>
+
+ <section anchor="GOAWAY" title="GOAWAY">
+ <t>
+ The GOAWAY frame (type=0x7) informs the remote peer to stop creating streams on this
+ connection. GOAWAY can be sent by either the client or the server. Once sent, the sender
+ will ignore frames sent on any new streams with identifiers higher than the included last
+ stream identifier. Receivers of a GOAWAY frame MUST NOT open additional streams on the
+ connection, although a new connection can be established for new streams.
+ </t>
+ <t>
+ The purpose of this frame is to allow an endpoint to gracefully stop accepting new
+ streams, while still finishing processing of previously established streams. This enables
+ administrative actions, like server maintainance.
+ </t>
+ <t>
+ There is an inherent race condition between an endpoint starting new streams and the
+ remote sending a GOAWAY frame. To deal with this case, the GOAWAY contains the stream
+ identifier of the last peer-initiated stream which was or might be processed on the
+ sending endpoint in this connection. For instance, if the server sends a GOAWAY frame,
+ the identified stream is the highest numbered stream initiated by the client.
+ </t>
+ <t>
+ If the receiver of the GOAWAY has sent data on streams with a higher stream identifier
+ than what is indicated in the GOAWAY frame, those streams are not or will not be
+ processed. The receiver of the GOAWAY frame can treat the streams as though they had
+ never been created at all, thereby allowing those streams to be retried later on a new
+ connection.
+ </t>
+ <t>
+ Endpoints SHOULD always send a GOAWAY frame before closing a connection so that the remote
+ can know whether a stream has been partially processed or not. For example, if an HTTP
+ client sends a POST at the same time that a server closes a connection, the client cannot
+ know if the server started to process that POST request if the server does not send a
+ GOAWAY frame to indicate what streams it might have acted on.
+ </t>
+ <t>
+ An endpoint might choose to close a connection without sending GOAWAY for misbehaving
+ peers.
+ </t>
+
+ <figure title="GOAWAY Payload Format">
+ <artwork type="inline"><![CDATA[
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |R| Last-Stream-ID (31) |
+ +-+-------------------------------------------------------------+
+ | Error Code (32) |
+ +---------------------------------------------------------------+
+ | Additional Debug Data (*) |
+ +---------------------------------------------------------------+
+]]></artwork>
+ </figure>
+ <t>
+ The GOAWAY frame does not define any flags.
+ </t>
+ <t>
+ The GOAWAY frame applies to the connection, not a specific stream. An endpoint MUST treat
+ a <x:ref>GOAWAY</x:ref> frame with a stream identifier other than 0x0 as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ The last stream identifier in the GOAWAY frame contains the highest numbered stream
+ identifier for which the sender of the GOAWAY frame might have taken some action on, or
+ might yet take action on. All streams up to and including the identified stream might
+ have been processed in some way. The last stream identifier can be set to 0 if no streams
+ were processed.
+ <list style="hanging">
+ <t hangText="Note:">
+ In this context, "processed" means that some data from the stream was passed to some
+ higher layer of software that might have taken some action as a result.
+ </t>
+ </list>
+ If a connection terminates without a GOAWAY frame, the last stream identifier is
+ effectively the highest possible stream identifier.
+ </t>
+ <t>
+ On streams with lower or equal numbered identifiers that were not closed completely prior
+ to the connection being closed, re-attempting requests, transactions, or any protocol
+ activity is not possible, with the exception of idempotent actions like HTTP GET, PUT, or
+ DELETE. Any protocol activity that uses higher numbered streams can be safely retried
+ using a new connection.
+ </t>
+ <t>
+ Activity on streams numbered lower or equal to the last stream identifier might still
+ complete successfully. The sender of a GOAWAY frame might gracefully shut down a
+ connection by sending a GOAWAY frame, maintaining the connection in an open state until
+ all in-progress streams complete.
+ </t>
+ <t>
+ An endpoint MAY send multiple GOAWAY frames if circumstances change. For instance, an
+ endpoint that sends GOAWAY with <x:ref>NO_ERROR</x:ref> during graceful shutdown could
+ subsequently encounter an condition that requires immediate termination of the connection.
+ The last stream identifier from the last GOAWAY frame received indicates which streams
+ could have been acted upon. Endpoints MUST NOT increase the value they send in the last
+ stream identifier, since the peers might already have retried unprocessed requests on
+ another connection.
+ </t>
+ <t>
+ A client that is unable to retry requests loses all requests that are in flight when the
+ server closes the connection. This is especially true for intermediaries that might
+ not be serving clients using HTTP/2. A server that is attempting to gracefully shut down
+ a connection SHOULD send an initial GOAWAY frame with the last stream identifier set to
+ 2<x:sup>31</x:sup>-1 and a <x:ref>NO_ERROR</x:ref> code. This signals to the client that
+ a shutdown is imminent and that no further requests can be initiated. After waiting at
+ least one round trip time, the server can send another GOAWAY frame with an updated last
+ stream identifier. This ensures that a connection can be cleanly shut down without losing
+ requests.
+ </t>
+
+ <t>
+ After sending a GOAWAY frame, the sender can discard frames for streams with identifiers
+ higher than the identified last stream. However, any frames that alter connection state
+ cannot be completely ignored. For instance, <x:ref>HEADERS</x:ref>,
+ <x:ref>PUSH_PROMISE</x:ref> and <x:ref>CONTINUATION</x:ref> frames MUST be minimally
+ processed to ensure the state maintained for header compression is consistent (see <xref
+ target="HeaderBlock"/>); similarly DATA frames MUST be counted toward the connection flow
+ control window. Failure to process these frames can cause flow control or header
+ compression state to become unsynchronized.
+ </t>
+
+ <t>
+ The GOAWAY frame also contains a 32-bit <xref target="ErrorCodes">error code</xref> that
+ contains the reason for closing the connection.
+ </t>
+ <t>
+ Endpoints MAY append opaque data to the payload of any GOAWAY frame. Additional debug
+ data is intended for diagnostic purposes only and carries no semantic value. Debug
+ information could contain security- or privacy-sensitive data. Logged or otherwise
+ persistently stored debug data MUST have adequate safeguards to prevent unauthorized
+ access.
+ </t>
+ </section>
+
+ <section anchor="WINDOW_UPDATE" title="WINDOW_UPDATE">
+ <t>
+ The WINDOW_UPDATE frame (type=0x8) is used to implement flow control; see <xref
+ target="FlowControl"/> for an overview.
+ </t>
+ <t>
+ Flow control operates at two levels: on each individual stream and on the entire
+ connection.
+ </t>
+ <t>
+ Both types of flow control are hop-by-hop; that is, only between the two endpoints.
+ Intermediaries do not forward WINDOW_UPDATE frames between dependent connections.
+ However, throttling of data transfer by any receiver can indirectly cause the propagation
+ of flow control information toward the original sender.
+ </t>
+ <t>
+ Flow control only applies to frames that are identified as being subject to flow control.
+ Of the frame types defined in this document, this includes only <x:ref>DATA</x:ref> frames.
+ Frames that are exempt from flow control MUST be accepted and processed, unless the
+ receiver is unable to assign resources to handling the frame. A receiver MAY respond with
+ a <xref target="StreamErrorHandler">stream error</xref> or <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>FLOW_CONTROL_ERROR</x:ref> if it is unable to accept a frame.
+ </t>
+ <figure title="WINDOW_UPDATE Payload Format">
+ <artwork type="inline"><![CDATA[
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ |R| Window Size Increment (31) |
+ +-+-------------------------------------------------------------+
+]]></artwork>
+ </figure>
+ <t>
+ The payload of a WINDOW_UPDATE frame is one reserved bit, plus an unsigned 31-bit integer
+ indicating the number of octets that the sender can transmit in addition to the existing
+ flow control window. The legal range for the increment to the flow control window is 1 to
+ 2<x:sup>31</x:sup>-1 (0x7fffffff) octets.
+ </t>
+ <t>
+ The WINDOW_UPDATE frame does not define any flags.
+ </t>
+ <t>
+ The WINDOW_UPDATE frame can be specific to a stream or to the entire connection. In the
+ former case, the frame's stream identifier indicates the affected stream; in the latter,
+ the value "0" indicates that the entire connection is the subject of the frame.
+ </t>
+ <t>
+ A receiver MUST treat the receipt of a WINDOW_UPDATE frame with an flow control window
+ increment of 0 as a <xref target="StreamErrorHandler">stream error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>; errors on the connection flow control window MUST be
+ treated as a <xref target="ConnectionErrorHandler">connection error</xref>.
+ </t>
+ <t>
+ WINDOW_UPDATE can be sent by a peer that has sent a frame bearing the END_STREAM flag.
+ This means that a receiver could receive a WINDOW_UPDATE frame on a "half closed (remote)"
+ or "closed" stream. A receiver MUST NOT treat this as an error, see <xref
+ target="StreamStates"/>.
+ </t>
+ <t>
+ A receiver that receives a flow controlled frame MUST always account for its contribution
+ against the connection flow control window, unless the receiver treats this as a <xref
+ target="ConnectionErrorHandler">connection error</xref>. This is necessary even if the
+ frame is in error. Since the sender counts the frame toward the flow control window, if
+ the receiver does not, the flow control window at sender and receiver can become
+ different.
+ </t>
+
+ <section title="The Flow Control Window">
+ <t>
+ Flow control in HTTP/2 is implemented using a window kept by each sender on every
+ stream. The flow control window is a simple integer value that indicates how many octets
+ of data the sender is permitted to transmit; as such, its size is a measure of the
+ buffering capacity of the receiver.
+ </t>
+ <t>
+ Two flow control windows are applicable: the stream flow control window and the
+ connection flow control window. The sender MUST NOT send a flow controlled frame with a
+ length that exceeds the space available in either of the flow control windows advertised
+ by the receiver. Frames with zero length with the END_STREAM flag set (that is, an
+ empty <x:ref>DATA</x:ref> frame) MAY be sent if there is no available space in either
+ flow control window.
+ </t>
+ <t>
+ For flow control calculations, the 9 octet frame header is not counted.
+ </t>
+ <t>
+ After sending a flow controlled frame, the sender reduces the space available in both
+ windows by the length of the transmitted frame.
+ </t>
+ <t>
+ The receiver of a frame sends a WINDOW_UPDATE frame as it consumes data and frees up
+ space in flow control windows. Separate WINDOW_UPDATE frames are sent for the stream
+ and connection level flow control windows.
+ </t>
+ <t>
+ A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the
+ amount specified in the frame.
+ </t>
+ <t>
+ A sender MUST NOT allow a flow control window to exceed 2<x:sup>31</x:sup>-1 octets.
+ If a sender receives a WINDOW_UPDATE that causes a flow control window to exceed this
+ maximum it MUST terminate either the stream or the connection, as appropriate. For
+ streams, the sender sends a <x:ref>RST_STREAM</x:ref> with the error code of
+ <x:ref>FLOW_CONTROL_ERROR</x:ref> code; for the connection, a <x:ref>GOAWAY</x:ref>
+ frame with a <x:ref>FLOW_CONTROL_ERROR</x:ref> code.
+ </t>
+ <t>
+ Flow controlled frames from the sender and WINDOW_UPDATE frames from the receiver are
+ completely asynchronous with respect to each other. This property allows a receiver to
+ aggressively update the window size kept by the sender to prevent streams from stalling.
+ </t>
+ </section>
+
+ <section anchor="InitialWindowSize" title="Initial Flow Control Window Size">
+ <t>
+ When an HTTP/2 connection is first established, new streams are created with an initial
+ flow control window size of 65,535 octets. The connection flow control window is 65,535
+ octets. Both endpoints can adjust the initial window size for new streams by including
+ a value for <x:ref>SETTINGS_INITIAL_WINDOW_SIZE</x:ref> in the <x:ref>SETTINGS</x:ref>
+ frame that forms part of the connection preface. The connection flow control window can
+ only be changed using WINDOW_UPDATE frames.
+ </t>
+ <t>
+ Prior to receiving a <x:ref>SETTINGS</x:ref> frame that sets a value for
+ <x:ref>SETTINGS_INITIAL_WINDOW_SIZE</x:ref>, an endpoint can only use the default
+ initial window size when sending flow controlled frames. Similarly, the connection flow
+ control window is set to the default initial window size until a WINDOW_UPDATE frame is
+ received.
+ </t>
+ <t>
+ A <x:ref>SETTINGS</x:ref> frame can alter the initial flow control window size for all
+ current streams. When the value of <x:ref>SETTINGS_INITIAL_WINDOW_SIZE</x:ref> changes,
+ a receiver MUST adjust the size of all stream flow control windows that it maintains by
+ the difference between the new value and the old value.
+ </t>
+ <t>
+ A change to <x:ref>SETTINGS_INITIAL_WINDOW_SIZE</x:ref> can cause the available space in
+ a flow control window to become negative. A sender MUST track the negative flow control
+ window, and MUST NOT send new flow controlled frames until it receives WINDOW_UPDATE
+ frames that cause the flow control window to become positive.
+ </t>
+ <t>
+ For example, if the client sends 60KB immediately on connection establishment, and the
+ server sets the initial window size to be 16KB, the client will recalculate the
+ available flow control window to be -44KB on receipt of the <x:ref>SETTINGS</x:ref>
+ frame. The client retains a negative flow control window until WINDOW_UPDATE frames
+ restore the window to being positive, after which the client can resume sending.
+ </t>
+ <t>
+ A <x:ref>SETTINGS</x:ref> frame cannot alter the connection flow control window.
+ </t>
+ <t>
+ An endpoint MUST treat a change to <x:ref>SETTINGS_INITIAL_WINDOW_SIZE</x:ref> that
+ causes any flow control window to exceed the maximum size as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>FLOW_CONTROL_ERROR</x:ref>.
+ </t>
+ </section>
+
+ <section title="Reducing the Stream Window Size">
+ <t>
+ A receiver that wishes to use a smaller flow control window than the current size can
+ send a new <x:ref>SETTINGS</x:ref> frame. However, the receiver MUST be prepared to
+ receive data that exceeds this window size, since the sender might send data that
+ exceeds the lower limit prior to processing the <x:ref>SETTINGS</x:ref> frame.
+ </t>
+ <t>
+ After sending a SETTINGS frame that reduces the initial flow control window size, a
+ receiver has two options for handling streams that exceed flow control limits:
+ <list style="numbers">
+ <t>
+ The receiver can immediately send <x:ref>RST_STREAM</x:ref> with
+ <x:ref>FLOW_CONTROL_ERROR</x:ref> error code for the affected streams.
+ </t>
+ <t>
+ The receiver can accept the streams and tolerate the resulting head of line
+ blocking, sending WINDOW_UPDATE frames as it consumes data.
+ </t>
+ </list>
+ </t>
+ </section>
+ </section>
+
+ <section anchor="CONTINUATION" title="CONTINUATION">
+ <t>
+ The CONTINUATION frame (type=0x9) is used to continue a sequence of <xref
+ target="HeaderBlock">header block fragments</xref>. Any number of CONTINUATION frames can
+ be sent on an existing stream, as long as the preceding frame is on the same stream and is
+ a <x:ref>HEADERS</x:ref>, <x:ref>PUSH_PROMISE</x:ref> or CONTINUATION frame without the
+ END_HEADERS flag set.
+ </t>
+
+ <figure title="CONTINUATION Frame Payload">
+ <artwork type="inline"><![CDATA[
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ | Header Block Fragment (*) ...
+ +---------------------------------------------------------------+
+]]></artwork>
+ </figure>
+ <t>
+ The CONTINUATION frame payload contains a <xref target="HeaderBlock">header block
+ fragment</xref>.
+ </t>
+
+ <t>
+ The CONTINUATION frame defines the following flag:
+ <list style="hanging">
+ <x:lt hangText="END_HEADERS (0x4):">
+ <t>
+ Bit 3 being set indicates that this frame ends a <xref target="HeaderBlock">header
+ block</xref>.
+ </t>
+ <t>
+ If the END_HEADERS bit is not set, this frame MUST be followed by another
+ CONTINUATION frame. A receiver MUST treat the receipt of any other type of frame or
+ a frame on a different stream as a <xref target="ConnectionErrorHandler">connection
+ error</xref> of type <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ </x:lt>
+ </list>
+ </t>
+
+ <t>
+ The CONTINUATION frame changes the connection state as defined in <xref
+ target="HeaderBlock" />.
+ </t>
+
+ <t>
+ CONTINUATION frames MUST be associated with a stream. If a CONTINUATION frame is received
+ whose stream identifier field is 0x0, the recipient MUST respond with a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type PROTOCOL_ERROR.
+ </t>
+
+ <t>
+ A CONTINUATION frame MUST be preceded by a <x:ref>HEADERS</x:ref>,
+ <x:ref>PUSH_PROMISE</x:ref> or CONTINUATION frame without the END_HEADERS flag set. A
+ recipient that observes violation of this rule MUST respond with a <xref
+ target="ConnectionErrorHandler"> connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="ErrorCodes" title="Error Codes">
+ <t>
+ Error codes are 32-bit fields that are used in <x:ref>RST_STREAM</x:ref> and
+ <x:ref>GOAWAY</x:ref> frames to convey the reasons for the stream or connection error.
+ </t>
+
+ <t>
+ Error codes share a common code space. Some error codes apply only to either streams or the
+ entire connection and have no defined semantics in the other context.
+ </t>
+
+ <t>
+ The following error codes are defined:
+ <list style="hanging">
+ <t hangText="NO_ERROR (0x0):" anchor="NO_ERROR">
+ The associated condition is not as a result of an error. For example, a
+ <x:ref>GOAWAY</x:ref> might include this code to indicate graceful shutdown of a
+ connection.
+ </t>
+ <t hangText="PROTOCOL_ERROR (0x1):" anchor="PROTOCOL_ERROR">
+ The endpoint detected an unspecific protocol error. This error is for use when a more
+ specific error code is not available.
+ </t>
+ <t hangText="INTERNAL_ERROR (0x2):" anchor="INTERNAL_ERROR">
+ The endpoint encountered an unexpected internal error.
+ </t>
+ <t hangText="FLOW_CONTROL_ERROR (0x3):" anchor="FLOW_CONTROL_ERROR">
+ The endpoint detected that its peer violated the flow control protocol.
+ </t>
+ <t hangText="SETTINGS_TIMEOUT (0x4):" anchor="SETTINGS_TIMEOUT">
+ The endpoint sent a <x:ref>SETTINGS</x:ref> frame, but did not receive a response in a
+ timely manner. See <xref target="SettingsSync">Settings Synchronization</xref>.
+ </t>
+ <t hangText="STREAM_CLOSED (0x5):" anchor="STREAM_CLOSED">
+ The endpoint received a frame after a stream was half closed.
+ </t>
+ <t hangText="FRAME_SIZE_ERROR (0x6):" anchor="FRAME_SIZE_ERROR">
+ The endpoint received a frame with an invalid size.
+ </t>
+ <t hangText="REFUSED_STREAM (0x7):" anchor="REFUSED_STREAM">
+ The endpoint refuses the stream prior to performing any application processing, see
+ <xref target="Reliability"/> for details.
+ </t>
+ <t hangText="CANCEL (0x8):" anchor="CANCEL">
+ Used by the endpoint to indicate that the stream is no longer needed.
+ </t>
+ <t hangText="COMPRESSION_ERROR (0x9):" anchor="COMPRESSION_ERROR">
+ The endpoint is unable to maintain the header compression context for the connection.
+ </t>
+ <t hangText="CONNECT_ERROR (0xa):" anchor="CONNECT_ERROR">
+ The connection established in response to a <xref target="CONNECT">CONNECT
+ request</xref> was reset or abnormally closed.
+ </t>
+ <t hangText="ENHANCE_YOUR_CALM (0xb):" anchor="ENHANCE_YOUR_CALM">
+ The endpoint detected that its peer is exhibiting a behavior that might be generating
+ excessive load.
+ </t>
+ <t hangText="INADEQUATE_SECURITY (0xc):" anchor="INADEQUATE_SECURITY">
+ The underlying transport has properties that do not meet minimum security
+ requirements (see <xref target="TLSUsage"/>).
+ </t>
+ </list>
+ </t>
+ <t>
+ Unknown or unsupported error codes MUST NOT trigger any special behavior. These MAY be
+ treated by an implementation as being equivalent to <x:ref>INTERNAL_ERROR</x:ref>.
+ </t>
+ </section>
+
+ <section anchor="HTTPLayer" title="HTTP Message Exchanges">
+ <t>
+ HTTP/2 is intended to be as compatible as possible with current uses of HTTP. This means
+ that, from the application perspective, the features of the protocol are largely
+ unchanged. To achieve this, all request and response semantics are preserved, although the
+ syntax of conveying those semantics has changed.
+ </t>
+ <t>
+ Thus, the specification and requirements of HTTP/1.1 Semantics and Content <xref
+ target="RFC7231"/>, Conditional Requests <xref target="RFC7232"/>, Range Requests <xref
+ target="RFC7233"/>, Caching <xref target="RFC7234"/> and Authentication <xref
+ target="RFC7235"/> are applicable to HTTP/2. Selected portions of HTTP/1.1 Message Syntax
+ and Routing <xref target="RFC7230"/>, such as the HTTP and HTTPS URI schemes, are also
+ applicable in HTTP/2, but the expression of those semantics for this protocol are defined
+ in the sections below.
+ </t>
+
+ <section anchor="HttpSequence" title="HTTP Request/Response Exchange">
+ <t>
+ A client sends an HTTP request on a new stream, using a previously unused <xref
+ target="StreamIdentifiers">stream identifier</xref>. A server sends an HTTP response on
+ the same stream as the request.
+ </t>
+ <t>
+ An HTTP message (request or response) consists of:
+ <list style="numbers">
+ <t>
+ for a response only, zero or more <x:ref>HEADERS</x:ref> frames (each followed by zero
+ or more <x:ref>CONTINUATION</x:ref> frames) containing the message headers of
+ informational (1xx) HTTP responses (see <xref target="RFC7230" x:fmt=","
+ x:rel="#header.fields"/> and <xref target="RFC7231" x:fmt="," x:rel="#status.1xx"/>),
+ and
+ </t>
+ <t>
+ one <x:ref>HEADERS</x:ref> frame (followed by zero or more <x:ref>CONTINUATION</x:ref>
+ frames) containing the message headers (see <xref target="RFC7230" x:fmt=","
+ x:rel="#header.fields"/>), and
+ </t>
+ <t>
+ zero or more <x:ref>DATA</x:ref> frames containing the message payload (see <xref
+ target="RFC7230" x:fmt="," x:rel="#message.body"/>), and
+ </t>
+ <t>
+ optionally, one <x:ref>HEADERS</x:ref> frame, followed by zero or more
+ <x:ref>CONTINUATION</x:ref> frames containing the trailer-part, if present (see <xref
+ target="RFC7230" x:fmt="," x:rel="#chunked.trailer.part"/>).
+ </t>
+ </list>
+ The last frame in the sequence bears an END_STREAM flag, noting that a
+ <x:ref>HEADERS</x:ref> frame bearing the END_STREAM flag can be followed by
+ <x:ref>CONTINUATION</x:ref> frames that carry any remaining portions of the header block.
+ </t>
+ <t>
+ Other frames (from any stream) MUST NOT occur between either <x:ref>HEADERS</x:ref> frame
+ and any <x:ref>CONTINUATION</x:ref> frames that might follow.
+ </t>
+
+ <t>
+ Trailing header fields are carried in a header block that also terminates the stream.
+ That is, a sequence starting with a <x:ref>HEADERS</x:ref> frame, followed by zero or more
+ <x:ref>CONTINUATION</x:ref> frames, where the <x:ref>HEADERS</x:ref> frame bears an
+ END_STREAM flag. Header blocks after the first that do not terminate the stream are not
+ part of an HTTP request or response.
+ </t>
+ <t>
+ A <x:ref>HEADERS</x:ref> frame (and associated <x:ref>CONTINUATION</x:ref> frames) can
+ only appear at the start or end of a stream. An endpoint that receives a
+ <x:ref>HEADERS</x:ref> frame without the END_STREAM flag set after receiving a final
+ (non-informational) status code MUST treat the corresponding request or response as <xref
+ target="malformed">malformed</xref>.
+ </t>
+
+ <t>
+ An HTTP request/response exchange fully consumes a single stream. A request starts with
+ the <x:ref>HEADERS</x:ref> frame that puts the stream into an "open" state. The request
+ ends with a frame bearing END_STREAM, which causes the stream to become "half closed
+ (local)" for the client and "half closed (remote)" for the server. A response starts with
+ a <x:ref>HEADERS</x:ref> frame and ends with a frame bearing END_STREAM, which places the
+ stream in the "closed" state.
+ <!-- Yes, the response might be completed before the request does, but that's not a detail
+ we need to expand upon. It's complicated enough explaining this as it is. -->
+ </t>
+
+ <section anchor="informational-responses" title="Upgrading From HTTP/2">
+ <t>
+ HTTP/2 removes support for the 101 (Switching Protocols) informational status code
+ (<xref target="RFC7231" x:fmt="," x:rel="#status.101"/>).
+ </t>
+ <t>
+ The semantics of 101 (Switching Protocols) aren't applicable to a multiplexed protocol.
+ Alternative protocols are able to use the same mechanisms that HTTP/2 uses to negotiate
+ their use (see <xref target="starting"/>).
+ </t>
+ </section>
+
+ <section anchor="HttpHeaders" title="HTTP Header Fields">
+ <t>
+ HTTP header fields carry information as a series of key-value pairs. For a listing of
+ registered HTTP headers, see the Message Header Field Registry maintained at <eref
+ target="https://www.iana.org/assignments/message-headers"/>.
+ </t>
+
+ <section anchor="PseudoHeaderFields" title="Pseudo-Header Fields">
+ <t>
+ While HTTP/1.x used the message start-line (see <xref target="RFC7230" x:fmt=","
+ x:rel="#start.line"/>) to convey the target URI and method of the request, and the
+ status code for the response, HTTP/2 uses special pseudo-header fields beginning with
+ ':' character (ASCII 0x3a) for this purpose.
+ </t>
+ <t>
+ Pseudo-header fields are not HTTP header fields. Endpoints MUST NOT generate
+ pseudo-header fields other than those defined in this document.
+ </t>
+ <t>
+ Pseudo-header fields are only valid in the context in which they are defined.
+ Pseudo-header fields defined for requests MUST NOT appear in responses; pseudo-header
+ fields defined for responses MUST NOT appear in requests. Pseudo-header fields MUST
+ NOT appear in trailers. Endpoints MUST treat a request or response that contains
+ undefined or invalid pseudo-header fields as <xref
+ target="malformed">malformed</xref>.
+ </t>
+ <t>
+ Just as in HTTP/1.x, header field names are strings of ASCII characters that are
+ compared in a case-insensitive fashion. However, header field names MUST be converted
+ to lowercase prior to their encoding in HTTP/2. A request or response containing
+ uppercase header field names MUST be treated as <xref
+ target="malformed">malformed</xref>.
+ </t>
+ <t>
+ All pseudo-header fields MUST appear in the header block before regular header fields.
+ Any request or response that contains a pseudo-header field that appears in a header
+ block after a regular header field MUST be treated as <xref
+ target="malformed">malformed</xref>.
+ </t>
+ </section>
+
+ <section title="Connection-Specific Header Fields">
+ <t>
+ HTTP/2 does not use the <spanx style="verb">Connection</spanx> header field to
+ indicate connection-specific header fields; in this protocol, connection-specific
+ metadata is conveyed by other means. An endpoint MUST NOT generate a HTTP/2 message
+ containing connection-specific header fields; any message containing
+ connection-specific header fields MUST be treated as <xref
+ target="malformed">malformed</xref>.
+ </t>
+ <t>
+ This means that an intermediary transforming an HTTP/1.x message to HTTP/2 will need
+ to remove any header fields nominated by the Connection header field, along with the
+ Connection header field itself. Such intermediaries SHOULD also remove other
+ connection-specific header fields, such as Keep-Alive, Proxy-Connection,
+ Transfer-Encoding and Upgrade, even if they are not nominated by Connection.
+ </t>
+ <t>
+ One exception to this is the TE header field, which MAY be present in an HTTP/2
+ request, but when it is MUST NOT contain any value other than "trailers".
+ </t>
+ <t>
+ <list style="hanging">
+ <t hangText="Note:">
+ HTTP/2 purposefully does not support upgrade to another protocol. The handshake
+ methods described in <xref target="starting"/> are believed sufficient to
+ negotiate the use of alternative protocols.
+ </t>
+ </list>
+ </t>
+ </section>
+
+ <section anchor="HttpRequest" title="Request Pseudo-Header Fields">
+ <t>
+ The following pseudo-header fields are defined for HTTP/2 requests:
+ <list style="symbols">
+ <x:lt>
+ <t>
+ The <spanx style="verb">:method</spanx> pseudo-header field includes the HTTP
+ method (<xref target="RFC7231" x:fmt="," x:rel="#methods"/>).
+ </t>
+ </x:lt>
+ <x:lt>
+ <t>
+ The <spanx style="verb">:scheme</spanx> pseudo-header field includes the scheme
+ portion of the target URI (<xref target="RFC3986" x:fmt="," x:sec="3.1"/>).
+ </t>
+ <t>
+ <spanx style="verb">:scheme</spanx> is not restricted to <spanx
+ style="verb">http</spanx> and <spanx style="verb">https</spanx> schemed URIs. A
+ proxy or gateway can translate requests for non-HTTP schemes, enabling the use
+ of HTTP to interact with non-HTTP services.
+ </t>
+ </x:lt>
+ <x:lt>
+ <t>
+ The <spanx style="verb">:authority</spanx> pseudo-header field includes the
+ authority portion of the target URI (<xref target="RFC3986" x:fmt=","
+ x:sec="3.2"/>). The authority MUST NOT include the deprecated <spanx
+ style="verb">userinfo</spanx> subcomponent for <spanx style="verb">http</spanx>
+ or <spanx style="verb">https</spanx> schemed URIs.
+ </t>
+ <t>
+ To ensure that the HTTP/1.1 request line can be reproduced accurately, this
+ pseudo-header field MUST be omitted when translating from an HTTP/1.1 request
+ that has a request target in origin or asterisk form (see <xref
+ target="RFC7230" x:fmt="," x:rel="#request-target"/>). Clients that generate
+ HTTP/2 requests directly SHOULD use the <spanx>:authority</spanx> pseudo-header
+ field instead of the <spanx style="verb">Host</spanx> header field. An
+ intermediary that converts an HTTP/2 request to HTTP/1.1 MUST create a <spanx
+ style="verb">Host</spanx> header field if one is not present in a request by
+ copying the value of the <spanx style="verb">:authority</spanx> pseudo-header
+ field.
+ </t>
+ </x:lt>
+ <x:lt>
+ <t>
+ The <spanx style="verb">:path</spanx> pseudo-header field includes the path and
+ query parts of the target URI (the <spanx style="verb">path-absolute</spanx>
+ production from <xref target="RFC3986"/> and optionally a '?' character
+ followed by the <spanx style="verb">query</spanx> production, see <xref
+ target="RFC3986" x:fmt="," x:sec="3.3"/> and <xref target="RFC3986" x:fmt=","
+ x:sec="3.4"/>). A request in asterisk form includes the value '*' for the
+ <spanx style="verb">:path</spanx> pseudo-header field.
+ </t>
+ <t>
+ This pseudo-header field MUST NOT be empty for <spanx style="verb">http</spanx>
+ or <spanx style="verb">https</spanx> URIs; <spanx style="verb">http</spanx> or
+ <spanx style="verb">https</spanx> URIs that do not contain a path component
+ MUST include a value of '/'. The exception to this rule is an OPTIONS request
+ for an <spanx style="verb">http</spanx> or <spanx style="verb">https</spanx>
+ URI that does not include a path component; these MUST include a <spanx
+ style="verb">:path</spanx> pseudo-header field with a value of '*' (see <xref
+ target="RFC7230" x:fmt="," x:rel="#asterisk-form"/>).
+ </t>
+ </x:lt>
+ </list>
+ </t>
+ <t>
+ All HTTP/2 requests MUST include exactly one valid value for the <spanx
+ style="verb">:method</spanx>, <spanx style="verb">:scheme</spanx>, and <spanx
+ style="verb">:path</spanx> pseudo-header fields, unless it is a <xref
+ target="CONNECT">CONNECT request</xref>. An HTTP request that omits mandatory
+ pseudo-header fields is <xref target="malformed">malformed</xref>.
+ </t>
+ <t>
+ HTTP/2 does not define a way to carry the version identifier that is included in the
+ HTTP/1.1 request line.
+ </t>
+ </section>
+
+ <section anchor="HttpResponse" title="Response Pseudo-Header Fields">
+ <t>
+ For HTTP/2 responses, a single <spanx style="verb">:status</spanx> pseudo-header
+ field is defined that carries the HTTP status code field (see <xref target="RFC7231"
+ x:fmt="," x:rel="#status.codes"/>). This pseudo-header field MUST be included in all
+ responses, otherwise the response is <xref target="malformed">malformed</xref>.
+ </t>
+ <t>
+ HTTP/2 does not define a way to carry the version or reason phrase that is included in
+ an HTTP/1.1 status line.
+ </t>
+ </section>
+
+ <section anchor="CompressCookie" title="Compressing the Cookie Header Field">
+ <t>
+ The <xref target="COOKIE">Cookie header field</xref> can carry a significant amount of
+ redundant data.
+ </t>
+ <t>
+ The Cookie header field uses a semi-colon (";") to delimit cookie-pairs (or "crumbs").
+ This header field doesn't follow the list construction rules in HTTP (see <xref
+ target="RFC7230" x:fmt="," x:rel="#field.order"/>), which prevents cookie-pairs from
+ being separated into different name-value pairs. This can significantly reduce
+ compression efficiency as individual cookie-pairs are updated.
+ </t>
+ <t>
+ To allow for better compression efficiency, the Cookie header field MAY be split into
+ separate header fields, each with one or more cookie-pairs. If there are multiple
+ Cookie header fields after decompression, these MUST be concatenated into a single
+ octet string using the two octet delimiter of 0x3B, 0x20 (the ASCII string "; ")
+ before being passed into a non-HTTP/2 context, such as an HTTP/1.1 connection, or a
+ generic HTTP server application.
+ </t>
+ <figure>
+ <preamble>
+ Therefore, the following two lists of Cookie header fields are semantically
+ equivalent.
+ </preamble>
+ <artwork type="inline"><![CDATA[
+ cookie: a=b; c=d; e=f
+
+ cookie: a=b
+ cookie: c=d
+ cookie: e=f
+]]></artwork>
+ </figure>
+ </section>
+
+ <section anchor="malformed" title="Malformed Requests and Responses">
+ <t>
+ A malformed request or response is one that is an otherwise valid sequence of HTTP/2
+ frames, but is otherwise invalid due to the presence of extraneous frames, prohibited
+ header fields, the absence of mandatory header fields, or the inclusion of uppercase
+ header field names.
+ </t>
+ <t>
+ A request or response that includes an entity body can include a <spanx
+ style="verb">content-length</spanx> header field. A request or response is also
+ malformed if the value of a <spanx style="verb">content-length</spanx> header field
+ does not equal the sum of the <x:ref>DATA</x:ref> frame payload lengths that form the
+ body. A response that is defined to have no payload, as described in <xref
+ target="RFC7230" x:fmt="," x:rel="#header.content-length"/>, can have a non-zero
+ <spanx style="verb">content-length</spanx> header field, even though no content is
+ included in <x:ref>DATA</x:ref> frames.
+ </t>
+ <t>
+ Intermediaries that process HTTP requests or responses (i.e., any intermediary not
+ acting as a tunnel) MUST NOT forward a malformed request or response. Malformed
+ requests or responses that are detected MUST be treated as a <xref
+ target="StreamErrorHandler">stream error</xref> of type <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ For malformed requests, a server MAY send an HTTP response prior to closing or
+ resetting the stream. Clients MUST NOT accept a malformed response. Note that these
+ requirements are intended to protect against several types of common attacks against
+ HTTP; they are deliberately strict, because being permissive can expose
+ implementations to these vulnerabilities.
+ </t>
+ </section>
+ </section>
+
+ <section title="Examples">
+ <t>
+ This section shows HTTP/1.1 requests and responses, with illustrations of equivalent
+ HTTP/2 requests and responses.
+ </t>
+ <t>
+ An HTTP GET request includes request header fields and no body and is therefore
+ transmitted as a single <x:ref>HEADERS</x:ref> frame, followed by zero or more
+ <x:ref>CONTINUATION</x:ref> frames containing the serialized block of request header
+ fields. The <x:ref>HEADERS</x:ref> frame in the following has both the END_HEADERS and
+ END_STREAM flags set; no <x:ref>CONTINUATION</x:ref> frames are sent:
+ </t>
+
+ <figure>
+ <artwork type="inline"><![CDATA[
+ GET /resource HTTP/1.1 HEADERS
+ Host: example.org ==> + END_STREAM
+ Accept: image/jpeg + END_HEADERS
+ :method = GET
+ :scheme = https
+ :path = /resource
+ host = example.org
+ accept = image/jpeg
+]]></artwork>
+ </figure>
+
+ <t>
+ Similarly, a response that includes only response header fields is transmitted as a
+ <x:ref>HEADERS</x:ref> frame (again, followed by zero or more
+ <x:ref>CONTINUATION</x:ref> frames) containing the serialized block of response header
+ fields.
+ </t>
+
+ <figure>
+ <artwork type="inline"><![CDATA[
+ HTTP/1.1 304 Not Modified HEADERS
+ ETag: "xyzzy" ==> + END_STREAM
+ Expires: Thu, 23 Jan ... + END_HEADERS
+ :status = 304
+ etag = "xyzzy"
+ expires = Thu, 23 Jan ...
+]]></artwork>
+ </figure>
+
+ <t>
+ An HTTP POST request that includes request header fields and payload data is transmitted
+ as one <x:ref>HEADERS</x:ref> frame, followed by zero or more
+ <x:ref>CONTINUATION</x:ref> frames containing the request header fields, followed by one
+ or more <x:ref>DATA</x:ref> frames, with the last <x:ref>CONTINUATION</x:ref> (or
+ <x:ref>HEADERS</x:ref>) frame having the END_HEADERS flag set and the final
+ <x:ref>DATA</x:ref> frame having the END_STREAM flag set:
+ </t>
+
+ <figure>
+ <artwork type="inline"><![CDATA[
+ POST /resource HTTP/1.1 HEADERS
+ Host: example.org ==> - END_STREAM
+ Content-Type: image/jpeg - END_HEADERS
+ Content-Length: 123 :method = POST
+ :path = /resource
+ {binary data} :scheme = https
+
+ CONTINUATION
+ + END_HEADERS
+ content-type = image/jpeg
+ host = example.org
+ content-length = 123
+
+ DATA
+ + END_STREAM
+ {binary data}
+]]></artwork>
+ <postamble>
+ Note that data contributing to any given header field could be spread between header
+ block fragments. The allocation of header fields to frames in this example is
+ illustrative only.
+ </postamble>
+ </figure>
+
+ <t>
+ A response that includes header fields and payload data is transmitted as a
+ <x:ref>HEADERS</x:ref> frame, followed by zero or more <x:ref>CONTINUATION</x:ref>
+ frames, followed by one or more <x:ref>DATA</x:ref> frames, with the last
+ <x:ref>DATA</x:ref> frame in the sequence having the END_STREAM flag set:
+ </t>
+
+ <figure>
+ <artwork type="inline"><![CDATA[
+ HTTP/1.1 200 OK HEADERS
+ Content-Type: image/jpeg ==> - END_STREAM
+ Content-Length: 123 + END_HEADERS
+ :status = 200
+ {binary data} content-type = image/jpeg
+ content-length = 123
+
+ DATA
+ + END_STREAM
+ {binary data}
+]]></artwork>
+ </figure>
+
+ <t>
+ Trailing header fields are sent as a header block after both the request or response
+ header block and all the <x:ref>DATA</x:ref> frames have been sent. The
+ <x:ref>HEADERS</x:ref> frame starting the trailers header block has the END_STREAM flag
+ set.
+ </t>
+
+ <figure>
+ <artwork type="inline"><![CDATA[
+ HTTP/1.1 200 OK HEADERS
+ Content-Type: image/jpeg ==> - END_STREAM
+ Transfer-Encoding: chunked + END_HEADERS
+ Trailer: Foo :status = 200
+ content-length = 123
+ 123 content-type = image/jpeg
+ {binary data} trailer = Foo
+ 0
+ Foo: bar DATA
+ - END_STREAM
+ {binary data}
+
+ HEADERS
+ + END_STREAM
+ + END_HEADERS
+ foo = bar
+]]></artwork>
+ </figure>
+
+
+ <figure>
+ <preamble>
+ An informational response using a 1xx status code other than 101 is transmitted as a
+ <x:ref>HEADERS</x:ref> frame, followed by zero or more <x:ref>CONTINUATION</x:ref>
+ frames:
+ </preamble>
+ <artwork type="inline"><![CDATA[
+ HTTP/1.1 103 BAR HEADERS
+ Extension-Field: bar ==> - END_STREAM
+ + END_HEADERS
+ :status = 103
+ extension-field = bar
+]]></artwork>
+ </figure>
+ </section>
+
+ <section anchor="Reliability" title="Request Reliability Mechanisms in HTTP/2">
+ <t>
+ In HTTP/1.1, an HTTP client is unable to retry a non-idempotent request when an error
+ occurs, because there is no means to determine the nature of the error. It is possible
+ that some server processing occurred prior to the error, which could result in
+ undesirable effects if the request were reattempted.
+ </t>
+ <t>
+ HTTP/2 provides two mechanisms for providing a guarantee to a client that a request has
+ not been processed:
+ <list style="symbols">
+ <t>
+ The <x:ref>GOAWAY</x:ref> frame indicates the highest stream number that might have
+ been processed. Requests on streams with higher numbers are therefore guaranteed to
+ be safe to retry.
+ </t>
+ <t>
+ The <x:ref>REFUSED_STREAM</x:ref> error code can be included in a
+ <x:ref>RST_STREAM</x:ref> frame to indicate that the stream is being closed prior to
+ any processing having occurred. Any request that was sent on the reset stream can
+ be safely retried.
+ </t>
+ </list>
+ </t>
+ <t>
+ Requests that have not been processed have not failed; clients MAY automatically retry
+ them, even those with non-idempotent methods.
+ </t>
+ <t>
+ A server MUST NOT indicate that a stream has not been processed unless it can guarantee
+ that fact. If frames that are on a stream are passed to the application layer for any
+ stream, then <x:ref>REFUSED_STREAM</x:ref> MUST NOT be used for that stream, and a
+ <x:ref>GOAWAY</x:ref> frame MUST include a stream identifier that is greater than or
+ equal to the given stream identifier.
+ </t>
+ <t>
+ In addition to these mechanisms, the <x:ref>PING</x:ref> frame provides a way for a
+ client to easily test a connection. Connections that remain idle can become broken as
+ some middleboxes (for instance, network address translators, or load balancers) silently
+ discard connection bindings. The <x:ref>PING</x:ref> frame allows a client to safely
+ test whether a connection is still active without sending a request.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="PushResources" title="Server Push">
+ <t>
+ HTTP/2 allows a server to pre-emptively send (or "push") responses (along with
+ corresponding "promised" requests) to a client in association with a previous
+ client-initiated request. This can be useful when the server knows the client will need
+ to have those responses available in order to fully process the response to the original
+ request.
+ </t>
+
+ <t>
+ Pushing additional message exchanges in this fashion is optional, and is negotiated
+ between individual endpoints. The <x:ref>SETTINGS_ENABLE_PUSH</x:ref> setting can be set
+ to 0 to indicate that server push is disabled.
+ </t>
+ <t>
+ Promised requests MUST be cacheable (see <xref target="RFC7231" x:fmt=","
+ x:rel="#cacheable.methods"/>), MUST be safe (see <xref target="RFC7231" x:fmt=","
+ x:rel="#safe.methods"/>) and MUST NOT include a request body. Clients that receive a
+ promised request that is not cacheable, unsafe or that includes a request body MUST
+ reset the stream with a <xref target="StreamErrorHandler">stream error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+ <t>
+ Pushed responses that are cacheable (see <xref target="RFC7234" x:fmt=","
+ x:rel="#response.cacheability"/>) can be stored by the client, if it implements a HTTP
+ cache. Pushed responses are considered successfully validated on the origin server (e.g.,
+ if the "no-cache" cache response directive <xref target="RFC7234" x:fmt=","
+ x:rel="#cache-response-directive"/> is present) while the stream identified by the
+ promised stream ID is still open.
+ </t>
+ <t>
+ Pushed responses that are not cacheable MUST NOT be stored by any HTTP cache. They MAY
+ be made available to the application separately.
+ </t>
+ <t>
+ An intermediary can receive pushes from the server and choose not to forward them on to
+ the client. In other words, how to make use of the pushed information is up to that
+ intermediary. Equally, the intermediary might choose to make additional pushes to the
+ client, without any action taken by the server.
+ </t>
+ <t>
+ A client cannot push. Thus, servers MUST treat the receipt of a
+ <x:ref>PUSH_PROMISE</x:ref> frame as a <xref target="ConnectionErrorHandler">connection
+ error</xref> of type <x:ref>PROTOCOL_ERROR</x:ref>. Clients MUST reject any attempt to
+ change the <x:ref>SETTINGS_ENABLE_PUSH</x:ref> setting to a value other than 0 by treating
+ the message as a <xref target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+
+ <section anchor="PushRequests" title="Push Requests">
+ <t>
+ Server push is semantically equivalent to a server responding to a request; however, in
+ this case that request is also sent by the server, as a <x:ref>PUSH_PROMISE</x:ref>
+ frame.
+ </t>
+ <t>
+ The <x:ref>PUSH_PROMISE</x:ref> frame includes a header block that contains a complete
+ set of request header fields that the server attributes to the request. It is not
+ possible to push a response to a request that includes a request body.
+ </t>
+
+ <t>
+ Pushed responses are always associated with an explicit request from the client. The
+ <x:ref>PUSH_PROMISE</x:ref> frames sent by the server are sent on that explicit
+ request's stream. The <x:ref>PUSH_PROMISE</x:ref> frame also includes a promised stream
+ identifier, chosen from the stream identifiers available to the server (see <xref
+ target="StreamIdentifiers"/>).
+ </t>
+
+ <t>
+ The header fields in <x:ref>PUSH_PROMISE</x:ref> and any subsequent
+ <x:ref>CONTINUATION</x:ref> frames MUST be a valid and complete set of <xref
+ target="HttpRequest">request header fields</xref>. The server MUST include a method in
+ the <spanx style="verb">:method</spanx> header field that is safe and cacheable. If a
+ client receives a <x:ref>PUSH_PROMISE</x:ref> that does not include a complete and valid
+ set of header fields, or the <spanx style="verb">:method</spanx> header field identifies
+ a method that is not safe, it MUST respond with a <xref
+ target="StreamErrorHandler">stream error</xref> of type <x:ref>PROTOCOL_ERROR</x:ref>.
+ </t>
+
+ <t>
+ The server SHOULD send <x:ref>PUSH_PROMISE</x:ref> (<xref target="PUSH_PROMISE"/>)
+ frames prior to sending any frames that reference the promised responses. This avoids a
+ race where clients issue requests prior to receiving any <x:ref>PUSH_PROMISE</x:ref>
+ frames.
+ </t>
+ <t>
+ For example, if the server receives a request for a document containing embedded links
+ to multiple image files, and the server chooses to push those additional images to the
+ client, sending push promises before the <x:ref>DATA</x:ref> frames that contain the
+ image links ensures that the client is able to see the promises before discovering
+ embedded links. Similarly, if the server pushes responses referenced by the header block
+ (for instance, in Link header fields), sending the push promises before sending the
+ header block ensures that clients do not request them.
+ </t>
+
+ <t>
+ <x:ref>PUSH_PROMISE</x:ref> frames MUST NOT be sent by the client.
+ </t>
+ <t>
+ <x:ref>PUSH_PROMISE</x:ref> frames can be sent by the server in response to any
+ client-initiated stream, but the stream MUST be in either the "open" or "half closed
+ (remote)" state with respect to the server. <x:ref>PUSH_PROMISE</x:ref> frames are
+ interspersed with the frames that comprise a response, though they cannot be
+ interspersed with <x:ref>HEADERS</x:ref> and <x:ref>CONTINUATION</x:ref> frames that
+ comprise a single header block.
+ </t>
+ <t>
+ Sending a <x:ref>PUSH_PROMISE</x:ref> frame creates a new stream and puts the stream
+ into the “reserved (local)” state for the server and the “reserved (remote)” state for
+ the client.
+ </t>
+ </section>
+
+ <section anchor="PushResponses" title="Push Responses">
+ <t>
+ After sending the <x:ref>PUSH_PROMISE</x:ref> frame, the server can begin delivering the
+ pushed response as a <xref target="HttpResponse">response</xref> on a server-initiated
+ stream that uses the promised stream identifier. The server uses this stream to
+ transmit an HTTP response, using the same sequence of frames as defined in <xref
+ target="HttpSequence"/>. This stream becomes <xref target="StreamStates">"half closed"
+ to the client</xref> after the initial <x:ref>HEADERS</x:ref> frame is sent.
+ </t>
+
+ <t>
+ Once a client receives a <x:ref>PUSH_PROMISE</x:ref> frame and chooses to accept the
+ pushed response, the client SHOULD NOT issue any requests for the promised response
+ until after the promised stream has closed.
+ </t>
+
+ <t>
+ If the client determines, for any reason, that it does not wish to receive the pushed
+ response from the server, or if the server takes too long to begin sending the promised
+ response, the client can send an <x:ref>RST_STREAM</x:ref> frame, using either the
+ <x:ref>CANCEL</x:ref> or <x:ref>REFUSED_STREAM</x:ref> codes, and referencing the pushed
+ stream's identifier.
+ </t>
+ <t>
+ A client can use the <x:ref>SETTINGS_MAX_CONCURRENT_STREAMS</x:ref> setting to limit the
+ number of responses that can be concurrently pushed by a server. Advertising a
+ <x:ref>SETTINGS_MAX_CONCURRENT_STREAMS</x:ref> value of zero disables server push by
+ preventing the server from creating the necessary streams. This does not prohibit a
+ server from sending <x:ref>PUSH_PROMISE</x:ref> frames; clients need to reset any
+ promised streams that are not wanted.
+ </t>
+
+ <t>
+ Clients receiving a pushed response MUST validate that either the server is
+ authoritative (see <xref target="authority"/>), or the proxy that provided the pushed
+ response is configured for the corresponding request. For example, a server that offers
+ a certificate for only the <spanx style="verb">example.com</spanx> DNS-ID or Common Name
+ is not permitted to push a response for <spanx
+ style="verb">https://www.example.org/doc</spanx>.
+ </t>
+ <t>
+ The response for a <x:ref>PUSH_PROMISE</x:ref> stream begins with a
+ <x:ref>HEADERS</x:ref> frame, which immediately puts the stream into the “half closed
+ (remote)” state for the server and “half closed (local)” state for the client, and ends
+ with a frame bearing END_STREAM, which places the stream in the "closed" state.
+ <list style="hanging">
+ <t hangText="Note:">
+ The client never sends a frame with the END_STREAM flag for a server push.
+ </t>
+ </list>
+ </t>
+ </section>
+
+ </section>
+
+ <section anchor="CONNECT" title="The CONNECT Method">
+ <t>
+ In HTTP/1.x, the pseudo-method CONNECT (<xref target="RFC7231" x:fmt=","
+ x:rel="#CONNECT"/>) is used to convert an HTTP connection into a tunnel to a remote host.
+ CONNECT is primarily used with HTTP proxies to establish a TLS session with an origin
+ server for the purposes of interacting with <spanx style="verb">https</spanx> resources.
+ </t>
+ <t>
+ In HTTP/2, the CONNECT method is used to establish a tunnel over a single HTTP/2 stream to
+ a remote host, for similar purposes. The HTTP header field mapping works as defined in
+ <xref target="HttpRequest">Request Header Fields</xref>, with a few
+ differences. Specifically:
+ <list style="symbols">
+ <t>
+ The <spanx style="verb">:method</spanx> header field is set to <spanx
+ style="verb">CONNECT</spanx>.
+ </t>
+ <t>
+ The <spanx style="verb">:scheme</spanx> and <spanx style="verb">:path</spanx> header
+ fields MUST be omitted.
+ </t>
+ <t>
+ The <spanx style="verb">:authority</spanx> header field contains the host and port to
+ connect to (equivalent to the authority-form of the request-target of CONNECT
+ requests, see <xref target="RFC7230" x:fmt="," x:rel="#request-target"/>).
+ </t>
+ </list>
+ </t>
+ <t>
+ A proxy that supports CONNECT establishes a <xref target="TCP">TCP connection</xref> to
+ the server identified in the <spanx style="verb">:authority</spanx> header field. Once
+ this connection is successfully established, the proxy sends a <x:ref>HEADERS</x:ref>
+ frame containing a 2xx series status code to the client, as defined in <xref
+ target="RFC7231" x:fmt="," x:rel="#CONNECT"/>.
+ </t>
+ <t>
+ After the initial <x:ref>HEADERS</x:ref> frame sent by each peer, all subsequent
+ <x:ref>DATA</x:ref> frames correspond to data sent on the TCP connection. The payload of
+ any <x:ref>DATA</x:ref> frames sent by the client is transmitted by the proxy to the TCP
+ server; data received from the TCP server is assembled into <x:ref>DATA</x:ref> frames by
+ the proxy. Frame types other than <x:ref>DATA</x:ref> or stream management frames
+ (<x:ref>RST_STREAM</x:ref>, <x:ref>WINDOW_UPDATE</x:ref>, and <x:ref>PRIORITY</x:ref>)
+ MUST NOT be sent on a connected stream, and MUST be treated as a <xref
+ target="StreamErrorHandler">stream error</xref> if received.
+ </t>
+ <t>
+ The TCP connection can be closed by either peer. The END_STREAM flag on a
+ <x:ref>DATA</x:ref> frame is treated as being equivalent to the TCP FIN bit. A client is
+ expected to send a <x:ref>DATA</x:ref> frame with the END_STREAM flag set after receiving
+ a frame bearing the END_STREAM flag. A proxy that receives a <x:ref>DATA</x:ref> frame
+ with the END_STREAM flag set sends the attached data with the FIN bit set on the last TCP
+ segment. A proxy that receives a TCP segment with the FIN bit set sends a
+ <x:ref>DATA</x:ref> frame with the END_STREAM flag set. Note that the final TCP segment
+ or <x:ref>DATA</x:ref> frame could be empty.
+ </t>
+ <t>
+ A TCP connection error is signaled with <x:ref>RST_STREAM</x:ref>. A proxy treats any
+ error in the TCP connection, which includes receiving a TCP segment with the RST bit set,
+ as a <xref target="StreamErrorHandler">stream error</xref> of type
+ <x:ref>CONNECT_ERROR</x:ref>. Correspondingly, a proxy MUST send a TCP segment with the
+ RST bit set if it detects an error with the stream or the HTTP/2 connection.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="HttpExtra" title="Additional HTTP Requirements/Considerations">
+ <t>
+ This section outlines attributes of the HTTP protocol that improve interoperability, reduce
+ exposure to known security vulnerabilities, or reduce the potential for implementation
+ variation.
+ </t>
+
+ <section title="Connection Management">
+ <t>
+ HTTP/2 connections are persistent. For best performance, it is expected clients will not
+ close connections until it is determined that no further communication with a server is
+ necessary (for example, when a user navigates away from a particular web page), or until
+ the server closes the connection.
+ </t>
+ <t>
+ Clients SHOULD NOT open more than one HTTP/2 connection to a given host and port pair,
+ where host is derived from a URI, a selected <xref target="ALT-SVC">alternative
+ service</xref>, or a configured proxy.
+ </t>
+ <t>
+ A client can create additional connections as replacements, either to replace connections
+ that are near to exhausting the available <xref target="StreamIdentifiers">stream
+ identifier space</xref>, to refresh the keying material for a TLS connection, or to
+ replace connections that have encountered <xref
+ target="ConnectionErrorHandler">errors</xref>.
+ </t>
+ <t>
+ A client MAY open multiple connections to the same IP address and TCP port using different
+ <xref target="TLS-EXT">Server Name Indication</xref> values or to provide different TLS
+ client certificates, but SHOULD avoid creating multiple connections with the same
+ configuration.
+ </t>
+ <t>
+ Servers are encouraged to maintain open connections for as long as possible, but are
+ permitted to terminate idle connections if necessary. When either endpoint chooses to
+ close the transport-layer TCP connection, the terminating endpoint SHOULD first send a
+ <x:ref>GOAWAY</x:ref> (<xref target="GOAWAY"/>) frame so that both endpoints can reliably
+ determine whether previously sent frames have been processed and gracefully complete or
+ terminate any necessary remaining tasks.
+ </t>
+
+ <section anchor="reuse" title="Connection Reuse">
+ <t>
+ Connections that are made to an origin servers, either directly or through a tunnel
+ created using the <xref target="CONNECT">CONNECT method</xref> MAY be reused for
+ requests with multiple different URI authority components. A connection can be reused
+ as long as the origin server is <xref target="authority">authoritative</xref>. For
+ <spanx style="verb">http</spanx> resources, this depends on the host having resolved to
+ the same IP address.
+ </t>
+ <t>
+ For <spanx style="verb">https</spanx> resources, connection reuse additionally depends
+ on having a certificate that is valid for the host in the URI. An origin server might
+ offer a certificate with multiple <spanx style="verb">subjectAltName</spanx> attributes,
+ or names with wildcards, one of which is valid for the authority in the URI. For
+ example, a certificate with a <spanx style="verb">subjectAltName</spanx> of <spanx
+ style="verb">*.example.com</spanx> might permit the use of the same connection for
+ requests to URIs starting with <spanx style="verb">https://a.example.com/</spanx> and
+ <spanx style="verb">https://b.example.com/</spanx>.
+ </t>
+ <t>
+ In some deployments, reusing a connection for multiple origins can result in requests
+ being directed to the wrong origin server. For example, TLS termination might be
+ performed by a middlebox that uses the TLS <xref target="TLS-EXT">Server Name Indication
+ (SNI)</xref> extension to select an origin server. This means that it is possible
+ for clients to send confidential information to servers that might not be the intended
+ target for the request, even though the server is otherwise authoritative.
+ </t>
+ <t>
+ A server that does not wish clients to reuse connections can indicate that it is not
+ authoritative for a request by sending a 421 (Misdirected Request) status code in response
+ to the request (see <xref target="MisdirectedRequest"/>).
+ </t>
+ <t>
+ A client that is configured to use a proxy over HTTP/2 directs requests to that proxy
+ through a single connection. That is, all requests sent via a proxy reuse the
+ connection to the proxy.
+ </t>
+ </section>
+
+ <section anchor="MisdirectedRequest" title="The 421 (Misdirected Request) Status Code">
+ <t>
+ The 421 (Misdirected Request) status code indicates that the request was directed at a
+ server that is not able to produce a response. This can be sent by a server that is not
+ configured to produce responses for the combination of scheme and authority that are
+ included in the request URI.
+ </t>
+ <t>
+ Clients receiving a 421 (Misdirected Request) response from a server MAY retry the
+ request - whether the request method is idempotent or not - over a different connection.
+ This is possible if a connection is reused (<xref target="reuse"/>) or if an alternative
+ service is selected (<xref target="ALT-SVC"/>).
+ </t>
+ <t>
+ This status code MUST NOT be generated by proxies.
+ </t>
+ <t>
+ A 421 response is cacheable by default; i.e., unless otherwise indicated by the method
+ definition or explicit cache controls (see <xref target="RFC7234"
+ x:rel="#heuristic.freshness" x:fmt="of"/>).
+ </t>
+ </section>
+ </section>
+
+ <section title="Use of TLS Features" anchor="TLSUsage">
+ <t>
+ Implementations of HTTP/2 MUST support <xref target="TLS12">TLS 1.2</xref> for HTTP/2 over
+ TLS. The general TLS usage guidance in <xref target="TLSBCP"/> SHOULD be followed, with
+ some additional restrictions that are specific to HTTP/2.
+ </t>
+
+ <t>
+ An implementation of HTTP/2 over TLS MUST use TLS 1.2 or higher with the restrictions on
+ feature set and cipher suite described in this section. Due to implementation
+ limitations, it might not be possible to fail TLS negotiation. An endpoint MUST
+ immediately terminate an HTTP/2 connection that does not meet these minimum requirements
+ with a <xref target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>INADEQUATE_SECURITY</x:ref>.
+ </t>
+
+ <section anchor="TLSFeatures" title="TLS Features">
+ <t>
+ The TLS implementation MUST support the <xref target="TLS-EXT">Server Name Indication
+ (SNI)</xref> extension to TLS. HTTP/2 clients MUST indicate the target domain name when
+ negotiating TLS.
+ </t>
+ <t>
+ The TLS implementation MUST disable compression. TLS compression can lead to the
+ exposure of information that would not otherwise be revealed <xref target="RFC3749"/>.
+ Generic compression is unnecessary since HTTP/2 provides compression features that are
+ more aware of context and therefore likely to be more appropriate for use for
+ performance, security or other reasons.
+ </t>
+ <t>
+ The TLS implementation MUST disable renegotiation. An endpoint MUST treat a TLS
+ renegotiation as a <xref target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>PROTOCOL_ERROR</x:ref>. Note that disabling renegotiation can result in
+ long-lived connections becoming unusable due to limits on the number of messages the
+ underlying cipher suite can encipher.
+ </t>
+ <t>
+ A client MAY use renegotiation to provide confidentiality protection for client
+ credentials offered in the handshake, but any renegotiation MUST occur prior to sending
+ the connection preface. A server SHOULD request a client certificate if it sees a
+ renegotiation request immediately after establishing a connection.
+ </t>
+ <t>
+ This effectively prevents the use of renegotiation in response to a request for a
+ specific protected resource. A future specification might provide a way to support this
+ use case. <!-- <cref> We are tracking this in a non-blocking fashion in issue #496 and
+ with a new draft. -->
+ </t>
+ </section>
+
+ <section title="TLS Cipher Suites">
+ <t>
+ The set of TLS cipher suites that are permitted in HTTP/2 is restricted. HTTP/2 MUST
+ only be used with cipher suites that have ephemeral key exchange, such as the <xref
+ target="TLS12">ephemeral Diffie-Hellman (DHE)</xref> or the <xref
+ target="RFC4492">elliptic curve variant (ECDHE)</xref>. Ephemeral key exchange MUST
+ have a minimum size of 2048 bits for DHE or security level of 128 bits for ECDHE.
+ Clients MUST accept DHE sizes of up to 4096 bits. HTTP MUST NOT be used with cipher
+ suites that use stream or block ciphers. Authenticated Encryption with Additional Data
+ (AEAD) modes, such as the <xref target="RFC5288">Galois Counter Model (GCM) mode for
+ AES</xref> are acceptable.
+ </t>
+ <t>
+ The effect of these restrictions is that TLS 1.2 implementations could have
+ non-intersecting sets of available cipher suites, since these prevent the use of the
+ cipher suite that TLS 1.2 makes mandatory. To avoid this problem, implementations of
+ HTTP/2 that use TLS 1.2 MUST support TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 <xref
+ target="TLS-ECDHE"/> with P256 <xref target="FIPS186"/>.
+ </t>
+ <t>
+ Clients MAY advertise support of cipher suites that are prohibited by the above
+ restrictions in order to allow for connection to servers that do not support HTTP/2.
+ This enables a fallback to protocols without these constraints without the additional
+ latency imposed by using a separate connection for fallback.
+ </t>
+ </section>
+ </section>
+ </section>
+
+ <section anchor="security" title="Security Considerations">
+ <section title="Server Authority" anchor="authority">
+ <t>
+ HTTP/2 relies on the HTTP/1.1 definition of authority for determining whether a server is
+ authoritative in providing a given response, see <xref target="RFC7230" x:fmt=","
+ x:rel="#establishing.authority"/>. This relies on local name resolution for the "http"
+ URI scheme, and the authenticated server identity for the "https" scheme (see <xref
+ target="RFC2818" x:fmt="," x:sec="3"/>).
+ </t>
+ </section>
+
+ <section title="Cross-Protocol Attacks">
+ <t>
+ In a cross-protocol attack, an attacker causes a client to initiate a transaction in one
+ protocol toward a server that understands a different protocol. An attacker might be able
+ to cause the transaction to appear as valid transaction in the second protocol. In
+ combination with the capabilities of the web context, this can be used to interact with
+ poorly protected servers in private networks.
+ </t>
+ <t>
+ Completing a TLS handshake with an ALPN identifier for HTTP/2 can be considered sufficient
+ protection against cross protocol attacks. ALPN provides a positive indication that a
+ server is willing to proceed with HTTP/2, which prevents attacks on other TLS-based
+ protocols.
+ </t>
+ <t>
+ The encryption in TLS makes it difficult for attackers to control the data which could be
+ used in a cross-protocol attack on a cleartext protocol.
+ </t>
+ <t>
+ The cleartext version of HTTP/2 has minimal protection against cross-protocol attacks.
+ The <xref target="ConnectionHeader">connection preface</xref> contains a string that is
+ designed to confuse HTTP/1.1 servers, but no special protection is offered for other
+ protocols. A server that is willing to ignore parts of an HTTP/1.1 request containing an
+ Upgrade header field in addition to the client connection preface could be exposed to a
+ cross-protocol attack.
+ </t>
+ </section>
+
+ <section title="Intermediary Encapsulation Attacks">
+ <t>
+ HTTP/2 header field names and values are encoded as sequences of octets with a length
+ prefix. This enables HTTP/2 to carry any string of octets as the name or value of a
+ header field. An intermediary that translates HTTP/2 requests or responses into HTTP/1.1
+ directly could permit the creation of corrupted HTTP/1.1 messages. An attacker might
+ exploit this behavior to cause the intermediary to create HTTP/1.1 messages with illegal
+ header fields, extra header fields, or even new messages that are entirely falsified.
+ </t>
+ <t>
+ Header field names or values that contain characters not permitted by HTTP/1.1, including
+ carriage return (ASCII 0xd) or line feed (ASCII 0xa) MUST NOT be translated verbatim by an
+ intermediary, as stipulated in <xref target="RFC7230" x:rel="#field.parsing" x:fmt=","/>.
+ </t>
+ <t>
+ Translation from HTTP/1.x to HTTP/2 does not produce the same opportunity to an attacker.
+ Intermediaries that perform translation to HTTP/2 MUST remove any instances of the <spanx
+ style="verb">obs-fold</spanx> production from header field values.
+ </t>
+ </section>
+
+ <section title="Cacheability of Pushed Responses">
+ <t>
+ Pushed responses do not have an explicit request from the client; the request
+ is provided by the server in the <x:ref>PUSH_PROMISE</x:ref> frame.
+ </t>
+ <t>
+ Caching responses that are pushed is possible based on the guidance provided by the origin
+ server in the Cache-Control header field. However, this can cause issues if a single
+ server hosts more than one tenant. For example, a server might offer multiple users each
+ a small portion of its URI space.
+ </t>
+ <t>
+ Where multiple tenants share space on the same server, that server MUST ensure that
+ tenants are not able to push representations of resources that they do not have authority
+ over. Failure to enforce this would allow a tenant to provide a representation that would
+ be served out of cache, overriding the actual representation that the authoritative tenant
+ provides.
+ </t>
+ <t>
+ Pushed responses for which an origin server is not authoritative (see
+ <xref target="authority"/>) are never cached or used.
+ </t>
+ </section>
+
+ <section anchor="dos" title="Denial of Service Considerations">
+ <t>
+ An HTTP/2 connection can demand a greater commitment of resources to operate than a
+ HTTP/1.1 connection. The use of header compression and flow control depend on a
+ commitment of resources for storing a greater amount of state. Settings for these
+ features ensure that memory commitments for these features are strictly bounded.
+ </t>
+ <t>
+ The number of <x:ref>PUSH_PROMISE</x:ref> frames is not constrained in the same fashion.
+ A client that accepts server push SHOULD limit the number of streams it allows to be in
+ the "reserved (remote)" state. Excessive number of server push streams can be treated as
+ a <xref target="StreamErrorHandler">stream error</xref> of type
+ <x:ref>ENHANCE_YOUR_CALM</x:ref>.
+ </t>
+ <t>
+ Processing capacity cannot be guarded as effectively as state capacity.
+ </t>
+ <t>
+ The <x:ref>SETTINGS</x:ref> frame can be abused to cause a peer to expend additional
+ processing time. This might be done by pointlessly changing SETTINGS parameters, setting
+ multiple undefined parameters, or changing the same setting multiple times in the same
+ frame. <x:ref>WINDOW_UPDATE</x:ref> or <x:ref>PRIORITY</x:ref> frames can be abused to
+ cause an unnecessary waste of resources.
+ </t>
+ <t>
+ Large numbers of small or empty frames can be abused to cause a peer to expend time
+ processing frame headers. Note however that some uses are entirely legitimate, such as
+ the sending of an empty <x:ref>DATA</x:ref> frame to end a stream.
+ </t>
+ <t>
+ Header compression also offers some opportunities to waste processing resources; see <xref
+ target="COMPRESSION" x:fmt="of" x:rel="#Security"/> for more details on potential abuses.
+ </t>
+ <t>
+ Limits in <x:ref>SETTINGS</x:ref> parameters cannot be reduced instantaneously, which
+ leaves an endpoint exposed to behavior from a peer that could exceed the new limits. In
+ particular, immediately after establishing a connection, limits set by a server are not
+ known to clients and could be exceeded without being an obvious protocol violation.
+ </t>
+ <t>
+ All these features - i.e., <x:ref>SETTINGS</x:ref> changes, small frames, header
+ compression - have legitimate uses. These features become a burden only when they are
+ used unnecessarily or to excess.
+ </t>
+ <t>
+ An endpoint that doesn't monitor this behavior exposes itself to a risk of denial of
+ service attack. Implementations SHOULD track the use of these features and set limits on
+ their use. An endpoint MAY treat activity that is suspicious as a <xref
+ target="ConnectionErrorHandler">connection error</xref> of type
+ <x:ref>ENHANCE_YOUR_CALM</x:ref>.
+ </t>
+
+ <section anchor="MaxHeaderBlock" title="Limits on Header Block Size">
+ <t>
+ A large <xref target="HeaderBlock">header block</xref> can cause an implementation to
+ commit a large amount of state. Header fields that are critical for routing can appear
+ toward the end of a header block, which prevents streaming of header fields to their
+ ultimate destination. For this an other reasons, such as ensuring cache correctness,
+ means that an endpoint might need to buffer the entire header block. Since there is no
+ hard limit to the size of a header block, some endpoints could be forced commit a large
+ amount of available memory for header fields.
+ </t>
+ <t>
+ An endpoint can use the <x:ref>SETTINGS_MAX_HEADER_LIST_SIZE</x:ref> to advise peers of
+ limits that might apply on the size of header blocks. This setting is only advisory, so
+ endpoints MAY choose to send header blocks that exceed this limit and risk having the
+ request or response being treated as malformed. This setting specific to a connection,
+ so any request or response could encounter a hop with a lower, unknown limit. An
+ intermediary can attempt to avoid this problem by passing on values presented by
+ different peers, but they are not obligated to do so.
+ </t>
+ <t>
+ A server that receives a larger header block than it is willing to handle can send an
+ HTTP 431 (Request Header Fields Too Large) status code <xref target="RFC6585"/>. A
+ client can discard responses that it cannot process. The header block MUST be processed
+ to ensure a consistent connection state, unless the connection is closed.
+ </t>
+ </section>
+ </section>
+
+ <section title="Use of Compression">
+ <t>
+ HTTP/2 enables greater use of compression for both header fields (<xref
+ target="HeaderBlock"/>) and entity bodies. Compression can allow an attacker to recover
+ secret data when it is compressed in the same context as data under attacker control.
+ </t>
+ <t>
+ There are demonstrable attacks on compression that exploit the characteristics of the web
+ (e.g., <xref target="BREACH"/>). The attacker induces multiple requests containing
+ varying plaintext, observing the length of the resulting ciphertext in each, which
+ reveals a shorter length when a guess about the secret is correct.
+ </t>
+ <t>
+ Implementations communicating on a secure channel MUST NOT compress content that includes
+ both confidential and attacker-controlled data unless separate compression dictionaries
+ are used for each source of data. Compression MUST NOT be used if the source of data
+ cannot be reliably determined. Generic stream compression, such as that provided by TLS
+ MUST NOT be used with HTTP/2 (<xref target="TLSFeatures"/>).
+ </t>
+ <t>
+ Further considerations regarding the compression of header fields are described in <xref
+ target="COMPRESSION"/>.
+ </t>
+ </section>
+
+ <section title="Use of Padding" anchor="padding">
+ <t>
+ Padding within HTTP/2 is not intended as a replacement for general purpose padding, such
+ as might be provided by <xref target="TLS12">TLS</xref>. Redundant padding could even be
+ counterproductive. Correct application can depend on having specific knowledge of the
+ data that is being padded.
+ </t>
+ <t>
+ To mitigate attacks that rely on compression, disabling or limiting compression might be
+ preferable to padding as a countermeasure.
+ </t>
+ <t>
+ Padding can be used to obscure the exact size of frame content, and is provided to
+ mitigate specific attacks within HTTP. For example, attacks where compressed content
+ includes both attacker-controlled plaintext and secret data (see for example, <xref
+ target="BREACH"/>).
+ </t>
+ <t>
+ Use of padding can result in less protection than might seem immediately obvious. At
+ best, padding only makes it more difficult for an attacker to infer length information by
+ increasing the number of frames an attacker has to observe. Incorrectly implemented
+ padding schemes can be easily defeated. In particular, randomized padding with a
+ predictable distribution provides very little protection; similarly, padding payloads to a
+ fixed size exposes information as payload sizes cross the fixed size boundary, which could
+ be possible if an attacker can control plaintext.
+ </t>
+ <t>
+ Intermediaries SHOULD retain padding for <x:ref>DATA</x:ref> frames, but MAY drop padding
+ for <x:ref>HEADERS</x:ref> and <x:ref>PUSH_PROMISE</x:ref> frames. A valid reason for an
+ intermediary to change the amount of padding of frames is to improve the protections that
+ padding provides.
+ </t>
+ </section>
+
+ <section title="Privacy Considerations">
+ <t>
+ Several characteristics of HTTP/2 provide an observer an opportunity to correlate actions
+ of a single client or server over time. This includes the value of settings, the manner
+ in which flow control windows are managed, the way priorities are allocated to streams,
+ timing of reactions to stimulus, and handling of any optional features.
+ </t>
+ <t>
+ As far as this creates observable differences in behavior, they could be used as a basis
+ for fingerprinting a specific client, as defined in <xref target="HTML5" x:fmt="of"
+ x:sec="1.8" x:rel="introduction.html#fingerprint"/>.
+ </t>
+ </section>
+ </section>
+
+ <section anchor="iana" title="IANA Considerations">
+ <t>
+ A string for identifying HTTP/2 is entered into the "Application Layer Protocol Negotiation
+ (ALPN) Protocol IDs" registry established in <xref target="TLS-ALPN"/>.
+ </t>
+ <t>
+ This document establishes a registry for frame types, settings, and error codes. These new
+ registries are entered into a new "Hypertext Transfer Protocol (HTTP) 2 Parameters" section.
+ </t>
+ <t>
+ This document registers the <spanx style="verb">HTTP2-Settings</spanx> header field for
+ use in HTTP; and the 421 (Misdirected Request) status code.
+ </t>
+ <t>
+ This document registers the <spanx style="verb">PRI</spanx> method for use in HTTP, to avoid
+ collisions with the <xref target="ConnectionHeader">connection preface</xref>.
+ </t>
+
+ <section anchor="iana-alpn" title="Registration of HTTP/2 Identification Strings">
+ <t>
+ This document creates two registrations for the identification of HTTP/2 in the
+ "Application Layer Protocol Negotiation (ALPN) Protocol IDs" registry established in <xref
+ target="TLS-ALPN"/>.
+ </t>
+ <t>
+ The "h2" string identifies HTTP/2 when used over TLS:
+ <list style="hanging">
+ <t hangText="Protocol:">HTTP/2 over TLS</t>
+ <t hangText="Identification Sequence:">0x68 0x32 ("h2")</t>
+ <t hangText="Specification:">This document</t>
+ </list>
+ </t>
+ <t>
+ The "h2c" string identifies HTTP/2 when used over cleartext TCP:
+ <list style="hanging">
+ <t hangText="Protocol:">HTTP/2 over TCP</t>
+ <t hangText="Identification Sequence:">0x68 0x32 0x63 ("h2c")</t>
+ <t hangText="Specification:">This document</t>
+ </list>
+ </t>
+ </section>
+
+ <section anchor="iana-frames" title="Frame Type Registry">
+ <t>
+ This document establishes a registry for HTTP/2 frame type codes. The "HTTP/2 Frame
+ Type" registry manages an 8-bit space. The "HTTP/2 Frame Type" registry operates under
+ either of the <xref target="RFC5226">"IETF Review" or "IESG Approval" policies</xref> for
+ values between 0x00 and 0xef, with values between 0xf0 and 0xff being reserved for
+ experimental use.
+ </t>
+ <t>
+ New entries in this registry require the following information:
+ <list style="hanging">
+ <t hangText="Frame Type:">
+ A name or label for the frame type.
+ </t>
+ <t hangText="Code:">
+ The 8-bit code assigned to the frame type.
+ </t>
+ <t hangText="Specification:">
+ A reference to a specification that includes a description of the frame layout,
+ it's semantics and flags that the frame type uses, including any parts of the frame
+ that are conditionally present based on the value of flags.
+ </t>
+ </list>
+ </t>
+ <t>
+ The entries in the following table are registered by this document.
+ </t>
+ <texttable align="left" suppress-title="true">
+ <ttcol>Frame Type</ttcol>
+ <ttcol>Code</ttcol>
+ <ttcol>Section</ttcol>
+ <c>DATA</c><c>0x0</c><c><xref target="DATA"/></c>
+ <c>HEADERS</c><c>0x1</c><c><xref target="HEADERS"/></c>
+ <c>PRIORITY</c><c>0x2</c><c><xref target="PRIORITY"/></c>
+ <c>RST_STREAM</c><c>0x3</c><c><xref target="RST_STREAM"/></c>
+ <c>SETTINGS</c><c>0x4</c><c><xref target="SETTINGS"/></c>
+ <c>PUSH_PROMISE</c><c>0x5</c><c><xref target="PUSH_PROMISE"/></c>
+ <c>PING</c><c>0x6</c><c><xref target="PING"/></c>
+ <c>GOAWAY</c><c>0x7</c><c><xref target="GOAWAY"/></c>
+ <c>WINDOW_UPDATE</c><c>0x8</c><c><xref target="WINDOW_UPDATE"/></c>
+ <c>CONTINUATION</c><c>0x9</c><c><xref target="CONTINUATION"/></c>
+ </texttable>
+ </section>
+
+ <section anchor="iana-settings" title="Settings Registry">
+ <t>
+ This document establishes a registry for HTTP/2 settings. The "HTTP/2 Settings" registry
+ manages a 16-bit space. The "HTTP/2 Settings" registry operates under the <xref
+ target="RFC5226">"Expert Review" policy</xref> for values in the range from 0x0000 to
+ 0xefff, with values between and 0xf000 and 0xffff being reserved for experimental use.
+ </t>
+ <t>
+ New registrations are advised to provide the following information:
+ <list style="hanging">
+ <t hangText="Name:">
+ A symbolic name for the setting. Specifying a setting name is optional.
+ </t>
+ <t hangText="Code:">
+ The 16-bit code assigned to the setting.
+ </t>
+ <t hangText="Initial Value:">
+ An initial value for the setting.
+ </t>
+ <t hangText="Specification:">
+ An optional reference to a specification that describes the use of the setting.
+ </t>
+ </list>
+ </t>
+ <t>
+ An initial set of setting registrations can be found in <xref target="SettingValues"/>.
+ </t>
+ <texttable align="left" suppress-title="true">
+ <ttcol>Name</ttcol>
+ <ttcol>Code</ttcol>
+ <ttcol>Initial Value</ttcol>
+ <ttcol>Specification</ttcol>
+ <c>HEADER_TABLE_SIZE</c>
+ <c>0x1</c><c>4096</c><c><xref target="SettingValues"/></c>
+ <c>ENABLE_PUSH</c>
+ <c>0x2</c><c>1</c><c><xref target="SettingValues"/></c>
+ <c>MAX_CONCURRENT_STREAMS</c>
+ <c>0x3</c><c>(infinite)</c><c><xref target="SettingValues"/></c>
+ <c>INITIAL_WINDOW_SIZE</c>
+ <c>0x4</c><c>65535</c><c><xref target="SettingValues"/></c>
+ <c>MAX_FRAME_SIZE</c>
+ <c>0x5</c><c>16384</c><c><xref target="SettingValues"/></c>
+ <c>MAX_HEADER_LIST_SIZE</c>
+ <c>0x6</c><c>(infinite)</c><c><xref target="SettingValues"/></c>
+ </texttable>
+
+ </section>
+
+ <section anchor="iana-errors" title="Error Code Registry">
+ <t>
+ This document establishes a registry for HTTP/2 error codes. The "HTTP/2 Error Code"
+ registry manages a 32-bit space. The "HTTP/2 Error Code" registry operates under the
+ <xref target="RFC5226">"Expert Review" policy</xref>.
+ </t>
+ <t>
+ Registrations for error codes are required to include a description of the error code. An
+ expert reviewer is advised to examine new registrations for possible duplication with
+ existing error codes. Use of existing registrations is to be encouraged, but not
+ mandated.
+ </t>
+ <t>
+ New registrations are advised to provide the following information:
+ <list style="hanging">
+ <t hangText="Name:">
+ A name for the error code. Specifying an error code name is optional.
+ </t>
+ <t hangText="Code:">
+ The 32-bit error code value.
+ </t>
+ <t hangText="Description:">
+ A brief description of the error code semantics, longer if no detailed specification
+ is provided.
+ </t>
+ <t hangText="Specification:">
+ An optional reference for a specification that defines the error code.
+ </t>
+ </list>
+ </t>
+ <t>
+ The entries in the following table are registered by this document.
+ </t>
+ <texttable align="left" suppress-title="true">
+ <ttcol>Name</ttcol>
+ <ttcol>Code</ttcol>
+ <ttcol>Description</ttcol>
+ <ttcol>Specification</ttcol>
+ <c>NO_ERROR</c><c>0x0</c>
+ <c>Graceful shutdown</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>PROTOCOL_ERROR</c><c>0x1</c>
+ <c>Protocol error detected</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>INTERNAL_ERROR</c><c>0x2</c>
+ <c>Implementation fault</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>FLOW_CONTROL_ERROR</c><c>0x3</c>
+ <c>Flow control limits exceeded</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>SETTINGS_TIMEOUT</c><c>0x4</c>
+ <c>Settings not acknowledged</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>STREAM_CLOSED</c><c>0x5</c>
+ <c>Frame received for closed stream</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>FRAME_SIZE_ERROR</c><c>0x6</c>
+ <c>Frame size incorrect</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>REFUSED_STREAM</c><c>0x7</c>
+ <c>Stream not processed</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>CANCEL</c><c>0x8</c>
+ <c>Stream cancelled</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>COMPRESSION_ERROR</c><c>0x9</c>
+ <c>Compression state not updated</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>CONNECT_ERROR</c><c>0xa</c>
+ <c>TCP connection error for CONNECT method</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>ENHANCE_YOUR_CALM</c><c>0xb</c>
+ <c>Processing capacity exceeded</c>
+ <c><xref target="ErrorCodes"/></c>
+ <c>INADEQUATE_SECURITY</c><c>0xc</c>
+ <c>Negotiated TLS parameters not acceptable</c>
+ <c><xref target="ErrorCodes"/></c>
+ </texttable>
+
+ </section>
+
+ <section title="HTTP2-Settings Header Field Registration">
+ <t>
+ This section registers the <spanx style="verb">HTTP2-Settings</spanx> header field in the
+ <xref target="BCP90">Permanent Message Header Field Registry</xref>.
+ <list style="hanging">
+ <t hangText="Header field name:">
+ HTTP2-Settings
+ </t>
+ <t hangText="Applicable protocol:">
+ http
+ </t>
+ <t hangText="Status:">
+ standard
+ </t>
+ <t hangText="Author/Change controller:">
+ IETF
+ </t>
+ <t hangText="Specification document(s):">
+ <xref target="Http2SettingsHeader"/> of this document
+ </t>
+ <t hangText="Related information:">
+ This header field is only used by an HTTP/2 client for Upgrade-based negotiation.
+ </t>
+ </list>
+ </t>
+ </section>
+
+ <section title="PRI Method Registration">
+ <t>
+ This section registers the <spanx style="verb">PRI</spanx> method in the HTTP Method
+ Registry (<xref target="RFC7231" x:fmt="," x:rel="#method.registry"/>).
+ <list style="hanging">
+ <t hangText="Method Name:">
+ PRI
+ </t>
+ <t hangText="Safe">
+ No
+ </t>
+ <t hangText="Idempotent">
+ No
+ </t>
+ <t hangText="Specification document(s)">
+ <xref target="ConnectionHeader"/> of this document
+ </t>
+ <t hangText="Related information:">
+ This method is never used by an actual client. This method will appear to be used
+ when an HTTP/1.1 server or intermediary attempts to parse an HTTP/2 connection
+ preface.
+ </t>
+ </list>
+ </t>
+ </section>
+
+ <section title="The 421 (Misdirected Request) HTTP Status Code"
+ anchor="iana-MisdirectedRequest">
+ <t>
+ This document registers the 421 (Misdirected Request) HTTP Status code in the Hypertext
+ Transfer Protocol (HTTP) Status Code Registry (<xref target="RFC7231" x:fmt=","
+ x:rel="#status.code.registry"/>).
+ </t>
+ <t>
+ <list style="hanging">
+ <t hangText="Status Code:">
+ 421
+ </t>
+ <t hangText="Short Description:">
+ Misdirected Request
+ </t>
+ <t hangText="Specification:">
+ <xref target="MisdirectedRequest"/> of this document
+ </t>
+ </list>
+ </t>
+ </section>
+
+ </section>
+
+ <section title="Acknowledgements">
+ <t>
+ This document includes substantial input from the following individuals:
+ <list style="symbols">
+ <t>
+ Adam Langley, Wan-Teh Chang, Jim Morrison, Mark Nottingham, Alyssa Wilk, Costin
+ Manolache, William Chan, Vitaliy Lvin, Joe Chan, Adam Barth, Ryan Hamilton, Gavin
+ Peters, Kent Alstad, Kevin Lindsay, Paul Amer, Fan Yang, Jonathan Leighton (SPDY
+ contributors).
+ </t>
+ <t>
+ Gabriel Montenegro and Willy Tarreau (Upgrade mechanism).
+ </t>
+ <t>
+ William Chan, Salvatore Loreto, Osama Mazahir, Gabriel Montenegro, Jitu Padhye, Roberto
+ Peon, Rob Trace (Flow control).
+ </t>
+ <t>
+ Mike Bishop (Extensibility).
+ </t>
+ <t>
+ Mark Nottingham, Julian Reschke, James Snell, Jeff Pinner, Mike Bishop, Herve Ruellan
+ (Substantial editorial contributions).
+ </t>
+ <t>
+ Kari Hurtta, Tatsuhiro Tsujikawa, Greg Wilkins, Poul-Henning Kamp.
+ </t>
+ <t>
+ Alexey Melnikov was an editor of this document during 2013.
+ </t>
+ <t>
+ A substantial proportion of Martin's contribution was supported by Microsoft during his
+ employment there.
+ </t>
+ </list>
+ </t>
+ </section>
+ </middle>
+
+ <back>
+ <references title="Normative References">
+ <reference anchor="COMPRESSION">
+ <front>
+ <title>HPACK - Header Compression for HTTP/2</title>
+ <author initials="H." surname="Ruellan" fullname="Herve Ruellan"/>
+ <author initials="R." surname="Peon" fullname="Roberto Peon"/>
+ <date month="July" year="2014" />
+ </front>
+ <seriesInfo name="Internet-Draft" value="draft-ietf-httpbis-header-compression-09" />
+ <x:source href="refs/draft-ietf-httpbis-header-compression-09.xml"/>
+ </reference>
+
+ <reference anchor="TCP">
+ <front>
+ <title abbrev="Transmission Control Protocol">
+ Transmission Control Protocol
+ </title>
+ <author initials="J." surname="Postel" fullname="Jon Postel">
+ <organization>University of Southern California (USC)/Information Sciences
+ Institute</organization>
+ </author>
+ <date year="1981" month="September" />
+ </front>
+ <seriesInfo name="STD" value="7" />
+ <seriesInfo name="RFC" value="793" />
+ </reference>
+
+ <reference anchor="RFC2119">
+ <front>
+ <title>
+ Key words for use in RFCs to Indicate Requirement Levels
+ </title>
+ <author initials="S." surname="Bradner" fullname="Scott Bradner">
+ <organization>Harvard University</organization>
+ <address><email>sob@harvard.edu</email></address>
+ </author>
+ <date month="March" year="1997"/>
+ </front>
+ <seriesInfo name="BCP" value="14"/>
+ <seriesInfo name="RFC" value="2119"/>
+ </reference>
+
+ <reference anchor="RFC2818">
+ <front>
+ <title>
+ HTTP Over TLS
+ </title>
+ <author initials="E." surname="Rescorla" fullname="Eric Rescorla"/>
+ <date month="May" year="2000"/>
+ </front>
+ <seriesInfo name="RFC" value="2818"/>
+ </reference>
+
+ <reference anchor="RFC3986">
+ <front>
+ <title abbrev="URI Generic Syntax">Uniform Resource Identifier (URI): Generic
+ Syntax</title>
+ <author initials="T." surname="Berners-Lee" fullname="Tim Berners-Lee"></author>
+ <author initials="R." surname="Fielding" fullname="Roy T. Fielding"></author>
+ <author initials="L." surname="Masinter" fullname="Larry Masinter"></author>
+ <date year="2005" month="January" />
+ </front>
+ <seriesInfo name="STD" value="66" />
+ <seriesInfo name="RFC" value="3986" />
+ </reference>
+
+ <reference anchor="RFC4648">
+ <front>
+ <title>The Base16, Base32, and Base64 Data Encodings</title>
+ <author fullname="S. Josefsson" initials="S." surname="Josefsson"/>
+ <date year="2006" month="October"/>
+ </front>
+ <seriesInfo value="4648" name="RFC"/>
+ </reference>
+
+ <reference anchor="RFC5226">
+ <front>
+ <title>Guidelines for Writing an IANA Considerations Section in RFCs</title>
+ <author initials="T." surname="Narten" fullname="T. Narten"/>
+ <author initials="H." surname="Alvestrand" fullname="H. Alvestrand"/>
+ <date year="2008" month="May" />
+ </front>
+ <seriesInfo name="BCP" value="26" />
+ <seriesInfo name="RFC" value="5226" />
+ </reference>
+
+ <reference anchor="RFC5234">
+ <front>
+ <title>Augmented BNF for Syntax Specifications: ABNF</title>
+ <author initials="D." surname="Crocker" fullname="D. Crocker"/>
+ <author initials="P." surname="Overell" fullname="P. Overell"/>
+ <date year="2008" month="January" />
+ </front>
+ <seriesInfo name="STD" value="68" />
+ <seriesInfo name="RFC" value="5234" />
+ </reference>
+
+ <reference anchor="TLS12">
+ <front>
+ <title>The Transport Layer Security (TLS) Protocol Version 1.2</title>
+ <author initials="T." surname="Dierks" fullname="Tim Dierks"/>
+ <author initials="E." surname="Rescorla" fullname="Eric Rescorla"/>
+ <date year="2008" month="August" />
+ </front>
+ <seriesInfo name="RFC" value="5246" />
+ </reference>
+
+ <reference anchor="TLS-EXT">
+ <front>
+ <title>
+ Transport Layer Security (TLS) Extensions: Extension Definitions
+ </title>
+ <author initials="D." surname="Eastlake" fullname="D. Eastlake"/>
+ <date year="2011" month="January"/>
+ </front>
+ <seriesInfo name="RFC" value="6066"/>
+ </reference>
+
+ <reference anchor="TLS-ALPN">
+ <front>
+ <title>Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension</title>
+ <author initials="S." surname="Friedl" fullname="Stephan Friedl"></author>
+ <author initials="A." surname="Popov" fullname="Andrei Popov"></author>
+ <author initials="A." surname="Langley" fullname="Adam Langley"></author>
+ <author initials="E." surname="Stephan" fullname="Emile Stephan"></author>
+ <date month="July" year="2014" />
+ </front>
+ <seriesInfo name="RFC" value="7301" />
+ </reference>
+
+ <reference anchor="TLS-ECDHE">
+ <front>
+ <title>
+ TLS Elliptic Curve Cipher Suites with SHA-256/384 and AES Galois
+ Counter Mode (GCM)
+ </title>
+ <author initials="E." surname="Rescorla" fullname="E. Rescorla"/>
+ <date year="2008" month="August" />
+ </front>
+ <seriesInfo name="RFC" value="5289" />
+ </reference>
+
+ <reference anchor="FIPS186">
+ <front>
+ <title>
+ Digital Signature Standard (DSS)
+ </title>
+ <author><organization>NIST</organization></author>
+ <date year="2013" month="July" />
+ </front>
+ <seriesInfo name="FIPS" value="PUB 186-4" />
+ </reference>
+
+ <reference anchor="RFC7230">
+ <front>
+ <title>
+ Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing</title>
+ <author fullname="Roy T. Fielding" initials="R." role="editor" surname="Fielding">
+ <organization abbrev="Adobe">Adobe Systems Incorporated</organization>
+ <address><email>fielding@gbiv.com</email></address>
+ </author>
+ <author fullname="Julian F. Reschke" initials="J. F." role="editor" surname="Reschke">
+ <organization abbrev="greenbytes">greenbytes GmbH</organization>
+ <address><email>julian.reschke@greenbytes.de</email></address>
+ </author>
+ <date month="June" year="2014" />
+ </front>
+ <seriesInfo name="RFC" value="7230" />
+ <x:source href="refs/rfc7230.xml"
+ basename="https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230"/>
+ </reference>
+ <reference anchor="RFC7231">
+ <front>
+ <title>
+ Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content</title>
+ <author fullname="Roy T. Fielding" initials="R." role="editor" surname="Fielding">
+ <organization abbrev="Adobe">Adobe Systems Incorporated</organization>
+ <address><email>fielding@gbiv.com</email></address>
+ </author>
+ <author fullname="Julian F. Reschke" initials="J. F." role="editor" surname="Reschke">
+ <organization abbrev="greenbytes">greenbytes GmbH</organization>
+ <address><email>julian.reschke@greenbytes.de</email></address>
+ </author>
+ <date month="June" year="2014" />
+ </front>
+ <seriesInfo name="RFC" value="7231" />
+ <x:source href="refs/rfc7231.xml"
+ basename="https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7231"/>
+ </reference>
+ <reference anchor="RFC7232">
+ <front>
+ <title>Hypertext Transfer Protocol (HTTP/1.1): Conditional Requests</title>
+ <author fullname="Roy T. Fielding" initials="R." role="editor" surname="Fielding">
+ <organization abbrev="Adobe">Adobe Systems Incorporated</organization>
+ <address><email>fielding@gbiv.com</email></address>
+ </author>
+ <author fullname="Julian F. Reschke" initials="J. F." role="editor" surname="Reschke">
+ <organization abbrev="greenbytes">greenbytes GmbH</organization>
+ <address><email>julian.reschke@greenbytes.de</email></address>
+ </author>
+ <date month="June" year="2014" />
+ </front>
+ <seriesInfo name="RFC" value="7232" />
+ </reference>
+ <reference anchor="RFC7233">
+ <front>
+ <title>Hypertext Transfer Protocol (HTTP/1.1): Range Requests</title>
+ <author initials="R." surname="Fielding" fullname="Roy T. Fielding" role="editor">
+ <organization abbrev="Adobe">Adobe Systems Incorporated</organization>
+ <address><email>fielding@gbiv.com</email></address>
+ </author>
+ <author initials="Y." surname="Lafon" fullname="Yves Lafon" role="editor">
+ <organization abbrev="W3C">World Wide Web Consortium</organization>
+ <address><email>ylafon@w3.org</email></address>
+ </author>
+ <author initials="J. F." surname="Reschke" fullname="Julian F. Reschke" role="editor">
+ <organization abbrev="greenbytes">greenbytes GmbH</organization>
+ <address><email>julian.reschke@greenbytes.de</email></address>
+ </author>
+ <date month="June" year="2014" />
+ </front>
+ <seriesInfo name="RFC" value="7233" />
+ </reference>
+ <reference anchor="RFC7234">
+ <front>
+ <title>Hypertext Transfer Protocol (HTTP/1.1): Caching</title>
+ <author initials="R." surname="Fielding" fullname="Roy T. Fielding" role="editor">
+ <organization abbrev="Adobe">Adobe Systems Incorporated</organization>
+ <address><email>fielding@gbiv.com</email></address>
+ </author>
+ <author fullname="Mark Nottingham" initials="M." role="editor" surname="Nottingham">
+ <organization>Akamai</organization>
+ <address><email>mnot@mnot.net</email></address>
+ </author>
+ <author initials="J. F." surname="Reschke" fullname="Julian F. Reschke" role="editor">
+ <organization abbrev="greenbytes">greenbytes GmbH</organization>
+ <address><email>julian.reschke@greenbytes.de</email></address>
+ </author>
+ <date month="June" year="2014" />
+ </front>
+ <seriesInfo name="RFC" value="7234"/>
+ <x:source href="refs/rfc7234.xml"
+ basename="https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7234"/>
+ </reference>
+ <reference anchor="RFC7235">
+ <front>
+ <title>Hypertext Transfer Protocol (HTTP/1.1): Authentication</title>
+ <author initials="R." surname="Fielding" fullname="Roy T. Fielding" role="editor">
+ <organization abbrev="Adobe">Adobe Systems Incorporated</organization>
+ <address><email>fielding@gbiv.com</email></address>
+ </author>
+ <author initials="J. F." surname="Reschke" fullname="Julian F. Reschke" role="editor">
+ <organization abbrev="greenbytes">greenbytes GmbH</organization>
+ <address><email>julian.reschke@greenbytes.de</email></address>
+ </author>
+ <date month="June" year="2014" />
+ </front>
+ <seriesInfo name="RFC" value="7235"/>
+ <x:source href="refs/rfc7235.xml"
+ basename="https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7235"/>
+ </reference>
+
+ <reference anchor="COOKIE">
+ <front>
+ <title>HTTP State Management Mechanism</title>
+ <author initials="A." surname="Barth" fullname="A. Barth"/>
+ <date year="2011" month="April" />
+ </front>
+ <seriesInfo name="RFC" value="6265" />
+ </reference>
+ </references>
+
+ <references title="Informative References">
+ <reference anchor="RFC1323">
+ <front>
+ <title>
+ TCP Extensions for High Performance
+ </title>
+ <author initials="V." surname="Jacobson" fullname="Van Jacobson"></author>
+ <author initials="B." surname="Braden" fullname="Bob Braden"></author>
+ <author initials="D." surname="Borman" fullname="Dave Borman"></author>
+ <date year="1992" month="May" />
+ </front>
+ <seriesInfo name="RFC" value="1323" />
+ </reference>
+
+ <reference anchor="RFC3749">
+ <front>
+ <title>Transport Layer Security Protocol Compression Methods</title>
+ <author initials="S." surname="Hollenbeck" fullname="S. Hollenbeck"/>
+ <date year="2004" month="May" />
+ </front>
+ <seriesInfo name="RFC" value="3749" />
+ </reference>
+
+ <reference anchor="RFC6585">
+ <front>
+ <title>Additional HTTP Status Codes</title>
+ <author initials="M." surname="Nottingham" fullname="Mark Nottingham"/>
+ <author initials="R." surname="Fielding" fullname="Roy Fielding"/>
+ <date year="2012" month="April" />
+ </front>
+ <seriesInfo name="RFC" value="6585" />
+ </reference>
+
+ <reference anchor="RFC4492">
+ <front>
+ <title>
+ Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS)
+ </title>
+ <author initials="S." surname="Blake-Wilson" fullname="S. Blake-Wilson"/>
+ <author initials="N." surname="Bolyard" fullname="N. Bolyard"/>
+ <author initials="V." surname="Gupta" fullname="V. Gupta"/>
+ <author initials="C." surname="Hawk" fullname="C. Hawk"/>
+ <author initials="B." surname="Moeller" fullname="B. Moeller"/>
+ <date year="2006" month="May" />
+ </front>
+ <seriesInfo name="RFC" value="4492" />
+ </reference>
+
+ <reference anchor="RFC5288">
+ <front>
+ <title>
+ AES Galois Counter Mode (GCM) Cipher Suites for TLS
+ </title>
+ <author initials="J." surname="Salowey" fullname="J. Salowey"/>
+ <author initials="A." surname="Choudhury" fullname="A. Choudhury"/>
+ <author initials="D." surname="McGrew" fullname="D. McGrew"/>
+ <date year="2008" month="August" />
+ </front>
+ <seriesInfo name="RFC" value="5288" />
+ </reference>
+
+ <reference anchor='HTML5'
+ target='http://www.w3.org/TR/2014/CR-html5-20140731/'>
+ <front>
+ <title>HTML5</title>
+ <author fullname='Robin Berjon' surname='Berjon' initials='R.'/>
+ <author fullname='Steve Faulkner' surname='Faulkner' initials='S.'/>
+ <author fullname='Travis Leithead' surname='Leithead' initials='T.'/>
+ <author fullname='Erika Doyle Navara' surname='Doyle Navara' initials='E.'/>
+ <author fullname='Edward O'Connor' surname='O'Connor' initials='E.'/>
+ <author fullname='Silvia Pfeiffer' surname='Pfeiffer' initials='S.'/>
+ <date year='2014' month='July' day='31'/>
+ </front>
+ <seriesInfo name='W3C Candidate Recommendation' value='CR-html5-20140731'/>
+ <annotation>
+ Latest version available at
+ <eref target='http://www.w3.org/TR/html5/'/>.
+ </annotation>
+ </reference>
+
+ <reference anchor="TALKING" target="http://w2spconf.com/2011/papers/websocket.pdf">
+ <front>
+ <title>
+ Talking to Yourself for Fun and Profit
+ </title>
+ <author initials="L-S." surname="Huang"/>
+ <author initials="E." surname="Chen"/>
+ <author initials="A." surname="Barth"/>
+ <author initials="E." surname="Rescorla"/>
+ <author initials="C." surname="Jackson"/>
+ <date year="2011" />
+ </front>
+ </reference>
+
+ <reference anchor="BREACH"
+ target="http://breachattack.com/resources/BREACH%20-%20SSL,%20gone%20in%2030%20seconds.pdf">
+ <front>
+ <title>
+ BREACH: Reviving the CRIME Attack
+ </title>
+ <author initials="Y." surname="Gluck"/>
+ <author initials="N." surname="Harris"/>
+ <author initials="A." surname="Prado"/>
+ <date year="2013" month="July" day="12"/>
+ </front>
+ </reference>
+
+ <reference anchor="BCP90">
+ <front>
+ <title>Registration Procedures for Message Header Fields</title>
+ <author initials="G." surname="Klyne" fullname="G. Klyne">
+ <organization>Nine by Nine</organization>
+ <address><email>GK-IETF@ninebynine.org</email></address>
+ </author>
+ <author initials="M." surname="Nottingham" fullname="M. Nottingham">
+ <organization>BEA Systems</organization>
+ <address><email>mnot@pobox.com</email></address>
+ </author>
+ <author initials="J." surname="Mogul" fullname="J. Mogul">
+ <organization>HP Labs</organization>
+ <address><email>JeffMogul@acm.org</email></address>
+ </author>
+ <date year="2004" month="September" />
+ </front>
+ <seriesInfo name="BCP" value="90" />
+ <seriesInfo name="RFC" value="3864" />
+ </reference>
+
+ <reference anchor="TLSBCP">
+ <front>
+ <title>Recommendations for Secure Use of TLS and DTLS</title>
+ <author initials="Y" surname="Sheffer" fullname="Yaron Sheffer">
+ <organization />
+ </author>
+ <author initials="R" surname="Holz" fullname="Ralph Holz">
+ <organization />
+ </author>
+ <author initials="P" surname="Saint-Andre" fullname="Peter Saint-Andre">
+ <organization />
+ </author>
+ <date month="June" day="23" year="2014" />
+ </front>
+ <seriesInfo name="Internet-Draft" value="draft-ietf-uta-tls-bcp-01" />
+ </reference>
+
+ <reference anchor="ALT-SVC">
+ <front>
+ <title>
+ HTTP Alternative Services
+ </title>
+ <author initials="M." surname="Nottingham" fullname="Mark Nottingham">
+ <organization>Akamai</organization>
+ </author>
+ <author initials="P." surname="McManus" fullname="Patrick McManus">
+ <organization>Mozilla</organization>
+ </author>
+ <author initials="J." surname="Reschke" fullname="Julian Reschke">
+ <organization>greenbytes</organization>
+ </author>
+ <date year="2014" month="April"/>
+ </front>
+ <seriesInfo name="Internet-Draft" value="draft-ietf-httpbis-alt-svc-02"/>
+ <x:source href="refs/draft-ietf-httpbis-alt-svc-02.xml"/>
+ </reference>
+ </references>
+
+ <section title="Change Log" anchor="change.log">
+ <t>
+ This section is to be removed by RFC Editor before publication.
+ </t>
+
+ <section title="Since draft-ietf-httpbis-http2-14" anchor="changes.since.draft-ietf-httpbis-http2-14">
+ <t>
+ Renamed Not Authoritative status code to Misdirected Request.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-13" anchor="changes.since.draft-ietf-httpbis-http2-13">
+ <t>
+ Pseudo-header fields are now required to appear strictly before regular ones.
+ </t>
+ <t>
+ Restored 1xx series status codes, except 101.
+ </t>
+ <t>
+ Changed frame length field 24-bits. Expanded frame header to 9 octets. Added a setting
+ to limit the damage.
+ </t>
+ <t>
+ Added a setting to advise peers of header set size limits.
+ </t>
+ <t>
+ Removed segments.
+ </t>
+ <t>
+ Made non-semantic-bearing <x:ref>HEADERS</x:ref> frames illegal in the HTTP mapping.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-12" anchor="changes.since.draft-ietf-httpbis-http2-12">
+ <t>
+ Restored extensibility options.
+ </t>
+ <t>
+ Restricting TLS cipher suites to AEAD only.
+ </t>
+ <t>
+ Removing Content-Encoding requirements.
+ </t>
+ <t>
+ Permitting the use of <x:ref>PRIORITY</x:ref> after stream close.
+ </t>
+ <t>
+ Removed ALTSVC frame.
+ </t>
+ <t>
+ Removed BLOCKED frame.
+ </t>
+ <t>
+ Reducing the maximum padding size to 256 octets; removing padding from
+ <x:ref>CONTINUATION</x:ref> frames.
+ </t>
+ <t>
+ Removed per-frame GZIP compression.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-11" anchor="changes.since.draft-ietf-httpbis-http2-11">
+ <t>
+ Added BLOCKED frame (at risk).
+ </t>
+ <t>
+ Simplified priority scheme.
+ </t>
+ <t>
+ Added <x:ref>DATA</x:ref> per-frame GZIP compression.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-10" anchor="changes.since.draft-ietf-httpbis-http2-10">
+ <t>
+ Changed "connection header" to "connection preface" to avoid confusion.
+ </t>
+ <t>
+ Added dependency-based stream prioritization.
+ </t>
+ <t>
+ Added "h2c" identifier to distinguish between cleartext and secured HTTP/2.
+ </t>
+ <t>
+ Adding missing padding to <x:ref>PUSH_PROMISE</x:ref>.
+ </t>
+ <t>
+ Integrate ALTSVC frame and supporting text.
+ </t>
+ <t>
+ Dropping requirement on "deflate" Content-Encoding.
+ </t>
+ <t>
+ Improving security considerations around use of compression.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-09" anchor="changes.since.draft-ietf-httpbis-http2-09">
+ <t>
+ Adding padding for data frames.
+ </t>
+ <t>
+ Renumbering frame types, error codes, and settings.
+ </t>
+ <t>
+ Adding INADEQUATE_SECURITY error code.
+ </t>
+ <t>
+ Updating TLS usage requirements to 1.2; forbidding TLS compression.
+ </t>
+ <t>
+ Removing extensibility for frames and settings.
+ </t>
+ <t>
+ Changing setting identifier size.
+ </t>
+ <t>
+ Removing the ability to disable flow control.
+ </t>
+ <t>
+ Changing the protocol identification token to "h2".
+ </t>
+ <t>
+ Changing the use of :authority to make it optional and to allow userinfo in non-HTTP
+ cases.
+ </t>
+ <t>
+ Allowing split on 0x0 for Cookie.
+ </t>
+ <t>
+ Reserved PRI method in HTTP/1.1 to avoid possible future collisions.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-08" anchor="changes.since.draft-ietf-httpbis-http2-08">
+ <t>
+ Added cookie crumbling for more efficient header compression.
+ </t>
+ <t>
+ Added header field ordering with the value-concatenation mechanism.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-07" anchor="changes.since.draft-ietf-httpbis-http2-07">
+ <t>
+ Marked draft for implementation.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-06" anchor="changes.since.draft-ietf-httpbis-http2-06">
+ <t>
+ Adding definition for CONNECT method.
+ </t>
+ <t>
+ Constraining the use of push to safe, cacheable methods with no request body.
+ </t>
+ <t>
+ Changing from :host to :authority to remove any potential confusion.
+ </t>
+ <t>
+ Adding setting for header compression table size.
+ </t>
+ <t>
+ Adding settings acknowledgement.
+ </t>
+ <t>
+ Removing unnecessary and potentially problematic flags from CONTINUATION.
+ </t>
+ <t>
+ Added denial of service considerations.
+ </t>
+ </section>
+ <section title="Since draft-ietf-httpbis-http2-05" anchor="changes.since.draft-ietf-httpbis-http2-05">
+ <t>
+ Marking the draft ready for implementation.
+ </t>
+ <t>
+ Renumbering END_PUSH_PROMISE flag.
+ </t>
+ <t>
+ Editorial clarifications and changes.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-04" anchor="changes.since.draft-ietf-httpbis-http2-04">
+ <t>
+ Added CONTINUATION frame for HEADERS and PUSH_PROMISE.
+ </t>
+ <t>
+ PUSH_PROMISE is no longer implicitly prohibited if SETTINGS_MAX_CONCURRENT_STREAMS is
+ zero.
+ </t>
+ <t>
+ Push expanded to allow all safe methods without a request body.
+ </t>
+ <t>
+ Clarified the use of HTTP header fields in requests and responses. Prohibited HTTP/1.1
+ hop-by-hop header fields.
+ </t>
+ <t>
+ Requiring that intermediaries not forward requests with missing or illegal routing
+ :-headers.
+ </t>
+ <t>
+ Clarified requirements around handling different frames after stream close, stream reset
+ and <x:ref>GOAWAY</x:ref>.
+ </t>
+ <t>
+ Added more specific prohibitions for sending of different frame types in various stream
+ states.
+ </t>
+ <t>
+ Making the last received setting value the effective value.
+ </t>
+ <t>
+ Clarified requirements on TLS version, extension and ciphers.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-03" anchor="changes.since.draft-ietf-httpbis-http2-03">
+ <t>
+ Committed major restructuring atrocities.
+ </t>
+ <t>
+ Added reference to first header compression draft.
+ </t>
+ <t>
+ Added more formal description of frame lifecycle.
+ </t>
+ <t>
+ Moved END_STREAM (renamed from FINAL) back to <x:ref>HEADERS</x:ref>/<x:ref>DATA</x:ref>.
+ </t>
+ <t>
+ Removed HEADERS+PRIORITY, added optional priority to <x:ref>HEADERS</x:ref> frame.
+ </t>
+ <t>
+ Added <x:ref>PRIORITY</x:ref> frame.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-02" anchor="changes.since.draft-ietf-httpbis-http2-02">
+ <t>
+ Added continuations to frames carrying header blocks.
+ </t>
+ <t>
+ Replaced use of "session" with "connection" to avoid confusion with other HTTP stateful
+ concepts, like cookies.
+ </t>
+ <t>
+ Removed "message".
+ </t>
+ <t>
+ Switched to TLS ALPN from NPN.
+ </t>
+ <t>
+ Editorial changes.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-01" anchor="changes.since.draft-ietf-httpbis-http2-01">
+ <t>
+ Added IANA considerations section for frame types, error codes and settings.
+ </t>
+ <t>
+ Removed data frame compression.
+ </t>
+ <t>
+ Added <x:ref>PUSH_PROMISE</x:ref>.
+ </t>
+ <t>
+ Added globally applicable flags to framing.
+ </t>
+ <t>
+ Removed zlib-based header compression mechanism.
+ </t>
+ <t>
+ Updated references.
+ </t>
+ <t>
+ Clarified stream identifier reuse.
+ </t>
+ <t>
+ Removed CREDENTIALS frame and associated mechanisms.
+ </t>
+ <t>
+ Added advice against naive implementation of flow control.
+ </t>
+ <t>
+ Added session header section.
+ </t>
+ <t>
+ Restructured frame header. Removed distinction between data and control frames.
+ </t>
+ <t>
+ Altered flow control properties to include session-level limits.
+ </t>
+ <t>
+ Added note on cacheability of pushed resources and multiple tenant servers.
+ </t>
+ <t>
+ Changed protocol label form based on discussions.
+ </t>
+ </section>
+
+ <section title="Since draft-ietf-httpbis-http2-00" anchor="changes.since.draft-ietf-httpbis-http2-00">
+ <t>
+ Changed title throughout.
+ </t>
+ <t>
+ Removed section on Incompatibilities with SPDY draft#2.
+ </t>
+ <t>
+ Changed <x:ref>INTERNAL_ERROR</x:ref> on <x:ref>GOAWAY</x:ref> to have a value of 2 <eref
+ target="https://groups.google.com/forum/?fromgroups#!topic/spdy-dev/cfUef2gL3iU"/>.
+ </t>
+ <t>
+ Replaced abstract and introduction.
+ </t>
+ <t>
+ Added section on starting HTTP/2.0, including upgrade mechanism.
+ </t>
+ <t>
+ Removed unused references.
+ </t>
+ <t>
+ Added <xref target="fc-principles">flow control principles</xref> based on <eref
+ target="https://tools.ietf.org/html/draft-montenegro-httpbis-http2-fc-principles-01"/>.
+ </t>
+ </section>
+
+ <section title="Since draft-mbelshe-httpbis-spdy-00" anchor="changes.since.draft-mbelshe-httpbis-spdy-00">
+ <t>
+ Adopted as base for draft-ietf-httpbis-http2.
+ </t>
+ <t>
+ Updated authors/editors list.
+ </t>
+ <t>
+ Added status note.
+ </t>
+ </section>
+ </section>
+
+ </back>
+</rfc>
+<!--
+ vim:et:tw=100:sw=2:
+ -->
diff --git a/http2/transport.go b/http2/transport.go
new file mode 100644
index 0000000..4e2518e
--- /dev/null
+++ b/http2/transport.go
@@ -0,0 +1,553 @@
+// Copyright 2015 The Go Authors.
+// See https://go.googlesource.com/go/+/master/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://go.googlesource.com/go/+/master/LICENSE
+
+package http2
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "io"
+ "log"
+ "net"
+ "net/http"
+ "strconv"
+ "strings"
+ "sync"
+
+ "golang.org/x/net/http2/hpack"
+)
+
+type Transport struct {
+ Fallback http.RoundTripper
+
+ // TODO: remove this and make more general with a TLS dial hook, like http
+ InsecureTLSDial bool
+
+ connMu sync.Mutex
+ conns map[string][]*clientConn // key is host:port
+}
+
+type clientConn struct {
+ t *Transport
+ tconn *tls.Conn
+ tlsState *tls.ConnectionState
+ connKey []string // key(s) this connection is cached in, in t.conns
+
+ readerDone chan struct{} // closed on error
+ readerErr error // set before readerDone is closed
+ hdec *hpack.Decoder
+ nextRes *http.Response
+
+ mu sync.Mutex
+ closed bool
+ goAway *GoAwayFrame // if non-nil, the GoAwayFrame we received
+ streams map[uint32]*clientStream
+ nextStreamID uint32
+ bw *bufio.Writer
+ werr error // first write error that has occurred
+ br *bufio.Reader
+ fr *Framer
+ // Settings from peer:
+ maxFrameSize uint32
+ maxConcurrentStreams uint32
+ initialWindowSize uint32
+ hbuf bytes.Buffer // HPACK encoder writes into this
+ henc *hpack.Encoder
+}
+
+type clientStream struct {
+ ID uint32
+ resc chan resAndError
+ pw *io.PipeWriter
+ pr *io.PipeReader
+}
+
+type stickyErrWriter struct {
+ w io.Writer
+ err *error
+}
+
+func (sew stickyErrWriter) Write(p []byte) (n int, err error) {
+ if *sew.err != nil {
+ return 0, *sew.err
+ }
+ n, err = sew.w.Write(p)
+ *sew.err = err
+ return
+}
+
+func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
+ if req.URL.Scheme != "https" {
+ if t.Fallback == nil {
+ return nil, errors.New("http2: unsupported scheme and no Fallback")
+ }
+ return t.Fallback.RoundTrip(req)
+ }
+
+ host, port, err := net.SplitHostPort(req.URL.Host)
+ if err != nil {
+ host = req.URL.Host
+ port = "443"
+ }
+
+ for {
+ cc, err := t.getClientConn(host, port)
+ if err != nil {
+ return nil, err
+ }
+ res, err := cc.roundTrip(req)
+ if shouldRetryRequest(err) { // TODO: or clientconn is overloaded (too many outstanding requests)?
+ continue
+ }
+ if err != nil {
+ return nil, err
+ }
+ return res, nil
+ }
+}
+
+// CloseIdleConnections closes any connections which were previously
+// connected from previous requests but are now sitting idle.
+// It does not interrupt any connections currently in use.
+func (t *Transport) CloseIdleConnections() {
+ t.connMu.Lock()
+ defer t.connMu.Unlock()
+ for _, vv := range t.conns {
+ for _, cc := range vv {
+ cc.closeIfIdle()
+ }
+ }
+}
+
+var errClientConnClosed = errors.New("http2: client conn is closed")
+
+func shouldRetryRequest(err error) bool {
+ // TODO: or GOAWAY graceful shutdown stuff
+ return err == errClientConnClosed
+}
+
+func (t *Transport) removeClientConn(cc *clientConn) {
+ t.connMu.Lock()
+ defer t.connMu.Unlock()
+ for _, key := range cc.connKey {
+ vv, ok := t.conns[key]
+ if !ok {
+ continue
+ }
+ newList := filterOutClientConn(vv, cc)
+ if len(newList) > 0 {
+ t.conns[key] = newList
+ } else {
+ delete(t.conns, key)
+ }
+ }
+}
+
+func filterOutClientConn(in []*clientConn, exclude *clientConn) []*clientConn {
+ out := in[:0]
+ for _, v := range in {
+ if v != exclude {
+ out = append(out, v)
+ }
+ }
+ return out
+}
+
+func (t *Transport) getClientConn(host, port string) (*clientConn, error) {
+ t.connMu.Lock()
+ defer t.connMu.Unlock()
+
+ key := net.JoinHostPort(host, port)
+
+ for _, cc := range t.conns[key] {
+ if cc.canTakeNewRequest() {
+ return cc, nil
+ }
+ }
+ if t.conns == nil {
+ t.conns = make(map[string][]*clientConn)
+ }
+ cc, err := t.newClientConn(host, port, key)
+ if err != nil {
+ return nil, err
+ }
+ t.conns[key] = append(t.conns[key], cc)
+ return cc, nil
+}
+
+func (t *Transport) newClientConn(host, port, key string) (*clientConn, error) {
+ cfg := &tls.Config{
+ ServerName: host,
+ NextProtos: []string{NextProtoTLS},
+ InsecureSkipVerify: t.InsecureTLSDial,
+ }
+ tconn, err := tls.Dial("tcp", host+":"+port, cfg)
+ if err != nil {
+ return nil, err
+ }
+ if err := tconn.Handshake(); err != nil {
+ return nil, err
+ }
+ if !t.InsecureTLSDial {
+ if err := tconn.VerifyHostname(cfg.ServerName); err != nil {
+ return nil, err
+ }
+ }
+ state := tconn.ConnectionState()
+ if p := state.NegotiatedProtocol; p != NextProtoTLS {
+ // TODO(bradfitz): fall back to Fallback
+ return nil, fmt.Errorf("bad protocol: %v", p)
+ }
+ if !state.NegotiatedProtocolIsMutual {
+ return nil, errors.New("could not negotiate protocol mutually")
+ }
+ if _, err := tconn.Write(clientPreface); err != nil {
+ return nil, err
+ }
+
+ cc := &clientConn{
+ t: t,
+ tconn: tconn,
+ connKey: []string{key}, // TODO: cert's validated hostnames too
+ tlsState: &state,
+ readerDone: make(chan struct{}),
+ nextStreamID: 1,
+ maxFrameSize: 16 << 10, // spec default
+ initialWindowSize: 65535, // spec default
+ maxConcurrentStreams: 1000, // "infinite", per spec. 1000 seems good enough.
+ streams: make(map[uint32]*clientStream),
+ }
+ cc.bw = bufio.NewWriter(stickyErrWriter{tconn, &cc.werr})
+ cc.br = bufio.NewReader(tconn)
+ cc.fr = NewFramer(cc.bw, cc.br)
+ cc.henc = hpack.NewEncoder(&cc.hbuf)
+
+ cc.fr.WriteSettings()
+ // TODO: re-send more conn-level flow control tokens when server uses all these.
+ cc.fr.WriteWindowUpdate(0, 1<<30) // um, 0x7fffffff doesn't work to Google? it hangs?
+ cc.bw.Flush()
+ if cc.werr != nil {
+ return nil, cc.werr
+ }
+
+ // Read the obligatory SETTINGS frame
+ f, err := cc.fr.ReadFrame()
+ if err != nil {
+ return nil, err
+ }
+ sf, ok := f.(*SettingsFrame)
+ if !ok {
+ return nil, fmt.Errorf("expected settings frame, got: %T", f)
+ }
+ cc.fr.WriteSettingsAck()
+ cc.bw.Flush()
+
+ sf.ForeachSetting(func(s Setting) error {
+ switch s.ID {
+ case SettingMaxFrameSize:
+ cc.maxFrameSize = s.Val
+ case SettingMaxConcurrentStreams:
+ cc.maxConcurrentStreams = s.Val
+ case SettingInitialWindowSize:
+ cc.initialWindowSize = s.Val
+ default:
+ // TODO(bradfitz): handle more
+ log.Printf("Unhandled Setting: %v", s)
+ }
+ return nil
+ })
+ // TODO: figure out henc size
+ cc.hdec = hpack.NewDecoder(initialHeaderTableSize, cc.onNewHeaderField)
+
+ go cc.readLoop()
+ return cc, nil
+}
+
+func (cc *clientConn) setGoAway(f *GoAwayFrame) {
+ cc.mu.Lock()
+ defer cc.mu.Unlock()
+ cc.goAway = f
+}
+
+func (cc *clientConn) canTakeNewRequest() bool {
+ cc.mu.Lock()
+ defer cc.mu.Unlock()
+ return cc.goAway == nil &&
+ int64(len(cc.streams)+1) < int64(cc.maxConcurrentStreams) &&
+ cc.nextStreamID < 2147483647
+}
+
+func (cc *clientConn) closeIfIdle() {
+ cc.mu.Lock()
+ if len(cc.streams) > 0 {
+ cc.mu.Unlock()
+ return
+ }
+ cc.closed = true
+ // TODO: do clients send GOAWAY too? maybe? Just Close:
+ cc.mu.Unlock()
+
+ cc.tconn.Close()
+}
+
+func (cc *clientConn) roundTrip(req *http.Request) (*http.Response, error) {
+ cc.mu.Lock()
+
+ if cc.closed {
+ cc.mu.Unlock()
+ return nil, errClientConnClosed
+ }
+
+ cs := cc.newStream()
+ hasBody := false // TODO
+
+ // we send: HEADERS[+CONTINUATION] + (DATA?)
+ hdrs := cc.encodeHeaders(req)
+ first := true
+ for len(hdrs) > 0 {
+ chunk := hdrs
+ if len(chunk) > int(cc.maxFrameSize) {
+ chunk = chunk[:cc.maxFrameSize]
+ }
+ hdrs = hdrs[len(chunk):]
+ endHeaders := len(hdrs) == 0
+ if first {
+ cc.fr.WriteHeaders(HeadersFrameParam{
+ StreamID: cs.ID,
+ BlockFragment: chunk,
+ EndStream: !hasBody,
+ EndHeaders: endHeaders,
+ })
+ first = false
+ } else {
+ cc.fr.WriteContinuation(cs.ID, endHeaders, chunk)
+ }
+ }
+ cc.bw.Flush()
+ werr := cc.werr
+ cc.mu.Unlock()
+
+ if hasBody {
+ // TODO: write data. and it should probably be interleaved:
+ // go ... io.Copy(dataFrameWriter{cc, cs, ...}, req.Body) ... etc
+ }
+
+ if werr != nil {
+ return nil, werr
+ }
+
+ re := <-cs.resc
+ if re.err != nil {
+ return nil, re.err
+ }
+ res := re.res
+ res.Request = req
+ res.TLS = cc.tlsState
+ return res, nil
+}
+
+// requires cc.mu be held.
+func (cc *clientConn) encodeHeaders(req *http.Request) []byte {
+ cc.hbuf.Reset()
+
+ // TODO(bradfitz): figure out :authority-vs-Host stuff between http2 and Go
+ host := req.Host
+ if host == "" {
+ host = req.URL.Host
+ }
+
+ path := req.URL.Path
+ if path == "" {
+ path = "/"
+ }
+
+ cc.writeHeader(":authority", host) // probably not right for all sites
+ cc.writeHeader(":method", req.Method)
+ cc.writeHeader(":path", path)
+ cc.writeHeader(":scheme", "https")
+
+ for k, vv := range req.Header {
+ lowKey := strings.ToLower(k)
+ if lowKey == "host" {
+ continue
+ }
+ for _, v := range vv {
+ cc.writeHeader(lowKey, v)
+ }
+ }
+ return cc.hbuf.Bytes()
+}
+
+func (cc *clientConn) writeHeader(name, value string) {
+ log.Printf("sending %q = %q", name, value)
+ cc.henc.WriteField(hpack.HeaderField{Name: name, Value: value})
+}
+
+type resAndError struct {
+ res *http.Response
+ err error
+}
+
+// requires cc.mu be held.
+func (cc *clientConn) newStream() *clientStream {
+ cs := &clientStream{
+ ID: cc.nextStreamID,
+ resc: make(chan resAndError, 1),
+ }
+ cc.nextStreamID += 2
+ cc.streams[cs.ID] = cs
+ return cs
+}
+
+func (cc *clientConn) streamByID(id uint32, andRemove bool) *clientStream {
+ cc.mu.Lock()
+ defer cc.mu.Unlock()
+ cs := cc.streams[id]
+ if andRemove {
+ delete(cc.streams, id)
+ }
+ return cs
+}
+
+// runs in its own goroutine.
+func (cc *clientConn) readLoop() {
+ defer cc.t.removeClientConn(cc)
+ defer close(cc.readerDone)
+
+ activeRes := map[uint32]*clientStream{} // keyed by streamID
+ // Close any response bodies if the server closes prematurely.
+ // TODO: also do this if we've written the headers but not
+ // gotten a response yet.
+ defer func() {
+ err := cc.readerErr
+ if err == io.EOF {
+ err = io.ErrUnexpectedEOF
+ }
+ for _, cs := range activeRes {
+ cs.pw.CloseWithError(err)
+ }
+ }()
+
+ // continueStreamID is the stream ID we're waiting for
+ // continuation frames for.
+ var continueStreamID uint32
+
+ for {
+ f, err := cc.fr.ReadFrame()
+ if err != nil {
+ cc.readerErr = err
+ return
+ }
+ log.Printf("Transport received %v: %#v", f.Header(), f)
+
+ streamID := f.Header().StreamID
+
+ _, isContinue := f.(*ContinuationFrame)
+ if isContinue {
+ if streamID != continueStreamID {
+ log.Printf("Protocol violation: got CONTINUATION with id %d; want %d", streamID, continueStreamID)
+ cc.readerErr = ConnectionError(ErrCodeProtocol)
+ return
+ }
+ } else if continueStreamID != 0 {
+ // Continue frames need to be adjacent in the stream
+ // and we were in the middle of headers.
+ log.Printf("Protocol violation: got %T for stream %d, want CONTINUATION for %d", f, streamID, continueStreamID)
+ cc.readerErr = ConnectionError(ErrCodeProtocol)
+ return
+ }
+
+ if streamID%2 == 0 {
+ // Ignore streams pushed from the server for now.
+ // These always have an even stream id.
+ continue
+ }
+ streamEnded := false
+ if ff, ok := f.(streamEnder); ok {
+ streamEnded = ff.StreamEnded()
+ }
+
+ cs := cc.streamByID(streamID, streamEnded)
+ if cs == nil {
+ log.Printf("Received frame for untracked stream ID %d", streamID)
+ continue
+ }
+
+ switch f := f.(type) {
+ case *HeadersFrame:
+ cc.nextRes = &http.Response{
+ Proto: "HTTP/2.0",
+ ProtoMajor: 2,
+ Header: make(http.Header),
+ }
+ cs.pr, cs.pw = io.Pipe()
+ cc.hdec.Write(f.HeaderBlockFragment())
+ case *ContinuationFrame:
+ cc.hdec.Write(f.HeaderBlockFragment())
+ case *DataFrame:
+ log.Printf("DATA: %q", f.Data())
+ cs.pw.Write(f.Data())
+ case *GoAwayFrame:
+ cc.t.removeClientConn(cc)
+ if f.ErrCode != 0 {
+ // TODO: deal with GOAWAY more. particularly the error code
+ log.Printf("transport got GOAWAY with error code = %v", f.ErrCode)
+ }
+ cc.setGoAway(f)
+ default:
+ log.Printf("Transport: unhandled response frame type %T", f)
+ }
+ headersEnded := false
+ if he, ok := f.(headersEnder); ok {
+ headersEnded = he.HeadersEnded()
+ if headersEnded {
+ continueStreamID = 0
+ } else {
+ continueStreamID = streamID
+ }
+ }
+
+ if streamEnded {
+ cs.pw.Close()
+ delete(activeRes, streamID)
+ }
+ if headersEnded {
+ if cs == nil {
+ panic("couldn't find stream") // TODO be graceful
+ }
+ // TODO: set the Body to one which notes the
+ // Close and also sends the server a
+ // RST_STREAM
+ cc.nextRes.Body = cs.pr
+ res := cc.nextRes
+ activeRes[streamID] = cs
+ cs.resc <- resAndError{res: res}
+ }
+ }
+}
+
+func (cc *clientConn) onNewHeaderField(f hpack.HeaderField) {
+ // TODO: verifiy pseudo headers come before non-pseudo headers
+ // TODO: verifiy the status is set
+ log.Printf("Header field: %+v", f)
+ if f.Name == ":status" {
+ code, err := strconv.Atoi(f.Value)
+ if err != nil {
+ panic("TODO: be graceful")
+ }
+ cc.nextRes.Status = f.Value + " " + http.StatusText(code)
+ cc.nextRes.StatusCode = code
+ return
+ }
+ if strings.HasPrefix(f.Name, ":") {
+ // "Endpoints MUST NOT generate pseudo-header fields other than those defined in this document."
+ // TODO: treat as invalid?
+ return
+ }
+ cc.nextRes.Header.Add(http.CanonicalHeaderKey(f.Name), f.Value)
+}
diff --git a/http2/transport_test.go b/http2/transport_test.go
new file mode 100644
index 0000000..d752da1
--- /dev/null
+++ b/http2/transport_test.go
@@ -0,0 +1,168 @@
+// Copyright 2015 The Go Authors.
+// See https://go.googlesource.com/go/+/master/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://go.googlesource.com/go/+/master/LICENSE
+
+package http2
+
+import (
+ "flag"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+)
+
+var (
+ extNet = flag.Bool("extnet", false, "do external network tests")
+ transportHost = flag.String("transporthost", "http2.golang.org", "hostname to use for TestTransport")
+ insecure = flag.Bool("insecure", false, "insecure TLS dials")
+)
+
+func TestTransportExternal(t *testing.T) {
+ if !*extNet {
+ t.Skip("skipping external network test")
+ }
+ req, _ := http.NewRequest("GET", "https://"+*transportHost+"/", nil)
+ rt := &Transport{
+ InsecureTLSDial: *insecure,
+ }
+ res, err := rt.RoundTrip(req)
+ if err != nil {
+ t.Fatalf("%v", err)
+ }
+ res.Write(os.Stdout)
+}
+
+func TestTransport(t *testing.T) {
+ const body = "sup"
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, body)
+ })
+ defer st.Close()
+
+ tr := &Transport{InsecureTLSDial: true}
+ defer tr.CloseIdleConnections()
+
+ req, err := http.NewRequest("GET", st.ts.URL, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ res, err := tr.RoundTrip(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer res.Body.Close()
+
+ t.Logf("Got res: %+v", res)
+ if g, w := res.StatusCode, 200; g != w {
+ t.Errorf("StatusCode = %v; want %v", g, w)
+ }
+ if g, w := res.Status, "200 OK"; g != w {
+ t.Errorf("Status = %q; want %q", g, w)
+ }
+ wantHeader := http.Header{
+ "Content-Length": []string{"3"},
+ "Content-Type": []string{"text/plain; charset=utf-8"},
+ }
+ if !reflect.DeepEqual(res.Header, wantHeader) {
+ t.Errorf("res Header = %v; want %v", res.Header, wantHeader)
+ }
+ if res.Request != req {
+ t.Errorf("Response.Request = %p; want %p", res.Request, req)
+ }
+ if res.TLS == nil {
+ t.Error("Response.TLS = nil; want non-nil")
+ }
+ slurp, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Errorf("Body read: %v", err)
+ } else if string(slurp) != body {
+ t.Errorf("Body = %q; want %q", slurp, body)
+ }
+
+}
+
+func TestTransportReusesConns(t *testing.T) {
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, r.RemoteAddr)
+ }, optOnlyServer)
+ defer st.Close()
+ tr := &Transport{InsecureTLSDial: true}
+ defer tr.CloseIdleConnections()
+ get := func() string {
+ req, err := http.NewRequest("GET", st.ts.URL, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ res, err := tr.RoundTrip(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer res.Body.Close()
+ slurp, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ t.Fatalf("Body read: %v", err)
+ }
+ addr := strings.TrimSpace(string(slurp))
+ if addr == "" {
+ t.Fatalf("didn't get an addr in response")
+ }
+ return addr
+ }
+ first := get()
+ second := get()
+ if first != second {
+ t.Errorf("first and second responses were on different connections: %q vs %q", first, second)
+ }
+}
+
+func TestTransportAbortClosesPipes(t *testing.T) {
+ shutdown := make(chan struct{})
+ st := newServerTester(t,
+ func(w http.ResponseWriter, r *http.Request) {
+ w.(http.Flusher).Flush()
+ <-shutdown
+ },
+ optOnlyServer,
+ )
+ defer st.Close()
+ defer close(shutdown) // we must shutdown before st.Close() to avoid hanging
+
+ done := make(chan struct{})
+ requestMade := make(chan struct{})
+ go func() {
+ defer close(done)
+ tr := &Transport{
+ InsecureTLSDial: true,
+ }
+ req, err := http.NewRequest("GET", st.ts.URL, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ res, err := tr.RoundTrip(req)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer res.Body.Close()
+ close(requestMade)
+ _, err = ioutil.ReadAll(res.Body)
+ if err == nil {
+ t.Error("expected error from res.Body.Read")
+ }
+ }()
+
+ <-requestMade
+ // Now force the serve loop to end, via closing the connection.
+ st.closeConn()
+ // deadlock? that's a bug.
+ select {
+ case <-done:
+ case <-time.After(3 * time.Second):
+ t.Fatal("timeout")
+ }
+}
diff --git a/http2/write.go b/http2/write.go
new file mode 100644
index 0000000..02f0743
--- /dev/null
+++ b/http2/write.go
@@ -0,0 +1,204 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "bytes"
+ "fmt"
+ "net/http"
+ "time"
+
+ "golang.org/x/net/http2/hpack"
+)
+
+// writeFramer is implemented by any type that is used to write frames.
+type writeFramer interface {
+ writeFrame(writeContext) error
+}
+
+// writeContext is the interface needed by the various frame writer
+// types below. All the writeFrame methods below are scheduled via the
+// frame writing scheduler (see writeScheduler in writesched.go).
+//
+// This interface is implemented by *serverConn.
+// TODO: use it from the client code too, once it exists.
+type writeContext interface {
+ Framer() *Framer
+ Flush() error
+ CloseConn() error
+ // HeaderEncoder returns an HPACK encoder that writes to the
+ // returned buffer.
+ HeaderEncoder() (*hpack.Encoder, *bytes.Buffer)
+}
+
+// endsStream reports whether the given frame writer w will locally
+// close the stream.
+func endsStream(w writeFramer) bool {
+ switch v := w.(type) {
+ case *writeData:
+ return v.endStream
+ case *writeResHeaders:
+ return v.endStream
+ }
+ return false
+}
+
+type flushFrameWriter struct{}
+
+func (flushFrameWriter) writeFrame(ctx writeContext) error {
+ return ctx.Flush()
+}
+
+type writeSettings []Setting
+
+func (s writeSettings) writeFrame(ctx writeContext) error {
+ return ctx.Framer().WriteSettings([]Setting(s)...)
+}
+
+type writeGoAway struct {
+ maxStreamID uint32
+ code ErrCode
+}
+
+func (p *writeGoAway) writeFrame(ctx writeContext) error {
+ err := ctx.Framer().WriteGoAway(p.maxStreamID, p.code, nil)
+ if p.code != 0 {
+ ctx.Flush() // ignore error: we're hanging up on them anyway
+ time.Sleep(50 * time.Millisecond)
+ ctx.CloseConn()
+ }
+ return err
+}
+
+type writeData struct {
+ streamID uint32
+ p []byte
+ endStream bool
+}
+
+func (w *writeData) String() string {
+ return fmt.Sprintf("writeData(stream=%d, p=%d, endStream=%v)", w.streamID, len(w.p), w.endStream)
+}
+
+func (w *writeData) writeFrame(ctx writeContext) error {
+ return ctx.Framer().WriteData(w.streamID, w.endStream, w.p)
+}
+
+func (se StreamError) writeFrame(ctx writeContext) error {
+ return ctx.Framer().WriteRSTStream(se.StreamID, se.Code)
+}
+
+type writePingAck struct{ pf *PingFrame }
+
+func (w writePingAck) writeFrame(ctx writeContext) error {
+ return ctx.Framer().WritePing(true, w.pf.Data)
+}
+
+type writeSettingsAck struct{}
+
+func (writeSettingsAck) writeFrame(ctx writeContext) error {
+ return ctx.Framer().WriteSettingsAck()
+}
+
+// writeResHeaders is a request to write a HEADERS and 0+ CONTINUATION frames
+// for HTTP response headers from a server handler.
+type writeResHeaders struct {
+ streamID uint32
+ httpResCode int
+ h http.Header // may be nil
+ endStream bool
+
+ contentType string
+ contentLength string
+}
+
+func (w *writeResHeaders) writeFrame(ctx writeContext) error {
+ enc, buf := ctx.HeaderEncoder()
+ buf.Reset()
+ enc.WriteField(hpack.HeaderField{Name: ":status", Value: httpCodeString(w.httpResCode)})
+ for k, vv := range w.h {
+ k = lowerHeader(k)
+ for _, v := range vv {
+ // TODO: more of "8.1.2.2 Connection-Specific Header Fields"
+ if k == "transfer-encoding" && v != "trailers" {
+ continue
+ }
+ enc.WriteField(hpack.HeaderField{Name: k, Value: v})
+ }
+ }
+ if w.contentType != "" {
+ enc.WriteField(hpack.HeaderField{Name: "content-type", Value: w.contentType})
+ }
+ if w.contentLength != "" {
+ enc.WriteField(hpack.HeaderField{Name: "content-length", Value: w.contentLength})
+ }
+
+ headerBlock := buf.Bytes()
+ if len(headerBlock) == 0 {
+ panic("unexpected empty hpack")
+ }
+
+ // For now we're lazy and just pick the minimum MAX_FRAME_SIZE
+ // that all peers must support (16KB). Later we could care
+ // more and send larger frames if the peer advertised it, but
+ // there's little point. Most headers are small anyway (so we
+ // generally won't have CONTINUATION frames), and extra frames
+ // only waste 9 bytes anyway.
+ const maxFrameSize = 16384
+
+ first := true
+ for len(headerBlock) > 0 {
+ frag := headerBlock
+ if len(frag) > maxFrameSize {
+ frag = frag[:maxFrameSize]
+ }
+ headerBlock = headerBlock[len(frag):]
+ endHeaders := len(headerBlock) == 0
+ var err error
+ if first {
+ first = false
+ err = ctx.Framer().WriteHeaders(HeadersFrameParam{
+ StreamID: w.streamID,
+ BlockFragment: frag,
+ EndStream: w.endStream,
+ EndHeaders: endHeaders,
+ })
+ } else {
+ err = ctx.Framer().WriteContinuation(w.streamID, endHeaders, frag)
+ }
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+type write100ContinueHeadersFrame struct {
+ streamID uint32
+}
+
+func (w write100ContinueHeadersFrame) writeFrame(ctx writeContext) error {
+ enc, buf := ctx.HeaderEncoder()
+ buf.Reset()
+ enc.WriteField(hpack.HeaderField{Name: ":status", Value: "100"})
+ return ctx.Framer().WriteHeaders(HeadersFrameParam{
+ StreamID: w.streamID,
+ BlockFragment: buf.Bytes(),
+ EndStream: false,
+ EndHeaders: true,
+ })
+}
+
+type writeWindowUpdate struct {
+ streamID uint32 // or 0 for conn-level
+ n uint32
+}
+
+func (wu writeWindowUpdate) writeFrame(ctx writeContext) error {
+ return ctx.Framer().WriteWindowUpdate(wu.streamID, wu.n)
+}
diff --git a/http2/writesched.go b/http2/writesched.go
new file mode 100644
index 0000000..0e1b748
--- /dev/null
+++ b/http2/writesched.go
@@ -0,0 +1,286 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import "fmt"
+
+// frameWriteMsg is a request to write a frame.
+type frameWriteMsg struct {
+ // write is the interface value that does the writing, once the
+ // writeScheduler (below) has decided to select this frame
+ // to write. The write functions are all defined in write.go.
+ write writeFramer
+
+ stream *stream // used for prioritization. nil for non-stream frames.
+
+ // done, if non-nil, must be a buffered channel with space for
+ // 1 message and is sent the return value from write (or an
+ // earlier error) when the frame has been written.
+ done chan error
+}
+
+// for debugging only:
+func (wm frameWriteMsg) String() string {
+ var streamID uint32
+ if wm.stream != nil {
+ streamID = wm.stream.id
+ }
+ var des string
+ if s, ok := wm.write.(fmt.Stringer); ok {
+ des = s.String()
+ } else {
+ des = fmt.Sprintf("%T", wm.write)
+ }
+ return fmt.Sprintf("[frameWriteMsg stream=%d, ch=%v, type: %v]", streamID, wm.done != nil, des)
+}
+
+// writeScheduler tracks pending frames to write, priorities, and decides
+// the next one to use. It is not thread-safe.
+type writeScheduler struct {
+ // zero are frames not associated with a specific stream.
+ // They're sent before any stream-specific freams.
+ zero writeQueue
+
+ // maxFrameSize is the maximum size of a DATA frame
+ // we'll write. Must be non-zero and between 16K-16M.
+ maxFrameSize uint32
+
+ // sq contains the stream-specific queues, keyed by stream ID.
+ // when a stream is idle, it's deleted from the map.
+ sq map[uint32]*writeQueue
+
+ // canSend is a slice of memory that's reused between frame
+ // scheduling decisions to hold the list of writeQueues (from sq)
+ // which have enough flow control data to send. After canSend is
+ // built, the best is selected.
+ canSend []*writeQueue
+
+ // pool of empty queues for reuse.
+ queuePool []*writeQueue
+}
+
+func (ws *writeScheduler) putEmptyQueue(q *writeQueue) {
+ if len(q.s) != 0 {
+ panic("queue must be empty")
+ }
+ ws.queuePool = append(ws.queuePool, q)
+}
+
+func (ws *writeScheduler) getEmptyQueue() *writeQueue {
+ ln := len(ws.queuePool)
+ if ln == 0 {
+ return new(writeQueue)
+ }
+ q := ws.queuePool[ln-1]
+ ws.queuePool = ws.queuePool[:ln-1]
+ return q
+}
+
+func (ws *writeScheduler) empty() bool { return ws.zero.empty() && len(ws.sq) == 0 }
+
+func (ws *writeScheduler) add(wm frameWriteMsg) {
+ st := wm.stream
+ if st == nil {
+ ws.zero.push(wm)
+ } else {
+ ws.streamQueue(st.id).push(wm)
+ }
+}
+
+func (ws *writeScheduler) streamQueue(streamID uint32) *writeQueue {
+ if q, ok := ws.sq[streamID]; ok {
+ return q
+ }
+ if ws.sq == nil {
+ ws.sq = make(map[uint32]*writeQueue)
+ }
+ q := ws.getEmptyQueue()
+ ws.sq[streamID] = q
+ return q
+}
+
+// take returns the most important frame to write and removes it from the scheduler.
+// It is illegal to call this if the scheduler is empty or if there are no connection-level
+// flow control bytes available.
+func (ws *writeScheduler) take() (wm frameWriteMsg, ok bool) {
+ if ws.maxFrameSize == 0 {
+ panic("internal error: ws.maxFrameSize not initialized or invalid")
+ }
+
+ // If there any frames not associated with streams, prefer those first.
+ // These are usually SETTINGS, etc.
+ if !ws.zero.empty() {
+ return ws.zero.shift(), true
+ }
+ if len(ws.sq) == 0 {
+ return
+ }
+
+ // Next, prioritize frames on streams that aren't DATA frames (no cost).
+ for id, q := range ws.sq {
+ if q.firstIsNoCost() {
+ return ws.takeFrom(id, q)
+ }
+ }
+
+ // Now, all that remains are DATA frames with non-zero bytes to
+ // send. So pick the best one.
+ if len(ws.canSend) != 0 {
+ panic("should be empty")
+ }
+ for _, q := range ws.sq {
+ if n := ws.streamWritableBytes(q); n > 0 {
+ ws.canSend = append(ws.canSend, q)
+ }
+ }
+ if len(ws.canSend) == 0 {
+ return
+ }
+ defer ws.zeroCanSend()
+
+ // TODO: find the best queue
+ q := ws.canSend[0]
+
+ return ws.takeFrom(q.streamID(), q)
+}
+
+// zeroCanSend is defered from take.
+func (ws *writeScheduler) zeroCanSend() {
+ for i := range ws.canSend {
+ ws.canSend[i] = nil
+ }
+ ws.canSend = ws.canSend[:0]
+}
+
+// streamWritableBytes returns the number of DATA bytes we could write
+// from the given queue's stream, if this stream/queue were
+// selected. It is an error to call this if q's head isn't a
+// *writeData.
+func (ws *writeScheduler) streamWritableBytes(q *writeQueue) int32 {
+ wm := q.head()
+ ret := wm.stream.flow.available() // max we can write
+ if ret == 0 {
+ return 0
+ }
+ if int32(ws.maxFrameSize) < ret {
+ ret = int32(ws.maxFrameSize)
+ }
+ if ret == 0 {
+ panic("internal error: ws.maxFrameSize not initialized or invalid")
+ }
+ wd := wm.write.(*writeData)
+ if len(wd.p) < int(ret) {
+ ret = int32(len(wd.p))
+ }
+ return ret
+}
+
+func (ws *writeScheduler) takeFrom(id uint32, q *writeQueue) (wm frameWriteMsg, ok bool) {
+ wm = q.head()
+ // If the first item in this queue costs flow control tokens
+ // and we don't have enough, write as much as we can.
+ if wd, ok := wm.write.(*writeData); ok && len(wd.p) > 0 {
+ allowed := wm.stream.flow.available() // max we can write
+ if allowed == 0 {
+ // No quota available. Caller can try the next stream.
+ return frameWriteMsg{}, false
+ }
+ if int32(ws.maxFrameSize) < allowed {
+ allowed = int32(ws.maxFrameSize)
+ }
+ // TODO: further restrict the allowed size, because even if
+ // the peer says it's okay to write 16MB data frames, we might
+ // want to write smaller ones to properly weight competing
+ // streams' priorities.
+
+ if len(wd.p) > int(allowed) {
+ wm.stream.flow.take(allowed)
+ chunk := wd.p[:allowed]
+ wd.p = wd.p[allowed:]
+ // Make up a new write message of a valid size, rather
+ // than shifting one off the queue.
+ return frameWriteMsg{
+ stream: wm.stream,
+ write: &writeData{
+ streamID: wd.streamID,
+ p: chunk,
+ // even if the original had endStream set, there
+ // arebytes remaining because len(wd.p) > allowed,
+ // so we know endStream is false:
+ endStream: false,
+ },
+ // our caller is blocking on the final DATA frame, not
+ // these intermediates, so no need to wait:
+ done: nil,
+ }, true
+ }
+ wm.stream.flow.take(int32(len(wd.p)))
+ }
+
+ q.shift()
+ if q.empty() {
+ ws.putEmptyQueue(q)
+ delete(ws.sq, id)
+ }
+ return wm, true
+}
+
+func (ws *writeScheduler) forgetStream(id uint32) {
+ q, ok := ws.sq[id]
+ if !ok {
+ return
+ }
+ delete(ws.sq, id)
+
+ // But keep it for others later.
+ for i := range q.s {
+ q.s[i] = frameWriteMsg{}
+ }
+ q.s = q.s[:0]
+ ws.putEmptyQueue(q)
+}
+
+type writeQueue struct {
+ s []frameWriteMsg
+}
+
+// streamID returns the stream ID for a non-empty stream-specific queue.
+func (q *writeQueue) streamID() uint32 { return q.s[0].stream.id }
+
+func (q *writeQueue) empty() bool { return len(q.s) == 0 }
+
+func (q *writeQueue) push(wm frameWriteMsg) {
+ q.s = append(q.s, wm)
+}
+
+// head returns the next item that would be removed by shift.
+func (q *writeQueue) head() frameWriteMsg {
+ if len(q.s) == 0 {
+ panic("invalid use of queue")
+ }
+ return q.s[0]
+}
+
+func (q *writeQueue) shift() frameWriteMsg {
+ if len(q.s) == 0 {
+ panic("invalid use of queue")
+ }
+ wm := q.s[0]
+ // TODO: less copy-happy queue.
+ copy(q.s, q.s[1:])
+ q.s[len(q.s)-1] = frameWriteMsg{}
+ q.s = q.s[:len(q.s)-1]
+ return wm
+}
+
+func (q *writeQueue) firstIsNoCost() bool {
+ if df, ok := q.s[0].write.(*writeData); ok {
+ return len(df.p) == 0
+ }
+ return true
+}
diff --git a/http2/z_spec_test.go b/http2/z_spec_test.go
new file mode 100644
index 0000000..07b53e3
--- /dev/null
+++ b/http2/z_spec_test.go
@@ -0,0 +1,357 @@
+// Copyright 2014 The Go Authors.
+// See https://code.google.com/p/go/source/browse/CONTRIBUTORS
+// Licensed under the same terms as Go itself:
+// https://code.google.com/p/go/source/browse/LICENSE
+
+package http2
+
+import (
+ "bytes"
+ "encoding/xml"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "reflect"
+ "regexp"
+ "sort"
+ "strconv"
+ "strings"
+ "sync"
+ "testing"
+)
+
+var coverSpec = flag.Bool("coverspec", false, "Run spec coverage tests")
+
+// The global map of sentence coverage for the http2 spec.
+var defaultSpecCoverage specCoverage
+
+var loadSpecOnce sync.Once
+
+func loadSpec() {
+ if f, err := os.Open("testdata/draft-ietf-httpbis-http2.xml"); err != nil {
+ panic(err)
+ } else {
+ defaultSpecCoverage = readSpecCov(f)
+ f.Close()
+ }
+}
+
+// covers marks all sentences for section sec in defaultSpecCoverage. Sentences not
+// "covered" will be included in report outputed by TestSpecCoverage.
+func covers(sec, sentences string) {
+ loadSpecOnce.Do(loadSpec)
+ defaultSpecCoverage.cover(sec, sentences)
+}
+
+type specPart struct {
+ section string
+ sentence string
+}
+
+func (ss specPart) Less(oo specPart) bool {
+ atoi := func(s string) int {
+ n, err := strconv.Atoi(s)
+ if err != nil {
+ panic(err)
+ }
+ return n
+ }
+ a := strings.Split(ss.section, ".")
+ b := strings.Split(oo.section, ".")
+ for len(a) > 0 {
+ if len(b) == 0 {
+ return false
+ }
+ x, y := atoi(a[0]), atoi(b[0])
+ if x == y {
+ a, b = a[1:], b[1:]
+ continue
+ }
+ return x < y
+ }
+ if len(b) > 0 {
+ return true
+ }
+ return false
+}
+
+type bySpecSection []specPart
+
+func (a bySpecSection) Len() int { return len(a) }
+func (a bySpecSection) Less(i, j int) bool { return a[i].Less(a[j]) }
+func (a bySpecSection) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
+
+type specCoverage struct {
+ coverage map[specPart]bool
+ d *xml.Decoder
+}
+
+func joinSection(sec []int) string {
+ s := fmt.Sprintf("%d", sec[0])
+ for _, n := range sec[1:] {
+ s = fmt.Sprintf("%s.%d", s, n)
+ }
+ return s
+}
+
+func (sc specCoverage) readSection(sec []int) {
+ var (
+ buf = new(bytes.Buffer)
+ sub = 0
+ )
+ for {
+ tk, err := sc.d.Token()
+ if err != nil {
+ if err == io.EOF {
+ return
+ }
+ panic(err)
+ }
+ switch v := tk.(type) {
+ case xml.StartElement:
+ if skipElement(v) {
+ if err := sc.d.Skip(); err != nil {
+ panic(err)
+ }
+ if v.Name.Local == "section" {
+ sub++
+ }
+ break
+ }
+ switch v.Name.Local {
+ case "section":
+ sub++
+ sc.readSection(append(sec, sub))
+ case "xref":
+ buf.Write(sc.readXRef(v))
+ }
+ case xml.CharData:
+ if len(sec) == 0 {
+ break
+ }
+ buf.Write(v)
+ case xml.EndElement:
+ if v.Name.Local == "section" {
+ sc.addSentences(joinSection(sec), buf.String())
+ return
+ }
+ }
+ }
+}
+
+func (sc specCoverage) readXRef(se xml.StartElement) []byte {
+ var b []byte
+ for {
+ tk, err := sc.d.Token()
+ if err != nil {
+ panic(err)
+ }
+ switch v := tk.(type) {
+ case xml.CharData:
+ if b != nil {
+ panic("unexpected CharData")
+ }
+ b = []byte(string(v))
+ case xml.EndElement:
+ if v.Name.Local != "xref" {
+ panic("expected </xref>")
+ }
+ if b != nil {
+ return b
+ }
+ sig := attrSig(se)
+ switch sig {
+ case "target":
+ return []byte(fmt.Sprintf("[%s]", attrValue(se, "target")))
+ case "fmt-of,rel,target", "fmt-,,rel,target":
+ return []byte(fmt.Sprintf("[%s, %s]", attrValue(se, "target"), attrValue(se, "rel")))
+ case "fmt-of,sec,target", "fmt-,,sec,target":
+ return []byte(fmt.Sprintf("[section %s of %s]", attrValue(se, "sec"), attrValue(se, "target")))
+ case "fmt-of,rel,sec,target":
+ return []byte(fmt.Sprintf("[section %s of %s, %s]", attrValue(se, "sec"), attrValue(se, "target"), attrValue(se, "rel")))
+ default:
+ panic(fmt.Sprintf("unknown attribute signature %q in %#v", sig, fmt.Sprintf("%#v", se)))
+ }
+ default:
+ panic(fmt.Sprintf("unexpected tag %q", v))
+ }
+ }
+}
+
+var skipAnchor = map[string]bool{
+ "intro": true,
+ "Overview": true,
+}
+
+var skipTitle = map[string]bool{
+ "Acknowledgements": true,
+ "Change Log": true,
+ "Document Organization": true,
+ "Conventions and Terminology": true,
+}
+
+func skipElement(s xml.StartElement) bool {
+ switch s.Name.Local {
+ case "artwork":
+ return true
+ case "section":
+ for _, attr := range s.Attr {
+ switch attr.Name.Local {
+ case "anchor":
+ if skipAnchor[attr.Value] || strings.HasPrefix(attr.Value, "changes.since.") {
+ return true
+ }
+ case "title":
+ if skipTitle[attr.Value] {
+ return true
+ }
+ }
+ }
+ }
+ return false
+}
+
+func readSpecCov(r io.Reader) specCoverage {
+ sc := specCoverage{
+ coverage: map[specPart]bool{},
+ d: xml.NewDecoder(r)}
+ sc.readSection(nil)
+ return sc
+}
+
+func (sc specCoverage) addSentences(sec string, sentence string) {
+ for _, s := range parseSentences(sentence) {
+ sc.coverage[specPart{sec, s}] = false
+ }
+}
+
+func (sc specCoverage) cover(sec string, sentence string) {
+ for _, s := range parseSentences(sentence) {
+ p := specPart{sec, s}
+ if _, ok := sc.coverage[p]; !ok {
+ panic(fmt.Sprintf("Not found in spec: %q, %q", sec, s))
+ }
+ sc.coverage[specPart{sec, s}] = true
+ }
+
+}
+
+var whitespaceRx = regexp.MustCompile(`\s+`)
+
+func parseSentences(sens string) []string {
+ sens = strings.TrimSpace(sens)
+ if sens == "" {
+ return nil
+ }
+ ss := strings.Split(whitespaceRx.ReplaceAllString(sens, " "), ". ")
+ for i, s := range ss {
+ s = strings.TrimSpace(s)
+ if !strings.HasSuffix(s, ".") {
+ s += "."
+ }
+ ss[i] = s
+ }
+ return ss
+}
+
+func TestSpecParseSentences(t *testing.T) {
+ tests := []struct {
+ ss string
+ want []string
+ }{
+ {"Sentence 1. Sentence 2.",
+ []string{
+ "Sentence 1.",
+ "Sentence 2.",
+ }},
+ {"Sentence 1. \nSentence 2.\tSentence 3.",
+ []string{
+ "Sentence 1.",
+ "Sentence 2.",
+ "Sentence 3.",
+ }},
+ }
+
+ for i, tt := range tests {
+ got := parseSentences(tt.ss)
+ if !reflect.DeepEqual(got, tt.want) {
+ t.Errorf("%d: got = %q, want %q", i, got, tt.want)
+ }
+ }
+}
+
+func TestSpecCoverage(t *testing.T) {
+ if !*coverSpec {
+ t.Skip()
+ }
+
+ loadSpecOnce.Do(loadSpec)
+
+ var (
+ list []specPart
+ cv = defaultSpecCoverage.coverage
+ total = len(cv)
+ complete = 0
+ )
+
+ for sp, touched := range defaultSpecCoverage.coverage {
+ if touched {
+ complete++
+ } else {
+ list = append(list, sp)
+ }
+ }
+ sort.Stable(bySpecSection(list))
+
+ if testing.Short() && len(list) > 5 {
+ list = list[:5]
+ }
+
+ for _, p := range list {
+ t.Errorf("\tSECTION %s: %s", p.section, p.sentence)
+ }
+
+ t.Logf("%d/%d (%d%%) sentances covered", complete, total, (complete/total)*100)
+}
+
+func attrSig(se xml.StartElement) string {
+ var names []string
+ for _, attr := range se.Attr {
+ if attr.Name.Local == "fmt" {
+ names = append(names, "fmt-"+attr.Value)
+ } else {
+ names = append(names, attr.Name.Local)
+ }
+ }
+ sort.Strings(names)
+ return strings.Join(names, ",")
+}
+
+func attrValue(se xml.StartElement, attr string) string {
+ for _, a := range se.Attr {
+ if a.Name.Local == attr {
+ return a.Value
+ }
+ }
+ panic("unknown attribute " + attr)
+}
+
+func TestSpecPartLess(t *testing.T) {
+ tests := []struct {
+ sec1, sec2 string
+ want bool
+ }{
+ {"6.2.1", "6.2", false},
+ {"6.2", "6.2.1", true},
+ {"6.10", "6.10.1", true},
+ {"6.10", "6.1.1", false}, // 10, not 1
+ {"6.1", "6.1", false}, // equal, so not less
+ }
+ for _, tt := range tests {
+ got := (specPart{tt.sec1, "foo"}).Less(specPart{tt.sec2, "foo"})
+ if got != tt.want {
+ t.Errorf("Less(%q, %q) = %v; want %v", tt.sec1, tt.sec2, got, tt.want)
+ }
+ }
+}