From acc7161320038ee6377d589890526cd11912b4e8 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 7 Jan 2016 23:43:52 +0000 Subject: [PATCH] net/http: document GODEBUG settings, update bundled http2 Document the three GODEBUG environment variables in the package doc. Updates the bundled http2 to x/net git rev 415f1917 for https://golang.org/cl/18372. Fixes #13611 Change-Id: I3116c5d7de70d3d15242d7198f3758b1fb7d94b9 Reviewed-on: https://go-review.googlesource.com/18373 Reviewed-by: Andrew Gerrand Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/net/http/doc.go | 15 + src/net/http/h2_bundle.go | 323 ++++++++++++++---- src/net/http/server.go | 3 + src/net/http/transport.go | 2 +- .../golang.org/x/net/http2/hpack/hpack.go | 8 + 5 files changed, 283 insertions(+), 68 deletions(-) diff --git a/src/net/http/doc.go b/src/net/http/doc.go index b1216e8daf..4ec8272f62 100644 --- a/src/net/http/doc.go +++ b/src/net/http/doc.go @@ -76,5 +76,20 @@ custom Server: MaxHeaderBytes: 1 << 20, } log.Fatal(s.ListenAndServe()) + +The http package has transparent support for the HTTP/2 protocol when +using HTTPS. Programs that must disable HTTP/2 can do so by setting +Transport.TLSNextProto (for clients) or Server.TLSNextProto (for +servers) to a non-nil, empty map. Alternatively, the following GODEBUG +environment variables are currently supported: + + GODEBUG=http2client=0 # disable HTTP/2 client support + GODEBUG=http2server=0 # disable HTTP/2 server support + GODEBUG=http2debug=1 # enable verbose HTTP/2 debug logs + GODEBUG=http2debug=2 # ... even more verbose, with frame dumps + +The GODEBUG variables are not covered by Go's API compatibility promise. +HTTP/2 support was added in Go 1.6. Please report any issues instead of +disabling HTTP/2 support: https://golang.org/s/http2bug */ package http diff --git a/src/net/http/h2_bundle.go b/src/net/http/h2_bundle.go index 7e7b494d55..69fad37bec 100644 --- a/src/net/http/h2_bundle.go +++ b/src/net/http/h2_bundle.go @@ -583,6 +583,12 @@ func (h http2FrameHeader) Header() http2FrameHeader { return h } 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=") @@ -599,15 +605,14 @@ func (h http2FrameHeader) String() string { if name != "" { buf.WriteString(name) } else { - fmt.Fprintf(&buf, "0x%x", 1<>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 @@ -733,6 +745,22 @@ func (f *http2Framer) endWrite() error { 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...) } @@ -1563,6 +1591,49 @@ type http2headersEnder interface { 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" @@ -1784,7 +1855,21 @@ func http2lowerHeader(v string) string { 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 @@ -2374,7 +2459,7 @@ func http2isBadCipher(cipher uint16) bool { } 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() @@ -2559,7 +2644,9 @@ func (sc *http2serverConn) condlogf(err error, format string, args ...interface{ 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 @@ -2605,13 +2692,15 @@ func (sc *http2serverConn) onNewHeaderField(f hpack.HeaderField) { 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) @@ -2624,7 +2713,6 @@ func (st *http2stream) onNewTrailerField(f hpack.HeaderField) { if len(vv) >= tooBig { sc.hpackDecoder.SetEmitEnabled(false) } - } } } @@ -2733,7 +2821,9 @@ func (sc *http2serverConn) serve() { 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{ @@ -2745,7 +2835,7 @@ func (sc *http2serverConn) serve() { 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 } @@ -2808,7 +2898,9 @@ func (sc *http2serverConn) readPreface() error { 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 } @@ -3064,7 +3156,9 @@ func (sc *http2serverConn) processFrameFromReader(res http2readFrameResult) bool } } 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 @@ -3079,14 +3173,14 @@ func (sc *http2serverConn) processFrameFromReader(res http2readFrameResult) bool 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 } @@ -3123,7 +3217,7 @@ func (sc *http2serverConn) processFrame(f http2Frame) error { return http2ConnectionError(http2ErrCodeProtocol) default: - sc.vlogf("Ignoring frame: %v", f.Header()) + sc.vlogf("http2: server ignoring frame: %v", f.Header()) return nil } } @@ -3219,7 +3313,9 @@ func (sc *http2serverConn) processSetting(s http2Setting) error { 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 @@ -3236,6 +3332,9 @@ func (sc *http2serverConn) processSetting(s http2Setting) error { sc.peerMaxHeaderListSize = s.Val default: + if http2VerboseLogs { + sc.vlogf("http2: server ignoring unknown setting %v", s) + } } return nil } @@ -3384,6 +3483,10 @@ func (st *http2stream) processTrailerHeaders(f *http2HeadersFrame) error { return http2ConnectionError(http2ErrCodeProtocol) } st.gotTrailerHeader = true + if !f.StreamEnded() { + return http2StreamError{st.id, http2ErrCodeProtocol} + } + sc.resetPendingRequest() return st.processTrailerHeaderBlockFragment(f.HeaderBlockFragment(), f.HeadersEnded()) } @@ -3449,6 +3552,12 @@ func (st *http2stream) processTrailerHeaderBlockFragment(frag []byte, end bool) 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 { @@ -3571,7 +3680,6 @@ func (sc *http2serverConn) newWriterAndRequest() (*http2responseWriter, *Request requestURI = rp.authority } else { var err error - url_, err = url.ParseRequestURI(rp.path) if err != nil { return nil, nil, http2StreamError{rp.stream.id, http2ErrCodeProtocol} @@ -4128,10 +4236,29 @@ type http2Transport struct { // 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 @@ -4309,7 +4436,7 @@ func (t *http2Transport) RoundTripOpt(req *Request, opt http2RoundTripOpt) (*Res 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) @@ -4397,7 +4524,7 @@ func (t *http2Transport) dialTLSDefault(network, addr string, cfg *tls.Config) ( 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) @@ -4420,6 +4547,7 @@ func (t *http2Transport) NewClientConn(c net.Conn) (*http2ClientConn, error) { 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 { @@ -4430,10 +4558,14 @@ func (t *http2Transport) NewClientConn(c net.Conn) (*http2ClientConn, error) { 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() @@ -4870,6 +5002,9 @@ func (cc *http2ClientConn) encodeTrailers(req *Request) []byte { } 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}) } @@ -4923,6 +5058,7 @@ type http2clientConnReadLoop struct { 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. @@ -4931,7 +5067,6 @@ func (cc *http2ClientConn) readLoop() { cc: cc, activeRes: make(map[uint32]*http2clientStream), } - rl.hdec = hpack.NewDecoder(http2initialHeaderTableSize, rl.onNewHeaderField) defer rl.cleanup() @@ -4982,7 +5117,9 @@ func (rl *http2clientConnReadLoop) run() error { } 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: @@ -5016,11 +5153,13 @@ func (rl *http2clientConnReadLoop) processHeaders(f *http2HeadersFrame) error { 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()) } @@ -5037,7 +5176,7 @@ func (rl *http2clientConnReadLoop) processHeaderBlockFragment(frag []byte, strea 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) } @@ -5208,9 +5347,6 @@ func (rl *http2clientConnReadLoop) processData(f *http2DataFrame) error { return nil } data := f.Data() - if http2VerboseLogs { - rl.cc.logf("DATA: %q", data) - } cc.mu.Lock() if cs.inflow.available() >= int32(len(data)) { @@ -5231,9 +5367,17 @@ func (rl *http2clientConnReadLoop) processData(f *http2DataFrame) error { 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) } @@ -5343,20 +5487,57 @@ func (cc *http2ClientConn) writeStreamReset(streamID uint32, code http2ErrCode, 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) @@ -5370,39 +5551,43 @@ func (rl *http2clientConnReadLoop) onNewHeaderField(f hpack.HeaderField) { 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) } } @@ -5580,27 +5765,31 @@ type http2writeResHeaders struct { 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() @@ -5650,7 +5839,7 @@ type http2write100ContinueHeadersFrame struct { 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(), @@ -5686,7 +5875,7 @@ func http2encodeHeaders(enc *hpack.Encoder, h Header, keys []string) { if isTE && v != "trailers" { continue } - enc.WriteField(hpack.HeaderField{Name: k, Value: v}) + http2encKV(enc, k, v) } } } diff --git a/src/net/http/server.go b/src/net/http/server.go index 5bbd05e813..ac7086c21e 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -2267,6 +2267,9 @@ func (srv *Server) setupHTTP2() error { // configured otherwise. (by setting srv.TLSNextProto non-nil) // It must only be called via srv.nextProtoOnce (use srv.setupHTTP2). func (srv *Server) onceSetNextProtoDefaults() { + if strings.Contains(os.Getenv("GODEBUG"), "http2server=0") { + return + } // Enable HTTP/2 by default if the user hasn't otherwise // configured their TLSNextProto map. if srv.TLSNextProto == nil { diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 01e3a05a3a..6c08391766 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -151,7 +151,7 @@ type Transport struct { // onceSetNextProtoDefaults initializes TLSNextProto. // It must be called via t.nextProtoOnce.Do. func (t *Transport) onceSetNextProtoDefaults() { - if strings.Contains(os.Getenv("GODEBUG"), "h2client=0") { + if strings.Contains(os.Getenv("GODEBUG"), "http2client=0") { return } if t.TLSNextProto != nil { diff --git a/src/vendor/golang.org/x/net/http2/hpack/hpack.go b/src/vendor/golang.org/x/net/http2/hpack/hpack.go index 329a8d036d..2ea4949ab0 100644 --- a/src/vendor/golang.org/x/net/http2/hpack/hpack.go +++ b/src/vendor/golang.org/x/net/http2/hpack/hpack.go @@ -41,6 +41,14 @@ type HeaderField struct { Sensitive bool } +func (hf HeaderField) String() string { + var suffix string + if hf.Sensitive { + suffix = " (sensitive)" + } + return fmt.Sprintf("header field %q = %q%s", hf.Name, hf.Value, suffix) +} + 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 -- 2.48.1