http2: disable server push on receiving a GOAWAY
Fixes golang/go#17800
Change-Id: Ibcba9302e2e595ae49d9246ecedd332760486441
Reviewed-on: https://go-review.googlesource.com/32887
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/http2/server.go b/http2/server.go
index e020c29..bbe6396 100644
--- a/http2/server.go
+++ b/http2/server.go
@@ -1166,6 +1166,8 @@
return sc.processResetStream(f)
case *PriorityFrame:
return sc.processPriority(f)
+ case *GoAwayFrame:
+ return sc.processGoAway(f)
case *PushPromiseFrame:
// A client cannot push. Thus, servers MUST treat the receipt of a PUSH_PROMISE
// frame as a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
@@ -1441,6 +1443,20 @@
return nil
}
+func (sc *serverConn) processGoAway(f *GoAwayFrame) error {
+ sc.serveG.check()
+ if f.ErrCode != ErrCodeNo {
+ sc.logf("http2: received GOAWAY %+v, starting graceful shutdown", f)
+ } else {
+ sc.vlogf("http2: received GOAWAY %+v, starting graceful shutdown", f)
+ }
+ sc.goAwayIn(ErrCodeNo, 0)
+ // http://tools.ietf.org/html/rfc7540#section-6.8
+ // We should not create any new streams, which means we should disable push.
+ sc.pushEnabled = false
+ return nil
+}
+
// isPushed reports whether the stream is server-initiated.
func (st *stream) isPushed() bool {
return st.id%2 == 0
diff --git a/http2/server_push_test.go b/http2/server_push_test.go
index 3017be4..7a975fe 100644
--- a/http2/server_push_test.go
+++ b/http2/server_push_test.go
@@ -427,3 +427,39 @@
}
close(finishedPush)
}
+
+func TestServer_Push_RejectAfterGoAway(t *testing.T) {
+ ready := make(chan struct{})
+ errc := make(chan error, 2)
+ st := newServerTester(t, func(w http.ResponseWriter, r *http.Request) {
+ select {
+ case <-ready:
+ case <-time.After(5 * time.Second):
+ errc <- fmt.Errorf("timeout waiting for GOAWAY to be processed")
+ }
+ if got, want := w.(http.Pusher).Push("https://"+r.Host+"/pushed", nil), http.ErrNotSupported; got != want {
+ errc <- fmt.Errorf("Push()=%v, want %v", got, want)
+ }
+ errc <- nil
+ })
+ defer st.Close()
+ st.greet()
+ getSlash(st)
+
+ // Send GOAWAY and wait for it to be processed.
+ st.fr.WriteGoAway(1, ErrCodeNo, nil)
+ go func() {
+ done := false
+ for !done {
+ st.sc.testHookCh <- func(loopNum int) {
+ if !st.sc.pushEnabled {
+ close(ready)
+ done = true
+ }
+ }
+ }
+ }()
+ if err := <-errc; err != nil {
+ t.Error(err)
+ }
+}