"io"
"io/ioutil"
"log"
+ "math"
"net"
"net/http/httptrace"
"net/textproto"
type http2StreamError struct {
StreamID uint32
Code http2ErrCode
+ Cause error // optional additional detail
+}
+
+func http2streamError(id uint32, code http2ErrCode) http2StreamError {
+ return http2StreamError{StreamID: id, Code: code}
}
func (e http2StreamError) Error() string {
+ if e.Cause != nil {
+ return fmt.Sprintf("stream error: stream ID %d; %v; %v", e.StreamID, e.Code, e.Cause)
+ }
return fmt.Sprintf("stream error: stream ID %d; %v", e.StreamID, e.Code)
}
if fh.StreamID == 0 {
return nil, http2ConnectionError(http2ErrCodeProtocol)
}
- return nil, http2StreamError{fh.StreamID, http2ErrCodeProtocol}
+ return nil, http2streamError(fh.StreamID, http2ErrCodeProtocol)
}
return &http2WindowUpdateFrame{
http2FrameHeader: fh,
}
}
if len(p)-int(padLength) <= 0 {
- return nil, http2StreamError{fh.StreamID, http2ErrCodeProtocol}
+ return nil, http2streamError(fh.StreamID, http2ErrCodeProtocol)
}
hf.headerFragBuf = p[:len(p)-int(padLength)]
return hf, nil
hdec.SetEmitEnabled(true)
hdec.SetMaxStringLength(fr.maxHeaderStringLen())
hdec.SetEmitFunc(func(hf hpack.HeaderField) {
+ if http2VerboseLogs && http2logFrameReads {
+ log.Printf("http2: decoded hpack field %+v", hf)
+ }
if !httplex.ValidHeaderFieldValue(hf.Value) {
invalid = http2headerFieldValueError(hf.Value)
}
}
if invalid != nil {
fr.errDetail = invalid
- return nil, http2StreamError{mh.StreamID, http2ErrCodeProtocol}
+ if http2VerboseLogs {
+ log.Printf("http2: invalid header: %v", invalid)
+ }
+ return nil, http2StreamError{mh.StreamID, http2ErrCodeProtocol, invalid}
}
if err := mh.checkPseudos(); err != nil {
fr.errDetail = err
- return nil, http2StreamError{mh.StreamID, http2ErrCodeProtocol}
+ if http2VerboseLogs {
+ log.Printf("http2: invalid pseudo headers: %v", err)
+ }
+ return nil, http2StreamError{mh.StreamID, http2ErrCodeProtocol, err}
}
return mh, nil
}
case http2stateOpen:
st.state = http2stateHalfClosedLocal
- errCancel := http2StreamError{st.id, http2ErrCodeCancel}
+ errCancel := http2streamError(st.id, http2ErrCodeCancel)
sc.resetStream(errCancel)
case http2stateHalfClosedRemote:
sc.closeStream(st, http2errHandlerComplete)
return nil
}
if !st.flow.add(int32(f.Increment)) {
- return http2StreamError{f.StreamID, http2ErrCodeFlowControl}
+ return http2streamError(f.StreamID, http2ErrCodeFlowControl)
}
default:
if !sc.flow.add(int32(f.Increment)) {
if st != nil {
st.gotReset = true
st.cancelCtx()
- sc.closeStream(st, http2StreamError{f.StreamID, f.ErrCode})
+ sc.closeStream(st, http2streamError(f.StreamID, f.ErrCode))
}
return nil
}
if !ok || st.state != http2stateOpen || st.gotTrailerHeader {
if sc.inflow.available() < int32(f.Length) {
- return http2StreamError{id, http2ErrCodeFlowControl}
+ return http2streamError(id, http2ErrCodeFlowControl)
}
sc.inflow.take(int32(f.Length))
sc.sendWindowUpdate(nil, int(f.Length))
- return http2StreamError{id, http2ErrCodeStreamClosed}
+ return http2streamError(id, http2ErrCodeStreamClosed)
}
if st.body == nil {
panic("internal error: should have a body in this state")
if st.declBodyBytes != -1 && st.bodyBytes+int64(len(data)) > st.declBodyBytes {
st.body.CloseWithError(fmt.Errorf("sender tried to send more than declared Content-Length of %d bytes", st.declBodyBytes))
- return http2StreamError{id, http2ErrCodeStreamClosed}
+ return http2streamError(id, http2ErrCodeStreamClosed)
}
if f.Length > 0 {
if st.inflow.available() < int32(f.Length) {
- return http2StreamError{id, http2ErrCodeFlowControl}
+ return http2streamError(id, http2ErrCodeFlowControl)
}
st.inflow.take(int32(f.Length))
if len(data) > 0 {
wrote, err := st.body.Write(data)
if err != nil {
- return http2StreamError{id, http2ErrCodeStreamClosed}
+ return http2streamError(id, http2ErrCodeStreamClosed)
}
if wrote != len(data) {
panic("internal error: bad Writer")
if sc.unackedSettings == 0 {
- return http2StreamError{st.id, http2ErrCodeProtocol}
+ return http2streamError(st.id, http2ErrCodeProtocol)
}
- return http2StreamError{st.id, http2ErrCodeRefusedStream}
+ return http2streamError(st.id, http2ErrCodeRefusedStream)
}
rw, req, err := sc.newWriterAndRequest(st, f)
}
st.gotTrailerHeader = true
if !f.StreamEnded() {
- return http2StreamError{st.id, http2ErrCodeProtocol}
+ return http2streamError(st.id, http2ErrCodeProtocol)
}
if len(f.PseudoFields()) > 0 {
- return http2StreamError{st.id, http2ErrCodeProtocol}
+ return http2streamError(st.id, http2ErrCodeProtocol)
}
if st.trailer != nil {
for _, hf := range f.RegularFields() {
key := sc.canonicalHeader(hf.Name)
if !http2ValidTrailerHeader(key) {
- return http2StreamError{st.id, http2ErrCodeProtocol}
+ return http2streamError(st.id, http2ErrCodeProtocol)
}
st.trailer[key] = append(st.trailer[key], hf.Value)
}
isConnect := method == "CONNECT"
if isConnect {
if path != "" || scheme != "" || authority == "" {
- return nil, nil, http2StreamError{f.StreamID, http2ErrCodeProtocol}
+ return nil, nil, http2streamError(f.StreamID, http2ErrCodeProtocol)
}
} else if method == "" || path == "" ||
(scheme != "https" && scheme != "http") {
- return nil, nil, http2StreamError{f.StreamID, http2ErrCodeProtocol}
+ return nil, nil, http2streamError(f.StreamID, http2ErrCodeProtocol)
}
bodyOpen := !f.StreamEnded()
if method == "HEAD" && bodyOpen {
- return nil, nil, http2StreamError{f.StreamID, http2ErrCodeProtocol}
+ return nil, nil, http2streamError(f.StreamID, http2ErrCodeProtocol)
}
var tlsState *tls.ConnectionState // nil if not scheme https
var err error
url_, err = url.ParseRequestURI(path)
if err != nil {
- return nil, nil, http2StreamError{f.StreamID, http2ErrCodeProtocol}
+ return nil, nil, http2streamError(f.StreamID, http2ErrCodeProtocol)
}
requestURI = path
}
br *bufio.Reader
fr *http2Framer
lastActive time.Time
-
- // Settings from peer:
+ // Settings from peer: (also guarded by mu)
maxFrameSize uint32
maxConcurrentStreams uint32
initialWindowSize uint32
- hbuf bytes.Buffer // HPACK encoder writes into this
- henc *hpack.Encoder
- freeBuf [][]byte
+
+ hbuf bytes.Buffer // HPACK encoder writes into this
+ henc *hpack.Encoder
+ freeBuf [][]byte
wmu sync.Mutex // held while writing; acquire AFTER mu if holding both
werr error // first write error that has occurred
}
func (t *http2Transport) newClientConn(c net.Conn, singleUse bool) (*http2ClientConn, error) {
- if http2VerboseLogs {
- t.vlogf("http2: Transport creating client conn to %v", c.RemoteAddr())
- }
-
cc := &http2ClientConn{
t: t,
tconn: c,
singleUse: singleUse,
wantSettingsAck: true,
}
+ if http2VerboseLogs {
+ t.vlogf("http2: Transport creating client conn %p to %v", cc, c.RemoteAddr())
+ }
+
cc.cond = sync.NewCond(&cc.mu)
cc.flow.add(int32(http2initialWindowSize))
}
return cc.goAway == nil && !cc.closed &&
int64(len(cc.streams)+1) < int64(cc.maxConcurrentStreams) &&
- cc.nextStreamID < 2147483647
+ cc.nextStreamID < math.MaxInt32
}
func (cc *http2ClientConn) closeIfIdle() {
return
}
cc.closed = true
+ nextID := cc.nextStreamID
cc.mu.Unlock()
+ if http2VerboseLogs {
+ cc.vlogf("http2: Transport closing idle conn %p (forSingleUse=%v, maxStream=%v)", cc, cc.singleUse, nextID-2)
+ }
cc.tconn.Close()
}
for {
f, err := cc.fr.ReadFrame()
if err != nil {
- cc.vlogf("Transport readFrame error: (%T) %v", err, err)
+ cc.vlogf("http2: Transport readFrame error on conn %p: (%T) %v", cc, err, err)
}
if se, ok := err.(http2StreamError); ok {
if cs := cc.streamByID(se.StreamID, true); cs != nil {
- rl.endStreamError(cs, cc.fr.errDetail)
+ cs.cc.writeStreamReset(cs.ID, se.Code, err)
+ if se.Cause == nil {
+ se.Cause = cc.fr.errDetail
+ }
+ rl.endStreamError(cs, se)
}
continue
} else if err != nil {
cc.logf("Transport: unhandled response frame type %T", f)
}
if err != nil {
+ if http2VerboseLogs {
+ cc.vlogf("http2: Transport conn %p received error from processing frame %v: %v", cc, http2summarizeFrame(f), err)
+ }
return err
}
if rl.closeWhenIdle && gotReply && maybeIdle && len(rl.activeRes) == 0 {
if http2isConnectionCloseRequest(cs.req) {
rl.closeWhenIdle = true
}
+
+ select {
+ case cs.resc <- http2resAndError{err: err}:
+ default:
+ }
}
func (cs *http2clientStream) copyTrailers() {
cc.maxConcurrentStreams = s.Val
case http2SettingInitialWindowSize:
+ if s.Val > math.MaxInt32 {
+ return http2ConnectionError(http2ErrCodeFlowControl)
+ }
+
+ delta := int32(s.Val) - int32(cc.initialWindowSize)
+ for _, cs := range cc.streams {
+ cs.flow.add(delta)
+ }
+ cc.cond.Broadcast()
+
cc.initialWindowSize = s.Val
default:
case <-cs.peerReset:
default:
- err := http2StreamError{cs.ID, f.ErrCode}
+ err := http2streamError(cs.ID, f.ErrCode)
cs.resetErr = err
close(cs.peerReset)
cs.bufPipe.CloseWithError(err)