func ServerPushTestGroup(ctx *Context) *TestGroup { tg := NewTestGroup("8.2", "Server Push") tg.AddTestCase(NewTestCase( "Sends a PUSH_PROMISE frame", "The endpoint MUST treat the receipt of a PUSH_PROMISE frame as a connection error of type PROTOCOL_ERROR.", func(ctx *Context) (pass bool, expected []Result, actual Result) { http2Conn := CreateHttp2Conn(ctx, true) defer http2Conn.conn.Close() var buf bytes.Buffer hdrs := commonHeaderFields(ctx) enc := hpack.NewEncoder(&buf) for _, hf := range hdrs { _ = enc.WriteField(hf) } var pp http2.PushPromiseParam pp.StreamID = 1 pp.PromiseID = 3 pp.EndHeaders = true pp.BlockFragment = buf.Bytes() http2Conn.fr.WritePushPromise(pp) actualCodes := []http2.ErrCode{http2.ErrCodeProtocol} return TestConnectionError(ctx, http2Conn, actualCodes) }, )) return tg }
func main() { flag.Usage = usage flag.Parse() if flag.NArg() != 1 { usage() os.Exit(2) } 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 TestTransportChecksResponseHeaderListSize(t *testing.T) { ct := newClientTester(t) ct.client = func() error { req, _ := http.NewRequest("GET", "https://dummy.tld/", nil) res, err := ct.tr.RoundTrip(req) if err != errResponseHeaderListSize { if res != nil { res.Body.Close() } size := int64(0) for k, vv := range res.Header { for _, v := range vv { size += int64(len(k)) + int64(len(v)) + 32 } } return fmt.Errorf("RoundTrip Error = %v (and %d bytes of response headers); want errResponseHeaderListSize", err, size) } return nil } ct.server = func() error { ct.greet() var buf bytes.Buffer enc := hpack.NewEncoder(&buf) for { f, err := ct.fr.ReadFrame() if err != nil { return err } switch f := f.(type) { case *HeadersFrame: enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"}) large := strings.Repeat("a", 1<<10) for i := 0; i < 5042; i++ { enc.WriteField(hpack.HeaderField{Name: large, Value: large}) } if size, want := buf.Len(), 6329; size != want { // Note: this number might change if // our hpack implementation // changes. That's fine. This is // just a sanity check that our // response can fit in a single // header block fragment frame. return fmt.Errorf("encoding over 10MB of duplicate keypairs took %d bytes; expected %d", size, want) } ct.fr.WriteHeaders(HeadersFrameParam{ StreamID: f.StreamID, EndHeaders: true, EndStream: true, BlockFragment: buf.Bytes(), }) return nil } } } ct.run() }
// newHTTP2Server constructs a ServerTransport based on HTTP2. ConnectionError is // returned if something goes wrong. func newHTTP2Server(conn net.Conn, config *ServerConfig) (_ ServerTransport, err error) { framer := newFramer(conn) // Send initial settings as connection preface to client. var settings []http2.Setting // TODO(zhaoq): Have a better way to signal "no limit" because 0 is // permitted in the HTTP2 spec. maxStreams := config.MaxStreams if maxStreams == 0 { maxStreams = math.MaxUint32 } else { settings = append(settings, http2.Setting{ ID: http2.SettingMaxConcurrentStreams, Val: maxStreams, }) } if initialWindowSize != defaultWindowSize { settings = append(settings, http2.Setting{ ID: http2.SettingInitialWindowSize, Val: uint32(initialWindowSize)}) } if err := framer.writeSettings(true, settings...); err != nil { return nil, connectionErrorf(true, err, "transport: %v", err) } // Adjust the connection flow control window if needed. if delta := uint32(initialConnWindowSize - defaultWindowSize); delta > 0 { if err := framer.writeWindowUpdate(true, 0, delta); err != nil { return nil, connectionErrorf(true, err, "transport: %v", err) } } var buf bytes.Buffer t := &http2Server{ conn: conn, remoteAddr: conn.RemoteAddr(), localAddr: conn.LocalAddr(), authInfo: config.AuthInfo, framer: framer, hBuf: &buf, hEnc: hpack.NewEncoder(&buf), maxStreams: maxStreams, inTapHandle: config.InTapHandle, controlBuf: newRecvBuffer(), fc: &inFlow{limit: initialConnWindowSize}, sendQuotaPool: newQuotaPool(defaultWindowSize), state: reachable, writableChan: make(chan int, 1), shutdownChan: make(chan struct{}), activeStreams: make(map[uint32]*Stream), streamSendQuota: defaultWindowSize, } go t.controller() t.writableChan <- 0 return t, nil }
// like encodeHeader, but don't add implicit pseudo 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() }
// The Google GFE responds to HEAD requests with a HEADERS frame // without END_STREAM, followed by a 0-length DATA frame with // END_STREAM. Make sure we don't get confused by that. (We did.) func TestTransportReadHeadResponse(t *testing.T) { ct := newClientTester(t) clientDone := make(chan struct{}) ct.client = func() error { defer close(clientDone) req, _ := http.NewRequest("HEAD", "https://dummy.tld/", nil) res, err := ct.tr.RoundTrip(req) if err != nil { return err } if res.ContentLength != 123 { return fmt.Errorf("Content-Length = %d; want 123", res.ContentLength) } slurp, err := ioutil.ReadAll(res.Body) if err != nil { return fmt.Errorf("ReadAll: %v", err) } if len(slurp) > 0 { return fmt.Errorf("Unexpected non-empty ReadAll body: %q", slurp) } return nil } ct.server = func() error { ct.greet() for { f, err := ct.fr.ReadFrame() if err != nil { t.Logf("ReadFrame: %v", err) return nil } hf, ok := f.(*HeadersFrame) if !ok { continue } var buf bytes.Buffer enc := hpack.NewEncoder(&buf) enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"}) enc.WriteField(hpack.HeaderField{Name: "content-length", Value: "123"}) ct.fr.WriteHeaders(HeadersFrameParam{ StreamID: hf.StreamID, EndHeaders: true, EndStream: false, // as the GFE does BlockFragment: buf.Bytes(), }) ct.fr.WriteData(hf.StreamID, true, nil) <-clientDone return nil } return nil } ct.run() }
func newServerTesterFromConn(t testing.TB, cc io.ReadWriteCloser) *serverTester { st := &serverTester{ t: t, cc: cc, frc: make(chan http2.Frame, 1), frErrc: make(chan error, 1), } st.hpackEnc = hpack.NewEncoder(&st.headerBuf) st.fr = http2.NewFramer(cc, cc) st.fr.ReadMetaHeaders = hpack.NewDecoder(4096 /*initialHeaderTableSize*/, nil) return st }
// newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2 // and starts to receive messages on it. Non-nil error returns if construction // fails. func newHTTP2Client(addr string, opts *ConnectOptions) (_ ClientTransport, err error) { if opts.Dialer == nil { // Set the default Dialer. opts.Dialer = func(addr string, timeout time.Duration) (net.Conn, error) { return net.DialTimeout("tcp", addr, timeout) } } scheme := "http" startT := time.Now() timeout := opts.Timeout conn, connErr := opts.Dialer(addr, timeout) if connErr != nil { return nil, ConnectionErrorf("transport: %v", connErr) } var authInfo credentials.AuthInfo if opts.TransportCredentials != nil { scheme = "https" if timeout > 0 { timeout -= time.Since(startT) } conn, authInfo, connErr = opts.TransportCredentials.ClientHandshake(addr, conn, timeout) } if connErr != nil { return nil, ConnectionErrorf("transport: %v", connErr) } defer func() { if err != nil { conn.Close() } }() ua := primaryUA if opts.UserAgent != "" { ua = opts.UserAgent + " " + ua } var buf bytes.Buffer t := &http2Client{ target: addr, userAgent: ua, conn: conn, authInfo: authInfo, // The client initiated stream id is odd starting from 1. nextID: 1, writableChan: make(chan int, 1), shutdownChan: make(chan struct{}), errorChan: make(chan struct{}), goAway: make(chan struct{}), framer: newFramer(conn), hBuf: &buf, hEnc: hpack.NewEncoder(&buf), controlBuf: newRecvBuffer(), fc: &inFlow{limit: initialConnWindowSize}, sendQuotaPool: newQuotaPool(defaultWindowSize), scheme: scheme, state: reachable, activeStreams: make(map[uint32]*Stream), creds: opts.PerRPCCredentials, maxStreams: math.MaxInt32, streamSendQuota: defaultWindowSize, } // Start the reader goroutine for incoming message. Each transport has // a dedicated goroutine which reads HTTP2 frame from network. Then it // dispatches the frame to the corresponding stream entity. go t.reader() // Send connection preface to server. n, err := t.conn.Write(clientPreface) if err != nil { t.Close() return nil, ConnectionErrorf("transport: %v", err) } if n != len(clientPreface) { t.Close() return nil, ConnectionErrorf("transport: preface mismatch, wrote %d bytes; want %d", n, len(clientPreface)) } if initialWindowSize != defaultWindowSize { err = t.framer.writeSettings(true, http2.Setting{ ID: http2.SettingInitialWindowSize, Val: uint32(initialWindowSize), }) } else { err = t.framer.writeSettings(true) } if err != nil { t.Close() return nil, ConnectionErrorf("transport: %v", err) } // Adjust the connection flow control window if needed. if delta := uint32(initialConnWindowSize - defaultWindowSize); delta > 0 { if err := t.framer.writeWindowUpdate(true, 0, delta); err != nil { t.Close() return nil, ConnectionErrorf("transport: %v", err) } } go t.controller() t.writableChan <- 0 return t, nil }
func (t *Transport) NewClientConn(c net.Conn) (*ClientConn, error) { if VerboseLogs { t.vlogf("http2: Transport creating client conn to %v", c.RemoteAddr()) } if _, err := c.Write(clientPreface); err != nil { t.vlogf("client preface write error: %v", err) return nil, err } cc := &ClientConn{ t: t, tconn: c, 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.cond = sync.NewCond(&cc.mu) cc.flow.add(int32(initialWindowSize)) // TODO: adjust this writer size to account for frame size + // MTU + crypto/tls record padding. cc.bw = bufio.NewWriter(stickyErrWriter{c, &cc.werr}) cc.br = bufio.NewReader(c) cc.fr = NewFramer(cc.bw, cc.br) // TODO: SetMaxDynamicTableSize, SetMaxDynamicTableSizeLimit on // henc in response to SETTINGS frames? cc.henc = hpack.NewEncoder(&cc.hbuf) type connectionStater interface { ConnectionState() tls.ConnectionState } if cs, ok := c.(connectionStater); ok { state := cs.ConnectionState() cc.tlsState = &state } initialSettings := []Setting{ Setting{ID: SettingEnablePush, Val: 0}, Setting{ID: SettingInitialWindowSize, Val: transportDefaultStreamFlow}, } if max := t.maxHeaderListSize(); max != 0 { initialSettings = append(initialSettings, Setting{ID: SettingMaxHeaderListSize, Val: max}) } cc.fr.WriteSettings(initialSettings...) cc.fr.WriteWindowUpdate(0, transportDefaultConnFlow) cc.inflow.add(transportDefaultConnFlow + initialWindowSize) 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; at least SETTINGS_HEADER_TABLE_SIZE? t.vlogf("Unhandled Setting: %v", s) } return nil }) go cc.readLoop() return cc, nil }
// newServerTesterInternal creates test context. If frontendTLS is // true, set up TLS frontend connection. connectPort is the server // side port where client connection is made. func newServerTesterInternal(src_args []string, t *testing.T, handler http.Handler, frontendTLS bool, connectPort int, clientConfig *tls.Config) *serverTester { ts := httptest.NewUnstartedServer(handler) args := []string{} backendTLS := false dns := false externalDNS := false acceptProxyProtocol := false for _, k := range src_args { switch k { case "--http2-bridge": backendTLS = true case "--dns": dns = true case "--external-dns": dns = true externalDNS = true case "--accept-proxy-protocol": acceptProxyProtocol = true default: args = append(args, k) } } if backendTLS { nghttp2.ConfigureServer(ts.Config, &nghttp2.Server{}) // According to httptest/server.go, we have to set // NextProtos separately for ts.TLS. NextProtos set // in nghttp2.ConfigureServer is effectively ignored. ts.TLS = new(tls.Config) ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2") ts.StartTLS() args = append(args, "-k") } else { ts.Start() } scheme := "http" if frontendTLS { scheme = "https" args = append(args, testDir+"/server.key", testDir+"/server.crt") } backendURL, err := url.Parse(ts.URL) if err != nil { t.Fatalf("Error parsing URL from httptest.Server: %v", err) } // URL.Host looks like "127.0.0.1:8080", but we want // "127.0.0.1,8080" b := "-b" if !externalDNS { b += fmt.Sprintf("%v;", strings.Replace(backendURL.Host, ":", ",", -1)) } else { sep := strings.LastIndex(backendURL.Host, ":") if sep == -1 { t.Fatalf("backendURL.Host %v does not contain separator ':'", backendURL.Host) } // We use awesome service xip.io. b += fmt.Sprintf("%v.xip.io,%v;", backendURL.Host[:sep], backendURL.Host[sep+1:]) } if backendTLS { b += ";proto=h2;tls" } if dns { b += ";dns" } noTLS := ";no-tls" if frontendTLS { noTLS = "" } var proxyProto string if acceptProxyProtocol { proxyProto = ";proxyproto" } args = append(args, fmt.Sprintf("-f127.0.0.1,%v%v%v", serverPort, noTLS, proxyProto), b, "--errorlog-file="+logDir+"/log.txt", "-LINFO") authority := fmt.Sprintf("127.0.0.1:%v", connectPort) st := &serverTester{ cmd: exec.Command(serverBin, args...), t: t, ts: ts, url: fmt.Sprintf("%v://%v", scheme, authority), frontendHost: fmt.Sprintf("127.0.0.1:%v", serverPort), backendHost: backendURL.Host, nextStreamID: 1, authority: authority, frCh: make(chan http2.Frame), spdyFrCh: make(chan spdy.Frame), errCh: make(chan error), } if err := st.cmd.Start(); err != nil { st.t.Fatalf("Error starting %v: %v", serverBin, err) } retry := 0 for { time.Sleep(50 * time.Millisecond) var conn net.Conn var err error if frontendTLS { var tlsConfig *tls.Config if clientConfig == nil { tlsConfig = new(tls.Config) } else { tlsConfig = clientConfig } tlsConfig.InsecureSkipVerify = true tlsConfig.NextProtos = []string{"h2", "spdy/3.1"} conn, err = tls.Dial("tcp", authority, tlsConfig) } else { conn, err = net.Dial("tcp", authority) } if err != nil { retry += 1 if retry >= 100 { st.Close() st.t.Fatalf("Error server is not responding too long; server command-line arguments may be invalid") } continue } if frontendTLS { tlsConn := conn.(*tls.Conn) cs := tlsConn.ConnectionState() if !cs.NegotiatedProtocolIsMutual { st.Close() st.t.Fatalf("Error negotiated next protocol is not mutual") } } st.conn = conn break } st.fr = http2.NewFramer(st.conn, st.conn) spdyFr, err := spdy.NewFramer(st.conn, st.conn) if err != nil { st.Close() st.t.Fatalf("Error spdy.NewFramer: %v", err) } st.spdyFr = spdyFr st.enc = hpack.NewEncoder(&st.headerBlkBuf) st.dec = hpack.NewDecoder(4096, func(f hpack.HeaderField) { st.header.Add(f.Name, f.Value) }) return st }
func HeadersTestGroup(ctx *Context) *TestGroup { tg := NewTestGroup("6.2", "HEADERS") tg.AddTestCase(NewTestCase( "Sends a HEADERS frame followed by any frame other than CONTINUATION", "The endpoint MUST treat the receipt of any other type of frame as a connection error of type PROTOCOL_ERROR.", func(ctx *Context) (pass bool, expected []Result, actual Result) { http2Conn := CreateHttp2Conn(ctx, true) defer http2Conn.conn.Close() hdrs := commonHeaderFields(ctx) var hp http2.HeadersFrameParam hp.StreamID = 1 hp.EndStream = false hp.EndHeaders = false hp.BlockFragment = http2Conn.EncodeHeader(hdrs) http2Conn.fr.WriteHeaders(hp) http2Conn.fr.WriteData(1, true, []byte("test")) actualCodes := []http2.ErrCode{http2.ErrCodeProtocol} return TestConnectionError(ctx, http2Conn, actualCodes) }, )) tg.AddTestCase(NewTestCase( "Sends a HEADERS frame followed by a frame on a different stream", "The endpoint MUST treat the receipt of a frame on a different stream as a connection error of type PROTOCOL_ERROR.", func(ctx *Context) (pass bool, expected []Result, actual Result) { http2Conn := CreateHttp2Conn(ctx, true) defer http2Conn.conn.Close() hdrs := commonHeaderFields(ctx) var hp1 http2.HeadersFrameParam hp1.StreamID = 1 hp1.EndStream = false hp1.EndHeaders = false hp1.BlockFragment = http2Conn.EncodeHeader(hdrs) http2Conn.fr.WriteHeaders(hp1) var hp2 http2.HeadersFrameParam hp2.StreamID = 3 hp2.EndStream = true hp2.EndHeaders = true hp2.BlockFragment = http2Conn.EncodeHeader(hdrs) http2Conn.fr.WriteHeaders(hp2) actualCodes := []http2.ErrCode{http2.ErrCodeProtocol} return TestConnectionError(ctx, http2Conn, actualCodes) }, )) tg.AddTestCase(NewTestCase( "Sends a HEADERS frame with 0x0 stream identifier", "The endpoint MUST respond with a connection error of type PROTOCOL_ERROR.", func(ctx *Context) (pass bool, expected []Result, actual Result) { http2Conn := CreateHttp2Conn(ctx, true) defer http2Conn.conn.Close() hdrs := commonHeaderFields(ctx) var hp http2.HeadersFrameParam hp.StreamID = 0 hp.EndStream = true hp.EndHeaders = true hp.BlockFragment = http2Conn.EncodeHeader(hdrs) http2Conn.fr.WriteHeaders(hp) actualCodes := []http2.ErrCode{http2.ErrCodeProtocol} return TestConnectionError(ctx, http2Conn, actualCodes) }, )) tg.AddTestCase(NewTestCase( "Sends a HEADERS frame with invalid pad length", "The endpoint MUST treat this as a connection error of type PROTOCOL_ERROR.", func(ctx *Context) (pass bool, expected []Result, actual Result) { http2Conn := CreateHttp2Conn(ctx, true) defer http2Conn.conn.Close() var buf bytes.Buffer hdrs := commonHeaderFields(ctx) enc := hpack.NewEncoder(&buf) for _, hf := range hdrs { _ = enc.WriteField(hf) } // Payload length: 12, Pad length: 13 fmt.Fprintf(http2Conn.conn, "\x00\x00\x0c\x01\x0d\x00\x00\x00\x01") fmt.Fprintf(http2Conn.conn, "\x0d") http2Conn.conn.Write(buf.Bytes()) actualCodes := []http2.ErrCode{http2.ErrCodeProtocol} return TestConnectionError(ctx, http2Conn, actualCodes) }, )) return tg }
func testTransportReqBodyAfterResponse(t *testing.T, status int) { const bodySize = 10 << 20 ct := newClientTester(t) ct.client = func() error { var n int64 // atomic req, err := http.NewRequest("PUT", "https://dummy.tld/", io.LimitReader(countingReader{&n}, bodySize)) if err != nil { return err } res, err := ct.tr.RoundTrip(req) if err != nil { return fmt.Errorf("RoundTrip: %v", err) } defer res.Body.Close() if res.StatusCode != status { return fmt.Errorf("status code = %v; want %v", res.StatusCode, status) } slurp, err := ioutil.ReadAll(res.Body) if err != nil { return fmt.Errorf("Slurp: %v", err) } if len(slurp) > 0 { return fmt.Errorf("unexpected body: %q", slurp) } if status == 200 { if got := atomic.LoadInt64(&n); got != bodySize { return fmt.Errorf("For 200 response, Transport wrote %d bytes; want %d", got, bodySize) } } else { if got := atomic.LoadInt64(&n); got == 0 || got >= bodySize { return fmt.Errorf("For %d response, Transport wrote %d bytes; want (0,%d) exclusive", status, got, bodySize) } } return nil } ct.server = func() error { ct.greet() var buf bytes.Buffer enc := hpack.NewEncoder(&buf) var dataRecv int64 var closed bool for { f, err := ct.fr.ReadFrame() if err != nil { return err } //println(fmt.Sprintf("server got frame: %v", f)) switch f := f.(type) { case *WindowUpdateFrame, *SettingsFrame: case *HeadersFrame: if !f.HeadersEnded() { return fmt.Errorf("headers should have END_HEADERS be ended: %v", f) } if f.StreamEnded() { return fmt.Errorf("headers contains END_STREAM unexpectedly: %v", f) } time.Sleep(50 * time.Millisecond) // let client send body enc.WriteField(hpack.HeaderField{Name: ":status", Value: strconv.Itoa(status)}) ct.fr.WriteHeaders(HeadersFrameParam{ StreamID: f.StreamID, EndHeaders: true, EndStream: false, BlockFragment: buf.Bytes(), }) case *DataFrame: dataLen := len(f.Data()) dataRecv += int64(dataLen) if dataLen > 0 { if err := ct.fr.WriteWindowUpdate(0, uint32(dataLen)); err != nil { return err } if err := ct.fr.WriteWindowUpdate(f.StreamID, uint32(dataLen)); err != nil { return err } } if !closed && ((status != 200 && dataRecv > 0) || (status == 200 && dataRecv == bodySize)) { closed = true if err := ct.fr.WriteData(f.StreamID, true, nil); err != nil { return err } return nil } default: return fmt.Errorf("Unexpected client frame %v", f) } } return nil } ct.run() }
func testTransportResPattern(t *testing.T, expect100Continue, resHeader headerType, withData bool, trailers headerType) { const reqBody = "some request body" const resBody = "some response body" if resHeader == noHeader { // TODO: test 100-continue followed by immediate // server stream reset, without headers in the middle? panic("invalid combination") } ct := newClientTester(t) ct.client = func() error { req, _ := http.NewRequest("POST", "https://dummy.tld/", strings.NewReader(reqBody)) if expect100Continue != noHeader { req.Header.Set("Expect", "100-continue") } res, err := ct.tr.RoundTrip(req) if err != nil { return fmt.Errorf("RoundTrip: %v", err) } defer res.Body.Close() if res.StatusCode != 200 { return fmt.Errorf("status code = %v; want 200", res.StatusCode) } slurp, err := ioutil.ReadAll(res.Body) if err != nil { return fmt.Errorf("Slurp: %v", err) } wantBody := resBody if !withData { wantBody = "" } if string(slurp) != wantBody { return fmt.Errorf("body = %q; want %q", slurp, wantBody) } if trailers == noHeader { if len(res.Trailer) > 0 { t.Errorf("Trailer = %v; want none", res.Trailer) } } else { want := http.Header{"Some-Trailer": {"some-value"}} if !reflect.DeepEqual(res.Trailer, want) { t.Errorf("Trailer = %v; want %v", res.Trailer, want) } } return nil } ct.server = func() error { ct.greet() var buf bytes.Buffer enc := hpack.NewEncoder(&buf) for { f, err := ct.fr.ReadFrame() if err != nil { return err } switch f := f.(type) { case *WindowUpdateFrame, *SettingsFrame: case *DataFrame: // ignore for now. case *HeadersFrame: endStream := false send := func(mode headerType) { hbf := buf.Bytes() switch mode { case oneHeader: ct.fr.WriteHeaders(HeadersFrameParam{ StreamID: f.StreamID, EndHeaders: true, EndStream: endStream, BlockFragment: hbf, }) case splitHeader: if len(hbf) < 2 { panic("too small") } ct.fr.WriteHeaders(HeadersFrameParam{ StreamID: f.StreamID, EndHeaders: false, EndStream: endStream, BlockFragment: hbf[:1], }) ct.fr.WriteContinuation(f.StreamID, true, hbf[1:]) default: panic("bogus mode") } } if expect100Continue != noHeader { buf.Reset() enc.WriteField(hpack.HeaderField{Name: ":status", Value: "100"}) send(expect100Continue) } // Response headers (1+ frames; 1 or 2 in this test, but never 0) { buf.Reset() enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"}) enc.WriteField(hpack.HeaderField{Name: "x-foo", Value: "blah"}) enc.WriteField(hpack.HeaderField{Name: "x-bar", Value: "more"}) if trailers != noHeader { enc.WriteField(hpack.HeaderField{Name: "trailer", Value: "some-trailer"}) } endStream = withData == false && trailers == noHeader send(resHeader) } if withData { endStream = trailers == noHeader ct.fr.WriteData(f.StreamID, endStream, []byte(resBody)) } if trailers != noHeader { endStream = true buf.Reset() enc.WriteField(hpack.HeaderField{Name: "some-trailer", Value: "some-value"}) send(trailers) } return nil } } } ct.run() }
func TestTransportReceiveUndeclaredTrailer(t *testing.T) { ct := newClientTester(t) ct.client = func() error { req, _ := http.NewRequest("GET", "https://dummy.tld/", nil) res, err := ct.tr.RoundTrip(req) if err != nil { return fmt.Errorf("RoundTrip: %v", err) } defer res.Body.Close() if res.StatusCode != 200 { return fmt.Errorf("status code = %v; want 200", res.StatusCode) } slurp, err := ioutil.ReadAll(res.Body) if err != nil { return fmt.Errorf("res.Body ReadAll error = %q, %v; want %v", slurp, err, nil) } if len(slurp) > 0 { return fmt.Errorf("body = %q; want nothing", slurp) } if _, ok := res.Trailer["Some-Trailer"]; !ok { return fmt.Errorf("expected Some-Trailer") } return nil } ct.server = func() error { ct.greet() var n int var hf *HeadersFrame for hf == nil && n < 10 { f, err := ct.fr.ReadFrame() if err != nil { return err } hf, _ = f.(*HeadersFrame) n++ } var buf bytes.Buffer enc := hpack.NewEncoder(&buf) // send headers without Trailer header enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"}) ct.fr.WriteHeaders(HeadersFrameParam{ StreamID: hf.StreamID, EndHeaders: true, EndStream: false, BlockFragment: buf.Bytes(), }) // send trailers buf.Reset() enc.WriteField(hpack.HeaderField{Name: "some-trailer", Value: "I'm an undeclared Trailer!"}) ct.fr.WriteHeaders(HeadersFrameParam{ StreamID: hf.StreamID, EndHeaders: true, EndStream: true, BlockFragment: buf.Bytes(), }) return nil } ct.run() }
func testInvalidTrailer(t *testing.T, trailers headerType, wantErr error, writeTrailer func(*hpack.Encoder)) { ct := newClientTester(t) ct.client = func() error { req, _ := http.NewRequest("GET", "https://dummy.tld/", nil) res, err := ct.tr.RoundTrip(req) if err != nil { return fmt.Errorf("RoundTrip: %v", err) } defer res.Body.Close() if res.StatusCode != 200 { return fmt.Errorf("status code = %v; want 200", res.StatusCode) } slurp, err := ioutil.ReadAll(res.Body) if err != wantErr { return fmt.Errorf("res.Body ReadAll error = %q, %#v; want %T of %#v", slurp, err, wantErr, wantErr) } if len(slurp) > 0 { return fmt.Errorf("body = %q; want nothing", slurp) } return nil } ct.server = func() error { ct.greet() var buf bytes.Buffer enc := hpack.NewEncoder(&buf) for { f, err := ct.fr.ReadFrame() if err != nil { return err } switch f := f.(type) { case *HeadersFrame: var endStream bool send := func(mode headerType) { hbf := buf.Bytes() switch mode { case oneHeader: ct.fr.WriteHeaders(HeadersFrameParam{ StreamID: f.StreamID, EndHeaders: true, EndStream: endStream, BlockFragment: hbf, }) case splitHeader: if len(hbf) < 2 { panic("too small") } ct.fr.WriteHeaders(HeadersFrameParam{ StreamID: f.StreamID, EndHeaders: false, EndStream: endStream, BlockFragment: hbf[:1], }) ct.fr.WriteContinuation(f.StreamID, true, hbf[1:]) default: panic("bogus mode") } } // Response headers (1+ frames; 1 or 2 in this test, but never 0) { buf.Reset() enc.WriteField(hpack.HeaderField{Name: ":status", Value: "200"}) enc.WriteField(hpack.HeaderField{Name: "trailer", Value: "declared"}) endStream = false send(oneHeader) } // Trailers: { endStream = true buf.Reset() writeTrailer(enc) send(trailers) } return nil } } } ct.run() }
func newHTTP2Connection( isServer bool, netConn net.Conn, serverStreamHandler StreamHandler) (*http2Connection, error) { var hpackBuffer bytes.Buffer writer := bufio.NewWriterSize(netConn, http2IOBufSize) conn := &http2Connection{ isServer: isServer, serverStreamHandler: serverStreamHandler, netConn: netConn, writer: writer, framer: http2.NewFramer(writer, netConn), hpackBuffer: &hpackBuffer, hpackEncoder: hpack.NewEncoder(&hpackBuffer), controlBuffer: leverutil.NewUnboundedChannel(), flowControl: &inFlow{limit: initialConnWindowSize}, sendQuotaPool: newQuotaPool(defaultWindowSize), writableChan: make(chan int, 1), shutdownChan: make(chan struct{}), closedChan: make(chan struct{}), streamSendQuota: defaultWindowSize, streams: make(map[uint32]*HTTP2Stream), } if !isServer { conn.nextStreamID = 1 // Odd numbers for client-initiated streams. n, err := netConn.Write(clientPreface) if err != nil { return nil, err } if n != len(clientPreface) { return nil, fmt.Errorf("Preface length mismatch") } } var settings []http2.Setting if initialWindowSize != defaultWindowSize { settings = append( settings, http2.Setting{ ID: http2.SettingInitialWindowSize, Val: uint32(initialWindowSize), }) } err := conn.framer.WriteSettings(settings...) if err != nil { return nil, err } windowDelta := uint32(initialConnWindowSize - defaultWindowSize) if windowDelta > 0 { err := conn.framer.WriteWindowUpdate(0, windowDelta) if err != nil { return nil, err } } conn.writer.Flush() conn.writableChan <- 0 // The controller is responsible with writing frames on the wire. go conn.controller() // The reader reads in frames from the wire, handles stream 0 / // control frames and dispatches frames for the rest of the streams. go conn.reader() return conn, nil }
// newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2 // and starts to receive messages on it. Non-nil error returns if construction // fails. func newHTTP2Client(addr string, opts *ConnectOptions) (_ ClientTransport, err error) { if opts.Dialer == nil { // Set the default Dialer. opts.Dialer = func(addr string, timeout time.Duration) (net.Conn, error) { return net.DialTimeout("tcp", addr, timeout) } } scheme := "http" startT := time.Now() timeout := opts.Timeout conn, connErr := opts.Dialer(addr, timeout) if connErr != nil { return nil, ConnectionErrorf("transport: %v", connErr) } var authInfo credentials.AuthInfo for _, c := range opts.AuthOptions { if ccreds, ok := c.(credentials.TransportAuthenticator); ok { scheme = "https" // TODO(zhaoq): Now the first TransportAuthenticator is used if there are // multiple ones provided. Revisit this if it is not appropriate. Probably // place the ClientTransport construction into a separate function to make // things clear. if timeout > 0 { timeout -= time.Since(startT) } conn, authInfo, connErr = ccreds.ClientHandshake(addr, conn, timeout) break } } if connErr != nil { return nil, ConnectionErrorf("transport: %v", connErr) } defer func() { if err != nil { conn.Close() } }() // Send connection preface to server. n, err := conn.Write(clientPreface) if err != nil { return nil, ConnectionErrorf("transport: %v", err) } if n != len(clientPreface) { return nil, ConnectionErrorf("transport: preface mismatch, wrote %d bytes; want %d", n, len(clientPreface)) } framer := newFramer(conn) if initialWindowSize != defaultWindowSize { err = framer.writeSettings(true, http2.Setting{http2.SettingInitialWindowSize, uint32(initialWindowSize)}) } else { err = framer.writeSettings(true) } if err != nil { return nil, ConnectionErrorf("transport: %v", err) } // Adjust the connection flow control window if needed. if delta := uint32(initialConnWindowSize - defaultWindowSize); delta > 0 { if err := framer.writeWindowUpdate(true, 0, delta); err != nil { return nil, ConnectionErrorf("transport: %v", err) } } ua := primaryUA if opts.UserAgent != "" { ua = opts.UserAgent + " " + ua } var buf bytes.Buffer t := &http2Client{ target: addr, userAgent: ua, conn: conn, authInfo: authInfo, // The client initiated stream id is odd starting from 1. nextID: 1, writableChan: make(chan int, 1), shutdownChan: make(chan struct{}), errorChan: make(chan struct{}), framer: framer, hBuf: &buf, hEnc: hpack.NewEncoder(&buf), controlBuf: newRecvBuffer(), fc: &inFlow{limit: initialConnWindowSize}, sendQuotaPool: newQuotaPool(defaultWindowSize), scheme: scheme, state: reachable, activeStreams: make(map[uint32]*Stream), authCreds: opts.AuthOptions, maxStreams: math.MaxInt32, streamSendQuota: defaultWindowSize, } go t.controller() t.writableChan <- 0 // Start the reader goroutine for incoming message. The threading model // on receiving is that each transport has a dedicated goroutine which // reads HTTP2 frame from network. Then it dispatches the frame to the // corresponding stream entity. go t.reader() return t, nil }
func TestHTTP2MatchHeaderField(t *testing.T) { defer leakCheck(t)() errCh := make(chan error) defer func() { select { case err := <-errCh: t.Fatal(err) default: } }() name := "name" value := "value" writer, reader := net.Pipe() go func() { if _, err := io.WriteString(writer, http2.ClientPreface); err != nil { t.Fatal(err) } var buf bytes.Buffer enc := hpack.NewEncoder(&buf) if err := enc.WriteField(hpack.HeaderField{Name: name, Value: value}); err != nil { t.Fatal(err) } framer := http2.NewFramer(writer, nil) err := framer.WriteHeaders(http2.HeadersFrameParam{ StreamID: 1, BlockFragment: buf.Bytes(), EndStream: true, EndHeaders: true, }) if err != nil { t.Fatal(err) } if err := writer.Close(); err != nil { t.Fatal(err) } }() l := newChanListener() l.connCh <- reader muxl := New(l) // Register a bogus matcher that only reads one byte. muxl.Match(func(r io.Reader) bool { var b [1]byte _, _ = r.Read(b[:]) return false }) // Create a matcher that cannot match the response. muxl.Match(HTTP2HeaderField(name, "another"+value)) // Then match with the expected field. h2l := muxl.Match(HTTP2HeaderField(name, value)) go safeServe(errCh, muxl) muxedConn, err := h2l.Accept() close(l.connCh) if err != nil { t.Fatal(err) } var b [len(http2.ClientPreface)]byte // We have the sniffed buffer first... if _, err := muxedConn.Read(b[:]); err == io.EOF { t.Fatal(err) } if string(b[:]) != http2.ClientPreface { t.Errorf("got unexpected read %s, expected %s", b, http2.ClientPreface) } }
func CreateHttp2Conn(ctx *Context, sn bool) *Http2Conn { var conn net.Conn var err error if ctx.Tls { conn, err = connectTls(ctx) } else { conn, err = net.DialTimeout("tcp", ctx.Authority(), ctx.Timeout) } if err != nil { printError(fmt.Sprintf("Unable to connect to the target server (%v)", err)) os.Exit(1) } fmt.Fprintf(conn, "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n") fr := http2.NewFramer(conn, conn) settings := map[http2.SettingID]uint32{} if sn { doneCh := make(chan bool, 1) errCh := make(chan error, 1) fr.WriteSettings() go func() { local := false remote := false for { f, err := fr.ReadFrame() if err != nil { errCh <- err return } switch f := f.(type) { case *http2.SettingsFrame: if f.IsAck() { local = true } else { f.ForeachSetting(func(setting http2.Setting) error { settings[setting.ID] = setting.Val return nil }) fr.WriteSettingsAck() remote = true } } if local && remote { doneCh <- true return } } }() select { case <-doneCh: // Nothing to do. case <-errCh: printError("HTTP/2 settings negotiation failed") os.Exit(1) case <-time.After(ctx.Timeout): printError("HTTP/2 settings negotiation timeout") os.Exit(1) } } fr.AllowIllegalWrites = true dataCh := make(chan http2.Frame) errCh := make(chan error, 1) http2Conn := &Http2Conn{ conn: conn, fr: fr, dataCh: dataCh, errCh: errCh, Settings: settings, } http2Conn.HpackEncoder = hpack.NewEncoder(&http2Conn.HeaderWriteBuf) return http2Conn }
// newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2 // and starts to receive messages on it. Non-nil error returns if construction // fails. func newHTTP2Client(ctx context.Context, addr TargetInfo, opts ConnectOptions) (_ ClientTransport, err error) { scheme := "http" conn, err := dial(ctx, opts.Dialer, addr.Addr) if err != nil { if opts.FailOnNonTempDialError { return nil, connectionErrorf(isTemporary(err), err, "transport: %v", err) } return nil, connectionErrorf(true, err, "transport: %v", err) } // Any further errors will close the underlying connection defer func(conn net.Conn) { if err != nil { conn.Close() } }(conn) var authInfo credentials.AuthInfo if creds := opts.TransportCredentials; creds != nil { scheme = "https" conn, authInfo, err = creds.ClientHandshake(ctx, addr.Addr, conn) if err != nil { // Credentials handshake errors are typically considered permanent // to avoid retrying on e.g. bad certificates. temp := isTemporary(err) return nil, connectionErrorf(temp, err, "transport: %v", err) } } ua := primaryUA if opts.UserAgent != "" { ua = opts.UserAgent + " " + ua } var buf bytes.Buffer t := &http2Client{ ctx: ctx, target: addr.Addr, userAgent: ua, md: addr.Metadata, conn: conn, remoteAddr: conn.RemoteAddr(), localAddr: conn.LocalAddr(), authInfo: authInfo, // The client initiated stream id is odd starting from 1. nextID: 1, writableChan: make(chan int, 1), shutdownChan: make(chan struct{}), errorChan: make(chan struct{}), goAway: make(chan struct{}), framer: newFramer(conn), hBuf: &buf, hEnc: hpack.NewEncoder(&buf), controlBuf: newRecvBuffer(), fc: &inFlow{limit: initialConnWindowSize}, sendQuotaPool: newQuotaPool(defaultWindowSize), scheme: scheme, state: reachable, activeStreams: make(map[uint32]*Stream), creds: opts.PerRPCCredentials, maxStreams: math.MaxInt32, streamSendQuota: defaultWindowSize, statsHandler: opts.StatsHandler, } if t.statsHandler != nil { t.ctx = t.statsHandler.TagConn(t.ctx, &stats.ConnTagInfo{ RemoteAddr: t.remoteAddr, LocalAddr: t.localAddr, }) connBegin := &stats.ConnBegin{ Client: true, } t.statsHandler.HandleConn(t.ctx, connBegin) } // Start the reader goroutine for incoming message. Each transport has // a dedicated goroutine which reads HTTP2 frame from network. Then it // dispatches the frame to the corresponding stream entity. go t.reader() // Send connection preface to server. n, err := t.conn.Write(clientPreface) if err != nil { t.Close() return nil, connectionErrorf(true, err, "transport: %v", err) } if n != len(clientPreface) { t.Close() return nil, connectionErrorf(true, err, "transport: preface mismatch, wrote %d bytes; want %d", n, len(clientPreface)) } if initialWindowSize != defaultWindowSize { err = t.framer.writeSettings(true, http2.Setting{ ID: http2.SettingInitialWindowSize, Val: uint32(initialWindowSize), }) } else { err = t.framer.writeSettings(true) } if err != nil { t.Close() return nil, connectionErrorf(true, err, "transport: %v", err) } // Adjust the connection flow control window if needed. if delta := uint32(initialConnWindowSize - defaultWindowSize); delta > 0 { if err := t.framer.writeWindowUpdate(true, 0, delta); err != nil { t.Close() return nil, connectionErrorf(true, err, "transport: %v", err) } } go t.controller() t.writableChan <- 0 return t, 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", net.JoinHostPort(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 t.vlogf("Unhandled Setting: %v", s) } return nil }) // TODO: figure out henc size cc.hdec = hpack.NewDecoder(initialHeaderTableSize, cc.onNewHeaderField) go cc.readLoop() return cc, nil }
// newServerTesterInternal creates test context. If frontendTLS is // true, set up TLS frontend connection. func newServerTesterInternal(args []string, t *testing.T, handler http.Handler, frontendTLS bool, clientConfig *tls.Config) *serverTester { ts := httptest.NewUnstartedServer(handler) backendTLS := false for _, k := range args { switch k { case "--http2-bridge": backendTLS = true } } if backendTLS { nghttp2.ConfigureServer(ts.Config, &nghttp2.Server{}) // According to httptest/server.go, we have to set // NextProtos separately for ts.TLS. NextProtos set // in nghttp2.ConfigureServer is effectively ignored. ts.TLS = new(tls.Config) ts.TLS.NextProtos = append(ts.TLS.NextProtos, "h2-14") ts.StartTLS() args = append(args, "-k") } else { ts.Start() } scheme := "http" if frontendTLS { scheme = "https" args = append(args, testDir+"/server.key", testDir+"/server.crt") } else { args = append(args, "--frontend-no-tls") } backendURL, err := url.Parse(ts.URL) if err != nil { t.Fatalf("Error parsing URL from httptest.Server: %v", err) } // URL.Host looks like "127.0.0.1:8080", but we want // "127.0.0.1,8080" b := "-b" + strings.Replace(backendURL.Host, ":", ",", -1) args = append(args, fmt.Sprintf("-f127.0.0.1,%v", serverPort), b, "--errorlog-file="+testDir+"/log.txt", "-LINFO") authority := fmt.Sprintf("127.0.0.1:%v", serverPort) st := &serverTester{ cmd: exec.Command(serverBin, args...), t: t, ts: ts, url: fmt.Sprintf("%v://%v", scheme, authority), frontendHost: fmt.Sprintf("127.0.0.1:%v", serverPort), backendHost: backendURL.Host, nextStreamID: 1, authority: authority, frCh: make(chan http2.Frame), spdyFrCh: make(chan spdy.Frame), errCh: make(chan error), } if err := st.cmd.Start(); err != nil { st.t.Fatalf("Error starting %v: %v", serverBin, err) } retry := 0 for { var conn net.Conn var err error if frontendTLS { var tlsConfig *tls.Config if clientConfig == nil { tlsConfig = new(tls.Config) } else { tlsConfig = clientConfig } tlsConfig.InsecureSkipVerify = true tlsConfig.NextProtos = []string{"h2-14", "spdy/3.1"} conn, err = tls.Dial("tcp", authority, tlsConfig) } else { conn, err = net.Dial("tcp", authority) } if err != nil { retry += 1 if retry >= 100 { st.Close() st.t.Fatalf("Error server is not responding too long; server command-line arguments may be invalid") } time.Sleep(150 * time.Millisecond) continue } if frontendTLS { tlsConn := conn.(*tls.Conn) cs := tlsConn.ConnectionState() if !cs.NegotiatedProtocolIsMutual { st.Close() st.t.Fatalf("Error negotiated next protocol is not mutual") } } st.conn = conn break } st.fr = http2.NewFramer(st.conn, st.conn) spdyFr, err := spdy.NewFramer(st.conn, st.conn) if err != nil { st.Close() st.t.Fatalf("Error spdy.NewFramer: %v", err) } st.spdyFr = spdyFr st.enc = hpack.NewEncoder(&st.headerBlkBuf) st.dec = hpack.NewDecoder(4096, func(f hpack.HeaderField) { st.header.Add(f.Name, f.Value) }) return st }