Skip to content

Commit 3c21e5b

Browse files
committed
http2: add Server.CountError hook for error metrics
Change-Id: Ieaa2e97a2c467a3cf0753fcbf1a143f7bf76ef29 Reviewed-on: https://go-review.googlesource.com/c/net/+/350649 Run-TryBot: Brad Fitzpatrick <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Damien Neil <[email protected]> Trust: Tobias Klauser <[email protected]>
1 parent 12bc252 commit 3c21e5b

File tree

1 file changed

+65
-31
lines changed

1 file changed

+65
-31
lines changed

http2/server.go

+65-31
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ type Server struct {
130130
// If nil, a default scheduler is chosen.
131131
NewWriteScheduler func() WriteScheduler
132132

133+
// CountError, if non-nil, is called on HTTP/2 server errors.
134+
// It's intended to increment a metric for monitoring, such
135+
// as an expvar or Prometheus metric.
136+
// The errType consists of only ASCII word characters.
137+
CountError func(errType string)
138+
133139
// Internal state. This is a pointer (rather than embedded directly)
134140
// so that we don't embed a Mutex in this struct, which will make the
135141
// struct non-copyable, which might break some callers.
@@ -1399,7 +1405,7 @@ func (sc *serverConn) processFrame(f Frame) error {
13991405
// First frame received must be SETTINGS.
14001406
if !sc.sawFirstSettings {
14011407
if _, ok := f.(*SettingsFrame); !ok {
1402-
return ConnectionError(ErrCodeProtocol)
1408+
return sc.countError("first_settings", ConnectionError(ErrCodeProtocol))
14031409
}
14041410
sc.sawFirstSettings = true
14051411
}
@@ -1424,7 +1430,7 @@ func (sc *serverConn) processFrame(f Frame) error {
14241430
case *PushPromiseFrame:
14251431
// A client cannot push. Thus, servers MUST treat the receipt of a PUSH_PROMISE
14261432
// frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
1427-
return ConnectionError(ErrCodeProtocol)
1433+
return sc.countError("push_promise", ConnectionError(ErrCodeProtocol))
14281434
default:
14291435
sc.vlogf("http2: server ignoring frame: %v", f.Header())
14301436
return nil
@@ -1444,7 +1450,7 @@ func (sc *serverConn) processPing(f *PingFrame) error {
14441450
// identifier field value other than 0x0, the recipient MUST
14451451
// respond with a connection error (Section 5.4.1) of type
14461452
// PROTOCOL_ERROR."
1447-
return ConnectionError(ErrCodeProtocol)
1453+
return sc.countError("ping_on_stream", ConnectionError(ErrCodeProtocol))
14481454
}
14491455
if sc.inGoAway && sc.goAwayCode != ErrCodeNo {
14501456
return nil
@@ -1463,7 +1469,7 @@ func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error {
14631469
// or PRIORITY on a stream in this state MUST be
14641470
// treated as a connection error (Section 5.4.1) of
14651471
// type PROTOCOL_ERROR."
1466-
return ConnectionError(ErrCodeProtocol)
1472+
return sc.countError("stream_idle", ConnectionError(ErrCodeProtocol))
14671473
}
14681474
if st == nil {
14691475
// "WINDOW_UPDATE can be sent by a peer that has sent a
@@ -1474,7 +1480,7 @@ func (sc *serverConn) processWindowUpdate(f *WindowUpdateFrame) error {
14741480
return nil
14751481
}
14761482
if !st.flow.add(int32(f.Increment)) {
1477-
return streamError(f.StreamID, ErrCodeFlowControl)
1483+
return sc.countError("bad_flow", streamError(f.StreamID, ErrCodeFlowControl))
14781484
}
14791485
default: // connection-level flow control
14801486
if !sc.flow.add(int32(f.Increment)) {
@@ -1495,7 +1501,7 @@ func (sc *serverConn) processResetStream(f *RSTStreamFrame) error {
14951501
// identifying an idle stream is received, the
14961502
// recipient MUST treat this as a connection error
14971503
// (Section 5.4.1) of type PROTOCOL_ERROR.
1498-
return ConnectionError(ErrCodeProtocol)
1504+
return sc.countError("reset_idle_stream", ConnectionError(ErrCodeProtocol))
14991505
}
15001506
if st != nil {
15011507
st.cancelCtx()
@@ -1547,15 +1553,15 @@ func (sc *serverConn) processSettings(f *SettingsFrame) error {
15471553
// Why is the peer ACKing settings we never sent?
15481554
// The spec doesn't mention this case, but
15491555
// hang up on them anyway.
1550-
return ConnectionError(ErrCodeProtocol)
1556+
return sc.countError("ack_mystery", ConnectionError(ErrCodeProtocol))
15511557
}
15521558
return nil
15531559
}
15541560
if f.NumSettings() > 100 || f.HasDuplicates() {
15551561
// This isn't actually in the spec, but hang up on
15561562
// suspiciously large settings frames or those with
15571563
// duplicate entries.
1558-
return ConnectionError(ErrCodeProtocol)
1564+
return sc.countError("settings_big_or_dups", ConnectionError(ErrCodeProtocol))
15591565
}
15601566
if err := f.ForeachSetting(sc.processSetting); err != nil {
15611567
return err
@@ -1622,7 +1628,7 @@ func (sc *serverConn) processSettingInitialWindowSize(val uint32) error {
16221628
// control window to exceed the maximum size as a
16231629
// connection error (Section 5.4.1) of type
16241630
// FLOW_CONTROL_ERROR."
1625-
return ConnectionError(ErrCodeFlowControl)
1631+
return sc.countError("setting_win_size", ConnectionError(ErrCodeFlowControl))
16261632
}
16271633
}
16281634
return nil
@@ -1655,7 +1661,7 @@ func (sc *serverConn) processData(f *DataFrame) error {
16551661
// or PRIORITY on a stream in this state MUST be
16561662
// treated as a connection error (Section 5.4.1) of
16571663
// type PROTOCOL_ERROR."
1658-
return ConnectionError(ErrCodeProtocol)
1664+
return sc.countError("data_on_idle", ConnectionError(ErrCodeProtocol))
16591665
}
16601666

16611667
// "If a DATA frame is received whose stream is not in "open"
@@ -1672,7 +1678,7 @@ func (sc *serverConn) processData(f *DataFrame) error {
16721678
// and return any flow control bytes since we're not going
16731679
// to consume them.
16741680
if sc.inflow.available() < int32(f.Length) {
1675-
return streamError(id, ErrCodeFlowControl)
1681+
return sc.countError("data_flow", streamError(id, ErrCodeFlowControl))
16761682
}
16771683
// Deduct the flow control from inflow, since we're
16781684
// going to immediately add it back in
@@ -1685,7 +1691,7 @@ func (sc *serverConn) processData(f *DataFrame) error {
16851691
// Already have a stream error in flight. Don't send another.
16861692
return nil
16871693
}
1688-
return streamError(id, ErrCodeStreamClosed)
1694+
return sc.countError("closed", streamError(id, ErrCodeStreamClosed))
16891695
}
16901696
if st.body == nil {
16911697
panic("internal error: should have a body in this state")
@@ -1697,20 +1703,20 @@ func (sc *serverConn) processData(f *DataFrame) error {
16971703
// RFC 7540, sec 8.1.2.6: A request or response is also malformed if the
16981704
// value of a content-length header field does not equal the sum of the
16991705
// DATA frame payload lengths that form the body.
1700-
return streamError(id, ErrCodeProtocol)
1706+
return sc.countError("send_too_much", streamError(id, ErrCodeProtocol))
17011707
}
17021708
if f.Length > 0 {
17031709
// Check whether the client has flow control quota.
17041710
if st.inflow.available() < int32(f.Length) {
1705-
return streamError(id, ErrCodeFlowControl)
1711+
return sc.countError("flow_on_data_length", streamError(id, ErrCodeFlowControl))
17061712
}
17071713
st.inflow.take(int32(f.Length))
17081714

17091715
if len(data) > 0 {
17101716
wrote, err := st.body.Write(data)
17111717
if err != nil {
17121718
sc.sendWindowUpdate(nil, int(f.Length)-wrote)
1713-
return streamError(id, ErrCodeStreamClosed)
1719+
return sc.countError("body_write_err", streamError(id, ErrCodeStreamClosed))
17141720
}
17151721
if wrote != len(data) {
17161722
panic("internal error: bad Writer")
@@ -1796,7 +1802,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
17961802
// stream identifier MUST respond with a connection error
17971803
// (Section 5.4.1) of type PROTOCOL_ERROR.
17981804
if id%2 != 1 {
1799-
return ConnectionError(ErrCodeProtocol)
1805+
return sc.countError("headers_even", ConnectionError(ErrCodeProtocol))
18001806
}
18011807
// A HEADERS frame can be used to create a new stream or
18021808
// send a trailer for an open one. If we already have a stream
@@ -1813,7 +1819,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
18131819
// this state, it MUST respond with a stream error (Section 5.4.2) of
18141820
// type STREAM_CLOSED.
18151821
if st.state == stateHalfClosedRemote {
1816-
return streamError(id, ErrCodeStreamClosed)
1822+
return sc.countError("headers_half_closed", streamError(id, ErrCodeStreamClosed))
18171823
}
18181824
return st.processTrailerHeaders(f)
18191825
}
@@ -1824,7 +1830,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
18241830
// receives an unexpected stream identifier MUST respond with
18251831
// a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
18261832
if id <= sc.maxClientStreamID {
1827-
return ConnectionError(ErrCodeProtocol)
1833+
return sc.countError("stream_went_down", ConnectionError(ErrCodeProtocol))
18281834
}
18291835
sc.maxClientStreamID = id
18301836

@@ -1841,14 +1847,14 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
18411847
if sc.curClientStreams+1 > sc.advMaxStreams {
18421848
if sc.unackedSettings == 0 {
18431849
// They should know better.
1844-
return streamError(id, ErrCodeProtocol)
1850+
return sc.countError("over_max_streams", streamError(id, ErrCodeProtocol))
18451851
}
18461852
// Assume it's a network race, where they just haven't
18471853
// received our last SETTINGS update. But actually
18481854
// this can't happen yet, because we don't yet provide
18491855
// a way for users to adjust server parameters at
18501856
// runtime.
1851-
return streamError(id, ErrCodeRefusedStream)
1857+
return sc.countError("over_max_streams_race", streamError(id, ErrCodeRefusedStream))
18521858
}
18531859

18541860
initialState := stateOpen
@@ -1858,7 +1864,7 @@ func (sc *serverConn) processHeaders(f *MetaHeadersFrame) error {
18581864
st := sc.newStream(id, 0, initialState)
18591865

18601866
if f.HasPriority() {
1861-
if err := checkPriority(f.StreamID, f.Priority); err != nil {
1867+
if err := sc.checkPriority(f.StreamID, f.Priority); err != nil {
18621868
return err
18631869
}
18641870
sc.writeSched.AdjustStream(st.id, f.Priority)
@@ -1902,15 +1908,15 @@ func (st *stream) processTrailerHeaders(f *MetaHeadersFrame) error {
19021908
sc := st.sc
19031909
sc.serveG.check()
19041910
if st.gotTrailerHeader {
1905-
return ConnectionError(ErrCodeProtocol)
1911+
return sc.countError("dup_trailers", ConnectionError(ErrCodeProtocol))
19061912
}
19071913
st.gotTrailerHeader = true
19081914
if !f.StreamEnded() {
1909-
return streamError(st.id, ErrCodeProtocol)
1915+
return sc.countError("trailers_not_ended", streamError(st.id, ErrCodeProtocol))
19101916
}
19111917

19121918
if len(f.PseudoFields()) > 0 {
1913-
return streamError(st.id, ErrCodeProtocol)
1919+
return sc.countError("trailers_pseudo", streamError(st.id, ErrCodeProtocol))
19141920
}
19151921
if st.trailer != nil {
19161922
for _, hf := range f.RegularFields() {
@@ -1919,7 +1925,7 @@ func (st *stream) processTrailerHeaders(f *MetaHeadersFrame) error {
19191925
// TODO: send more details to the peer somehow. But http2 has
19201926
// no way to send debug data at a stream level. Discuss with
19211927
// HTTP folk.
1922-
return streamError(st.id, ErrCodeProtocol)
1928+
return sc.countError("trailers_bogus", streamError(st.id, ErrCodeProtocol))
19231929
}
19241930
st.trailer[key] = append(st.trailer[key], hf.Value)
19251931
}
@@ -1928,13 +1934,13 @@ func (st *stream) processTrailerHeaders(f *MetaHeadersFrame) error {
19281934
return nil
19291935
}
19301936

1931-
func checkPriority(streamID uint32, p PriorityParam) error {
1937+
func (sc *serverConn) checkPriority(streamID uint32, p PriorityParam) error {
19321938
if streamID == p.StreamDep {
19331939
// Section 5.3.1: "A stream cannot depend on itself. An endpoint MUST treat
19341940
// this as a stream error (Section 5.4.2) of type PROTOCOL_ERROR."
19351941
// Section 5.3.3 says that a stream can depend on one of its dependencies,
19361942
// so it's only self-dependencies that are forbidden.
1937-
return streamError(streamID, ErrCodeProtocol)
1943+
return sc.countError("priority", streamError(streamID, ErrCodeProtocol))
19381944
}
19391945
return nil
19401946
}
@@ -1943,7 +1949,7 @@ func (sc *serverConn) processPriority(f *PriorityFrame) error {
19431949
if sc.inGoAway {
19441950
return nil
19451951
}
1946-
if err := checkPriority(f.StreamID, f.PriorityParam); err != nil {
1952+
if err := sc.checkPriority(f.StreamID, f.PriorityParam); err != nil {
19471953
return err
19481954
}
19491955
sc.writeSched.AdjustStream(f.StreamID, f.PriorityParam)
@@ -2013,13 +2019,13 @@ func (sc *serverConn) newWriterAndRequest(st *stream, f *MetaHeadersFrame) (*res
20132019
// "All HTTP/2 requests MUST include exactly one valid
20142020
// value for the :method, :scheme, and :path
20152021
// pseudo-header fields"
2016-
return nil, nil, streamError(f.StreamID, ErrCodeProtocol)
2022+
return nil, nil, sc.countError("bad_path_method", streamError(f.StreamID, ErrCodeProtocol))
20172023
}
20182024

20192025
bodyOpen := !f.StreamEnded()
20202026
if rp.method == "HEAD" && bodyOpen {
20212027
// HEAD requests can't have bodies
2022-
return nil, nil, streamError(f.StreamID, ErrCodeProtocol)
2028+
return nil, nil, sc.countError("head_body", streamError(f.StreamID, ErrCodeProtocol))
20232029
}
20242030

20252031
rp.header = make(http.Header)
@@ -2102,7 +2108,7 @@ func (sc *serverConn) newWriterAndRequestNoBody(st *stream, rp requestParam) (*r
21022108
var err error
21032109
url_, err = url.ParseRequestURI(rp.path)
21042110
if err != nil {
2105-
return nil, nil, streamError(st.id, ErrCodeProtocol)
2111+
return nil, nil, sc.countError("bad_path", streamError(st.id, ErrCodeProtocol))
21062112
}
21072113
requestURI = rp.path
21082114
}
@@ -2985,3 +2991,31 @@ func h1ServerKeepAlivesDisabled(hs *http.Server) bool {
29852991
}
29862992
return false
29872993
}
2994+
2995+
func (sc *serverConn) countError(name string, err error) error {
2996+
if sc == nil || sc.srv == nil {
2997+
return err
2998+
}
2999+
f := sc.srv.CountError
3000+
if f == nil {
3001+
return err
3002+
}
3003+
var typ string
3004+
var code ErrCode
3005+
switch e := err.(type) {
3006+
case ConnectionError:
3007+
typ = "conn"
3008+
code = ErrCode(e)
3009+
case StreamError:
3010+
typ = "stream"
3011+
code = ErrCode(e.Code)
3012+
default:
3013+
return err
3014+
}
3015+
codeStr := errCodeName[code]
3016+
if codeStr == "" {
3017+
codeStr = strconv.Itoa(int(code))
3018+
}
3019+
f(fmt.Sprintf("%s_%s_%s", typ, codeStr, name))
3020+
return err
3021+
}

0 commit comments

Comments
 (0)