func (h http2FrameHeader) String() string {
var buf bytes.Buffer
buf.WriteString("[FrameHeader ")
+ h.writeDebug(&buf)
+ buf.WriteByte(']')
+ return buf.String()
+}
+
+func (h http2FrameHeader) writeDebug(buf *bytes.Buffer) {
buf.WriteString(h.Type.String())
if h.Flags != 0 {
buf.WriteString(" flags=")
if name != "" {
buf.WriteString(name)
} else {
- fmt.Fprintf(&buf, "0x%x", 1<<i)
+ fmt.Fprintf(buf, "0x%x", 1<<i)
}
}
}
if h.StreamID != 0 {
- fmt.Fprintf(&buf, " stream=%d", h.StreamID)
+ fmt.Fprintf(buf, " stream=%d", h.StreamID)
}
- fmt.Fprintf(&buf, " len=%d]", h.Length)
- return buf.String()
+ fmt.Fprintf(buf, " len=%d", h.Length)
}
func (h *http2FrameHeader) checkValid() {
// This is for testing and permits using the Framer to test
// other HTTP/2 implementations' conformance to the spec.
AllowIllegalReads bool
+
+ debugFramer *http2Framer // only use for logging written writes
+ debugFramerBuf *bytes.Buffer
}
func (f *http2Framer) startWrite(ftype http2FrameType, flags http2Flags, streamID uint32) {
byte(length>>16),
byte(length>>8),
byte(length))
+ if http2logFrameWrites {
+ f.logWrite()
+ }
+
n, err := f.w.Write(f.wbuf)
if err == nil && n != len(f.wbuf) {
err = io.ErrShortWrite
return err
}
+func (f *http2Framer) logWrite() {
+ if f.debugFramer == nil {
+ f.debugFramerBuf = new(bytes.Buffer)
+ f.debugFramer = http2NewFramer(nil, f.debugFramerBuf)
+
+ f.debugFramer.AllowIllegalReads = true
+ }
+ f.debugFramerBuf.Write(f.wbuf)
+ fr, err := f.debugFramer.ReadFrame()
+ if err != nil {
+ log.Printf("http2: Framer %p: failed to decode just-written frame", f)
+ return
+ }
+ log.Printf("http2: Framer %p: wrote %v", f, http2summarizeFrame(fr))
+}
+
func (f *http2Framer) writeByte(v byte) { f.wbuf = append(f.wbuf, v) }
func (f *http2Framer) writeBytes(v []byte) { f.wbuf = append(f.wbuf, v...) }
HeadersEnded() bool
}
+func http2summarizeFrame(f http2Frame) string {
+ var buf bytes.Buffer
+ f.Header().writeDebug(&buf)
+ switch f := f.(type) {
+ case *http2SettingsFrame:
+ n := 0
+ f.ForeachSetting(func(s http2Setting) error {
+ n++
+ if n == 1 {
+ buf.WriteString(", settings:")
+ }
+ fmt.Fprintf(&buf, " %v=%v,", s.ID, s.Val)
+ return nil
+ })
+ if n > 0 {
+ buf.Truncate(buf.Len() - 1)
+ }
+ case *http2DataFrame:
+ data := f.Data()
+ const max = 256
+ if len(data) > max {
+ data = data[:max]
+ }
+ fmt.Fprintf(&buf, " data=%q", data)
+ if len(f.Data()) > max {
+ fmt.Fprintf(&buf, " (%d bytes omitted)", len(f.Data())-max)
+ }
+ case *http2WindowUpdateFrame:
+ if f.StreamID == 0 {
+ buf.WriteString(" (conn)")
+ }
+ fmt.Fprintf(&buf, " incr=%v", f.Increment)
+ case *http2PingFrame:
+ fmt.Fprintf(&buf, " ping=%q", f.Data[:])
+ case *http2GoAwayFrame:
+ fmt.Fprintf(&buf, " LastStreamID=%v ErrCode=%v Debug=%q",
+ f.LastStreamID, f.ErrCode, f.debugData)
+ case *http2RSTStreamFrame:
+ fmt.Fprintf(&buf, " ErrCode=%v", f.ErrCode)
+ }
+ return buf.String()
+}
+
func http2requestCancel(req *Request) <-chan struct{} { return req.Cancel }
var http2DebugGoroutines = os.Getenv("DEBUG_HTTP2_GOROUTINES") == "1"
return strings.ToLower(v)
}
-var http2VerboseLogs = strings.Contains(os.Getenv("GODEBUG"), "h2debug=1")
+var (
+ http2VerboseLogs bool
+ http2logFrameWrites bool
+)
+
+func init() {
+ e := os.Getenv("GODEBUG")
+ if strings.Contains(e, "http2debug=1") {
+ http2VerboseLogs = true
+ }
+ if strings.Contains(e, "http2debug=2") {
+ http2VerboseLogs = true
+ http2logFrameWrites = true
+ }
+}
const (
// ClientPreface is the string that must be sent by new
}
func (sc *http2serverConn) rejectConn(err http2ErrCode, debug string) {
- sc.vlogf("REJECTING conn: %v, %s", err, debug)
+ sc.vlogf("http2: server rejecting conn: %v, %s", err, debug)
sc.framer.WriteGoAway(0, err, []byte(debug))
sc.bw.Flush()
func (sc *http2serverConn) onNewHeaderField(f hpack.HeaderField) {
sc.serveG.check()
- sc.vlogf("got header field %+v", f)
+ if http2VerboseLogs {
+ sc.vlogf("http2: server decoded %v", f)
+ }
switch {
case !http2validHeader(f.Name):
sc.req.invalidHeader = true
func (st *http2stream) onNewTrailerField(f hpack.HeaderField) {
sc := st.sc
sc.serveG.check()
- sc.vlogf("got trailer field %+v", f)
+ if http2VerboseLogs {
+ sc.vlogf("http2: server decoded trailer %v", f)
+ }
switch {
case !http2validHeader(f.Name):
-
+ sc.req.invalidHeader = true
return
case strings.HasPrefix(f.Name, ":"):
-
+ sc.req.invalidHeader = true
return
default:
key := sc.canonicalHeader(f.Name)
if len(vv) >= tooBig {
sc.hpackDecoder.SetEmitEnabled(false)
}
-
}
}
}
defer sc.stopShutdownTimer()
defer close(sc.doneServing)
- sc.vlogf("HTTP/2 connection from %v on %p", sc.conn.RemoteAddr(), sc.hs)
+ if http2VerboseLogs {
+ sc.vlogf("http2: server connection from %v on %p", sc.conn.RemoteAddr(), sc.hs)
+ }
sc.writeFrame(http2frameWriteMsg{
write: http2writeSettings{
sc.unackedSettings++
if err := sc.readPreface(); err != nil {
- sc.condlogf(err, "error reading preface from client %v: %v", sc.conn.RemoteAddr(), err)
+ sc.condlogf(err, "http2: server: error reading preface from client %v: %v", sc.conn.RemoteAddr(), err)
return
}
return errors.New("timeout waiting for client preface")
case err := <-errc:
if err == nil {
- sc.vlogf("client %v said hello", sc.conn.RemoteAddr())
+ if http2VerboseLogs {
+ sc.vlogf("http2: server: client %v said hello", sc.conn.RemoteAddr())
+ }
}
return err
}
}
} else {
f := res.f
- sc.vlogf("got %v: %#v", f.Header(), f)
+ if http2VerboseLogs {
+ sc.vlogf("http2: server read frame %v", http2summarizeFrame(f))
+ }
err = sc.processFrame(f)
if err == nil {
return true
sc.goAway(http2ErrCodeFlowControl)
return true
case http2ConnectionError:
- sc.logf("%v: %v", sc.conn.RemoteAddr(), ev)
+ sc.logf("http2: server connection error from %v: %v", sc.conn.RemoteAddr(), ev)
sc.goAway(http2ErrCode(ev))
return true
default:
if res.err != nil {
- sc.logf("disconnecting; error reading frame from client %s: %v", sc.conn.RemoteAddr(), err)
+ sc.logf("http2: server closing client connection; error reading frame from client %s: %v", sc.conn.RemoteAddr(), err)
} else {
- sc.logf("disconnection due to other error: %v", err)
+ sc.logf("http2: server closing client connection: %v", err)
}
return false
}
return http2ConnectionError(http2ErrCodeProtocol)
default:
- sc.vlogf("Ignoring frame: %v", f.Header())
+ sc.vlogf("http2: server ignoring frame: %v", f.Header())
return nil
}
}
if err := s.Valid(); err != nil {
return err
}
- sc.vlogf("processing setting %v", s)
+ if http2VerboseLogs {
+ sc.vlogf("http2: server processing setting %v", s)
+ }
switch s.ID {
case http2SettingHeaderTableSize:
sc.headerTableSize = s.Val
sc.peerMaxHeaderListSize = s.Val
default:
+ if http2VerboseLogs {
+ sc.vlogf("http2: server ignoring unknown setting %v", s)
+ }
}
return nil
}
return http2ConnectionError(http2ErrCodeProtocol)
}
st.gotTrailerHeader = true
+ if !f.StreamEnded() {
+ return http2StreamError{st.id, http2ErrCodeProtocol}
+ }
+ sc.resetPendingRequest()
return st.processTrailerHeaderBlockFragment(f.HeaderBlockFragment(), f.HeadersEnded())
}
if !end {
return nil
}
+
+ rp := &sc.req
+ if rp.invalidHeader {
+ return http2StreamError{rp.stream.id, http2ErrCodeProtocol}
+ }
+
err := sc.hpackDecoder.Close()
st.endStream()
if err != nil {
requestURI = rp.authority
} else {
var err error
-
url_, err = url.ParseRequestURI(rp.path)
if err != nil {
return nil, nil, http2StreamError{rp.stream.id, http2ErrCodeProtocol}
// uncompressed.
DisableCompression bool
+ // MaxHeaderListSize is the http2 SETTINGS_MAX_HEADER_LIST_SIZE to
+ // send in the initial settings frame. It is how many bytes
+ // of response headers are allow. Unlike the http2 spec, zero here
+ // means to use a default limit (currently 10MB). If you actually
+ // want to advertise an ulimited value to the peer, Transport
+ // interprets the highest possible value here (0xffffffff or 1<<32-1)
+ // to mean no limit.
+ MaxHeaderListSize uint32
+
connPoolOnce sync.Once
connPoolOrDef http2ClientConnPool // non-nil version of ConnPool
}
+func (t *http2Transport) maxHeaderListSize() uint32 {
+ if t.MaxHeaderListSize == 0 {
+ return 10 << 20
+ }
+ if t.MaxHeaderListSize == 0xffffffff {
+ return 0
+ }
+ return t.MaxHeaderListSize
+}
+
func (t *http2Transport) disableCompression() bool {
if t.DisableCompression {
return true
for {
cc, err := t.connPool().GetClientConn(req, addr)
if err != nil {
- t.vlogf("failed to get client conn: %v", err)
+ t.vlogf("http2: Transport failed to get client conn for %s: %v", addr, err)
return nil, err
}
res, err := cc.RoundTrip(req)
func (t *http2Transport) NewClientConn(c net.Conn) (*http2ClientConn, error) {
if http2VerboseLogs {
- t.vlogf("creating client conn to %v", c.RemoteAddr())
+ t.vlogf("http2: Transport creating client conn to %v", c.RemoteAddr())
}
if _, err := c.Write(http2clientPreface); err != nil {
t.vlogf("client preface write error: %v", err)
cc.bw = bufio.NewWriter(http2stickyErrWriter{c, &cc.werr})
cc.br = bufio.NewReader(c)
cc.fr = http2NewFramer(cc.bw, cc.br)
+
cc.henc = hpack.NewEncoder(&cc.hbuf)
type connectionStater interface {
cc.tlsState = &state
}
- cc.fr.WriteSettings(
+ initialSettings := []http2Setting{
http2Setting{ID: http2SettingEnablePush, Val: 0},
http2Setting{ID: http2SettingInitialWindowSize, Val: http2transportDefaultStreamFlow},
- )
+ }
+ if max := t.maxHeaderListSize(); max != 0 {
+ initialSettings = append(initialSettings, http2Setting{ID: http2SettingMaxHeaderListSize, Val: max})
+ }
+ cc.fr.WriteSettings(initialSettings...)
cc.fr.WriteWindowUpdate(0, http2transportDefaultConnFlow)
cc.inflow.add(http2transportDefaultConnFlow + http2initialWindowSize)
cc.bw.Flush()
}
func (cc *http2ClientConn) writeHeader(name, value string) {
+ if http2VerboseLogs {
+ log.Printf("http2: Transport encoding header %q = %q", name, value)
+ }
cc.henc.WriteField(hpack.HeaderField{Name: name, Value: value})
}
sawRegHeader bool // saw non-pseudo header
reqMalformed error // non-nil once known to be malformed
lastHeaderEndsStream bool
+ headerListSize int64 // actually uint32, but easier math this way
}
// readLoop runs in its own goroutine and reads and dispatches frames.
cc: cc,
activeRes: make(map[uint32]*http2clientStream),
}
-
rl.hdec = hpack.NewDecoder(http2initialHeaderTableSize, rl.onNewHeaderField)
defer rl.cleanup()
} else if err != nil {
return err
}
- cc.vlogf("Transport received %v: %#v", f.Header(), f)
+ if http2VerboseLogs {
+ cc.vlogf("http2: Transport received %s", http2summarizeFrame(f))
+ }
switch f := f.(type) {
case *http2HeadersFrame:
rl.sawRegHeader = false
rl.reqMalformed = nil
rl.lastHeaderEndsStream = f.StreamEnded()
+ rl.headerListSize = 0
rl.nextRes = &Response{
Proto: "HTTP/2.0",
ProtoMajor: 2,
Header: make(Header),
}
+ rl.hdec.SetEmitEnabled(true)
return rl.processHeaderBlockFragment(f.HeaderBlockFragment(), f.StreamID, f.HeadersEnded())
}
return nil
}
if cs.pastHeaders {
- rl.hdec.SetEmitFunc(cs.onNewTrailerField)
+ rl.hdec.SetEmitFunc(func(f hpack.HeaderField) { rl.onNewTrailerField(cs, f) })
} else {
rl.hdec.SetEmitFunc(rl.onNewHeaderField)
}
return nil
}
data := f.Data()
- if http2VerboseLogs {
- rl.cc.logf("DATA: %q", data)
- }
cc.mu.Lock()
if cs.inflow.available() >= int32(len(data)) {
return nil
}
+var http2errInvalidTrailers = errors.New("http2: invalid trailers")
+
func (rl *http2clientConnReadLoop) endStream(cs *http2clientStream) {
- cs.bufPipe.closeWithErrorAndCode(io.EOF, cs.copyTrailers)
+ err := io.EOF
+ code := cs.copyTrailers
+ if rl.reqMalformed != nil {
+ err = rl.reqMalformed
+ code = nil
+ }
+ cs.bufPipe.closeWithErrorAndCode(err, code)
delete(rl.activeRes, cs.ID)
}
cc.wmu.Unlock()
}
+var (
+ http2errResponseHeaderListSize = errors.New("http2: response header list larger than advertised limit")
+ http2errInvalidHeaderKey = errors.New("http2: invalid header key")
+ http2errPseudoTrailers = errors.New("http2: invalid pseudo header in trailers")
+)
+
+func (rl *http2clientConnReadLoop) checkHeaderField(f hpack.HeaderField) bool {
+ if rl.reqMalformed != nil {
+ return false
+ }
+
+ const headerFieldOverhead = 32 // per spec
+ rl.headerListSize += int64(len(f.Name)) + int64(len(f.Value)) + headerFieldOverhead
+ if max := rl.cc.t.maxHeaderListSize(); max != 0 && rl.headerListSize > int64(max) {
+ rl.hdec.SetEmitEnabled(false)
+ rl.reqMalformed = http2errResponseHeaderListSize
+ return false
+ }
+
+ if !http2validHeader(f.Name) {
+ rl.reqMalformed = http2errInvalidHeaderKey
+ return false
+ }
+
+ isPseudo := strings.HasPrefix(f.Name, ":")
+ if isPseudo {
+ if rl.sawRegHeader {
+ rl.reqMalformed = errors.New("http2: invalid pseudo header after regular header")
+ return false
+ }
+ } else {
+ rl.sawRegHeader = true
+ }
+
+ return true
+}
+
// onNewHeaderField runs on the readLoop goroutine whenever a new
// hpack header field is decoded.
func (rl *http2clientConnReadLoop) onNewHeaderField(f hpack.HeaderField) {
cc := rl.cc
if http2VerboseLogs {
- cc.logf("Header field: %+v", f)
+ cc.logf("http2: Transport decoded %v", f)
+ }
+
+ if !rl.checkHeaderField(f) {
+ return
}
isPseudo := strings.HasPrefix(f.Name, ":")
if isPseudo {
- if rl.sawRegHeader {
- rl.reqMalformed = errors.New("http2: invalid pseudo header after regular header")
- return
- }
switch f.Name {
case ":status":
code, err := strconv.Atoi(f.Value)
rl.reqMalformed = fmt.Errorf("http2: unknown response pseudo header %q", f.Name)
}
+ return
+ }
+
+ key := CanonicalHeaderKey(f.Name)
+ if key == "Trailer" {
+ t := rl.nextRes.Trailer
+ if t == nil {
+ t = make(Header)
+ rl.nextRes.Trailer = t
+ }
+ http2foreachHeaderElement(f.Value, func(v string) {
+ t[CanonicalHeaderKey(v)] = nil
+ })
} else {
- rl.sawRegHeader = true
- key := CanonicalHeaderKey(f.Name)
- if key == "Trailer" {
- t := rl.nextRes.Trailer
- if t == nil {
- t = make(Header)
- rl.nextRes.Trailer = t
- }
- http2foreachHeaderElement(f.Value, func(v string) {
- t[CanonicalHeaderKey(v)] = nil
- })
- } else {
- rl.nextRes.Header.Add(key, f.Value)
- }
+ rl.nextRes.Header.Add(key, f.Value)
}
}
-func (cs *http2clientStream) onNewTrailerField(f hpack.HeaderField) {
- isPseudo := strings.HasPrefix(f.Name, ":")
- if isPseudo {
+func (rl *http2clientConnReadLoop) onNewTrailerField(cs *http2clientStream, f hpack.HeaderField) {
+ if http2VerboseLogs {
+ rl.cc.logf("http2: Transport decoded trailer %v", f)
+ }
+ if !rl.checkHeaderField(f) {
+ return
+ }
+ if strings.HasPrefix(f.Name, ":") {
+ rl.reqMalformed = http2errPseudoTrailers
return
}
+
key := CanonicalHeaderKey(f.Name)
if _, ok := cs.resTrailer[key]; ok {
if cs.trailer == nil {
cs.trailer = make(Header)
}
- const tooBig = 1000 // TODO: arbitrary; use max header list size limits
- if cur := cs.trailer[key]; len(cur) < tooBig {
- cs.trailer[key] = append(cur, f.Value)
- }
+ cs.trailer[key] = append(cs.trailer[key], f.Value)
}
}
contentLength string
}
+func http2encKV(enc *hpack.Encoder, k, v string) {
+ if http2VerboseLogs {
+ log.Printf("http2: server encoding header %q = %q", k, v)
+ }
+ enc.WriteField(hpack.HeaderField{Name: k, Value: v})
+}
+
func (w *http2writeResHeaders) writeFrame(ctx http2writeContext) error {
enc, buf := ctx.HeaderEncoder()
buf.Reset()
if w.httpResCode != 0 {
- enc.WriteField(hpack.HeaderField{
- Name: ":status",
- Value: http2httpCodeString(w.httpResCode),
- })
+ http2encKV(enc, ":status", http2httpCodeString(w.httpResCode))
}
http2encodeHeaders(enc, w.h, w.trailers)
if w.contentType != "" {
- enc.WriteField(hpack.HeaderField{Name: "content-type", Value: w.contentType})
+ http2encKV(enc, "content-type", w.contentType)
}
if w.contentLength != "" {
- enc.WriteField(hpack.HeaderField{Name: "content-length", Value: w.contentLength})
+ http2encKV(enc, "content-length", w.contentLength)
}
if w.date != "" {
- enc.WriteField(hpack.HeaderField{Name: "date", Value: w.date})
+ http2encKV(enc, "date", w.date)
}
headerBlock := buf.Bytes()
func (w http2write100ContinueHeadersFrame) writeFrame(ctx http2writeContext) error {
enc, buf := ctx.HeaderEncoder()
buf.Reset()
- enc.WriteField(hpack.HeaderField{Name: ":status", Value: "100"})
+ http2encKV(enc, ":status", "100")
return ctx.Framer().WriteHeaders(http2HeadersFrameParam{
StreamID: w.streamID,
BlockFragment: buf.Bytes(),
if isTE && v != "trailers" {
continue
}
- enc.WriteField(hpack.HeaderField{Name: k, Value: v})
+ http2encKV(enc, k, v)
}
}
}