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") }
// newHTTP2Server constructs a ServerTransport based on HTTP2. ConnectionError is // returned if something goes wrong. func newHTTP2Server(conn net.Conn, maxStreams uint32, authInfo credentials.AuthInfo) (_ 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. if maxStreams == 0 { maxStreams = math.MaxUint32 } else { settings = append(settings, http2.Setting{http2.SettingMaxConcurrentStreams, maxStreams}) } if initialWindowSize != defaultWindowSize { settings = append(settings, http2.Setting{http2.SettingInitialWindowSize, uint32(initialWindowSize)}) } if err := framer.writeSettings(true, settings...); 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) } } var buf bytes.Buffer t := &http2Server{ conn: conn, authInfo: authInfo, framer: framer, hBuf: &buf, hEnc: hpack.NewEncoder(&buf), maxStreams: maxStreams, 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 }
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) 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 }
// 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 }