瀏覽代碼

http2: don't hang a stream if trailers values are not provided

Pre-declared trailers are written after a server's request handler
returns, in which case the trailer header frame is also responsible
for closing the stream. However, if the server handler does not
define a trailer header value before returning, the headers are empty
and no header frame is written. In this case, the stream would simply
hang.

This change fixes this by closing the stream if there are no header
values but endStream is true. Some other options to consider:

1. Reset the stream with an error; the user didn't define a trailer value
   as they said they would. From my brief reading of the http/2 RFC,
   this isn't actually required.
2. Send an explicitly empty header value.

Change-Id: I07e01250df60ac33fa6d34beb81ec2fa7e43c146
GitHub-Last-Rev: dfc532d7857822d507b218e4cc2511bf0b6dfcbd
GitHub-Pull-Request: golang/net#31
Reviewed-on: https://go-review.googlesource.com/c/net/+/161958
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
marius a. eriksen 6 年之前
父節點
當前提交
1da14a5a36
共有 2 個文件被更改,包括 35 次插入3 次删除
  1. 15 3
      http2/server.go
  2. 20 0
      http2/server_test.go

+ 15 - 3
http2/server.go

@@ -2307,7 +2307,16 @@ type chunkWriter struct{ rws *responseWriterState }
 
 func (cw chunkWriter) Write(p []byte) (n int, err error) { return cw.rws.writeChunk(p) }
 
-func (rws *responseWriterState) hasTrailers() bool { return len(rws.trailers) != 0 }
+func (rws *responseWriterState) hasTrailers() bool { return len(rws.trailers) > 0 }
+
+func (rws *responseWriterState) hasNonemptyTrailers() bool {
+	for _, trailer := range rws.trailers {
+		if _, ok := rws.handlerHeader[trailer]; ok {
+			return true
+		}
+	}
+	return false
+}
 
 // declareTrailer is called for each Trailer header when the
 // response header is written. It notes that a header will need to be
@@ -2407,7 +2416,10 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
 		rws.promoteUndeclaredTrailers()
 	}
 
-	endStream := rws.handlerDone && !rws.hasTrailers()
+	// only send trailers if they have actually been defined by the
+	// server handler.
+	hasNonemptyTrailers := rws.hasNonemptyTrailers()
+	endStream := rws.handlerDone && !hasNonemptyTrailers
 	if len(p) > 0 || endStream {
 		// only send a 0 byte DATA frame if we're ending the stream.
 		if err := rws.conn.writeDataFromHandler(rws.stream, p, endStream); err != nil {
@@ -2416,7 +2428,7 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
 		}
 	}
 
-	if rws.handlerDone && rws.hasTrailers() {
+	if rws.handlerDone && hasNonemptyTrailers {
 		err = rws.conn.writeHeaders(rws.stream, &writeResHeaders{
 			streamID:  rws.stream.id,
 			h:         rws.handlerHeader,

+ 20 - 0
http2/server_test.go

@@ -2708,6 +2708,26 @@ func TestServerDoS_MaxHeaderListSize(t *testing.T) {
 	}
 }
 
+func TestServer_Response_Stream_With_Missing_Trailer(t *testing.T) {
+	testServerResponse(t, func(w http.ResponseWriter, r *http.Request) error {
+		w.Header().Set("Trailer", "test-trailer")
+		return nil
+	}, func(st *serverTester) {
+		getSlash(st)
+		hf := st.wantHeaders()
+		if !hf.HeadersEnded() {
+			t.Fatal("want END_HEADERS flag")
+		}
+		df := st.wantData()
+		if len(df.data) != 0 {
+			t.Fatal("did not want data")
+		}
+		if !df.StreamEnded() {
+			t.Fatal("want END_STREAM flag")
+		}
+	})
+}
+
 func TestCompressionErrorOnWrite(t *testing.T) {
 	const maxStrLen = 8 << 10
 	var serverConfig *http.Server