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)
+	}
+}