| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293 |
- // 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.
- //
- // It currently targets draft-14. See http://http2.github.io/
- package http2
- import (
- "bytes"
- "crypto/tls"
- "io"
- "log"
- "net/http"
- "strings"
- "github.com/bradfitz/http2/hpack"
- )
- 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"
- )
- var (
- clientPreface = []byte(ClientPreface)
- )
- const (
- npnProto = "h2-14"
- // http://http2.github.io/http2-spec/#SettingValues
- initialHeaderTableSize = 4096
- )
- // Server is an HTTP2 server.
- type Server struct {
- // MaxStreams optionally ...
- MaxStreams int
- }
- func (srv *Server) handleConn(hs *http.Server, c *tls.Conn, h http.Handler) {
- sc := &serverConn{
- hs: hs,
- conn: c,
- handler: h,
- framer: NewFramer(c, c),
- streams: make(map[uint32]*stream),
- canonHeader: make(map[string]string),
- }
- sc.hpackDecoder = hpack.NewDecoder(initialHeaderTableSize, sc.onNewHeaderField)
- sc.serve()
- }
- type serverConn struct {
- hs *http.Server
- conn *tls.Conn
- handler http.Handler
- framer *Framer
- maxStreamID uint32 // max ever seen
- streams map[uint32]*stream
- // State related to parsing current headers:
- hpackDecoder *hpack.Decoder
- header http.Header
- canonHeader map[string]string // http2-lower-case -> Go-Canonical-Case
- method, path, scheme, authority string
- // curHeaderStreamID is non-zero if we're in the middle
- // of parsing headers that span multiple frames.
- curHeaderStreamID uint32
- }
- type streamState int
- const (
- stateIdle streamState = iota
- stateOpen
- stateHalfClosedLocal
- stateHalfClosedRemote
- stateResvLocal
- stateResvRemote
- stateClosed
- )
- type stream struct {
- id uint32
- state streamState // owned by serverConn's processing loop
- }
- func (sc *serverConn) state(streamID uint32) streamState {
- // http://http2.github.io/http2-spec/#rfc.section.5.1
- if st, ok := sc.streams[streamID]; ok {
- return st.state
- }
- // "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
- }
- return stateIdle
- }
- 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) onNewHeaderField(f hpack.HeaderField) {
- log.Printf("Header field: +%v", f)
- if strings.HasPrefix(f.Name, ":") {
- switch f.Name {
- case ":method":
- sc.method = f.Value
- case ":path":
- sc.path = f.Value
- case ":scheme":
- sc.scheme = f.Value
- case ":authority":
- sc.authority = f.Value
- default:
- log.Printf("Ignoring unknown pseudo-header %q", f.Name)
- }
- return
- }
- sc.header.Add(sc.canonicalHeader(f.Name), f.Value)
- }
- func (sc *serverConn) canonicalHeader(v string) string {
- // TODO: use a sync.Pool instead of putting the cache on *serverConn?
- cv, ok := sc.canonHeader[v]
- if !ok {
- cv = http.CanonicalHeaderKey(v)
- sc.canonHeader[v] = cv
- }
- return cv
- }
- func (sc *serverConn) serve() {
- defer sc.conn.Close()
- log.Printf("HTTP/2 connection from %v on %p", sc.conn.RemoteAddr(), sc.hs)
- buf := make([]byte, len(ClientPreface))
- // TODO: timeout reading from the client
- if _, err := io.ReadFull(sc.conn, buf); err != nil {
- sc.logf("error reading client preface: %v", err)
- return
- }
- if !bytes.Equal(buf, clientPreface) {
- sc.logf("bogus greeting from client: %q", buf)
- return
- }
- log.Printf("client %v said hello", sc.conn.RemoteAddr())
- for {
- f, err := sc.framer.ReadFrame()
- if err == nil {
- log.Printf("got %v: %#v", f.Header(), f)
- err = sc.processFrame(f)
- }
- if h2e, ok := err.(Error); ok {
- if h2e.IsConnectionError() {
- sc.logf("Disconnection; connection error: %v", err)
- return
- }
- // TODO: stream errors, etc
- }
- if err != nil {
- sc.logf("Disconnection due to other error: %v", err)
- return
- }
- }
- }
- func (sc *serverConn) processFrame(f Frame) error {
- 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)
- default:
- log.Printf("Ignoring unknown %v", f.Header)
- return nil
- }
- }
- func (sc *serverConn) processSettings(f *SettingsFrame) error {
- f.ForeachSetting(func(s SettingID, v uint32) {
- log.Printf(" setting %s = %v", s, v)
- })
- return nil
- }
- func (sc *serverConn) processHeaders(f *HeadersFrame) error {
- id := f.Header().StreamID
- // http://http2.github.io/http2-spec/#rfc.section.5.1.1
- if id%2 != 1 || id <= sc.maxStreamID {
- // 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
- }
- sc.header = make(http.Header)
- sc.curHeaderStreamID = id
- return sc.processHeaderBlockFragment(f.HeaderBlockFragment(), f.HeadersEnded())
- }
- func (sc *serverConn) processHeaderBlockFragment(frag []byte, end bool) error {
- if _, err := sc.hpackDecoder.Write(frag); err != nil {
- // TODO: convert to stream error I assume?
- }
- if end {
- if err := sc.hpackDecoder.Close(); err != nil {
- // TODO: convert to stream error I assume?
- return err
- }
- sc.curHeaderStreamID = 0
- // TODO: transition state
- }
- return nil
- }
- func (sc *serverConn) processContinuation(f *ContinuationFrame) error {
- return sc.processHeaderBlockFragment(f.HeaderBlockFragment(), f.HeadersEnded())
- }
- // ConfigureServer adds HTTP2 support to s as configured by the HTTP/2
- // server configuration in conf. The configuration 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)
- }
- haveNPN := false
- for _, p := range s.TLSConfig.NextProtos {
- if p == npnProto {
- haveNPN = true
- break
- }
- }
- if !haveNPN {
- s.TLSConfig.NextProtos = append(s.TLSConfig.NextProtos, npnProto)
- }
- if s.TLSNextProto == nil {
- s.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){}
- }
- s.TLSNextProto[npnProto] = func(hs *http.Server, c *tls.Conn, h http.Handler) {
- if testHookOnConn != nil {
- testHookOnConn()
- }
- conf.handleConn(hs, c, h)
- }
- }
- var testHookOnConn func() // for testing
|