|
|
@@ -51,6 +51,7 @@ import (
|
|
|
"os"
|
|
|
"reflect"
|
|
|
"runtime"
|
|
|
+ "sort"
|
|
|
"strconv"
|
|
|
"strings"
|
|
|
"sync"
|
|
|
@@ -1973,7 +1974,9 @@ func (rws *responseWriterState) declareTrailer(k string) {
|
|
|
// Forbidden by RFC 2616 14.40.
|
|
|
return
|
|
|
}
|
|
|
- rws.trailers = append(rws.trailers, k)
|
|
|
+ if !strSliceContains(rws.trailers, k) {
|
|
|
+ rws.trailers = append(rws.trailers, k)
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// writeChunk writes chunks from the bufio.Writer. But because
|
|
|
@@ -2041,6 +2044,10 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
|
|
|
return 0, nil
|
|
|
}
|
|
|
|
|
|
+ if rws.handlerDone {
|
|
|
+ rws.promoteUndeclaredTrailers()
|
|
|
+ }
|
|
|
+
|
|
|
endStream := rws.handlerDone && !rws.hasTrailers()
|
|
|
if len(p) > 0 || endStream {
|
|
|
// only send a 0 byte DATA frame if we're ending the stream.
|
|
|
@@ -2061,6 +2068,53 @@ func (rws *responseWriterState) writeChunk(p []byte) (n int, err error) {
|
|
|
return len(p), nil
|
|
|
}
|
|
|
|
|
|
+// TrailerPrefix is a magic prefix for ResponseWriter.Header map keys
|
|
|
+// that, if present, signals that the map entry is actually for
|
|
|
+// the response trailers, and not the response headers. The prefix
|
|
|
+// is stripped after the ServeHTTP call finishes and the values are
|
|
|
+// sent in the trailers.
|
|
|
+//
|
|
|
+// This mechanism is intended only for trailers that are not known
|
|
|
+// prior to the headers being written. If the set of trailers is fixed
|
|
|
+// or known before the header is written, the normal Go trailers mechanism
|
|
|
+// is preferred:
|
|
|
+// https://golang.org/pkg/net/http/#ResponseWriter
|
|
|
+// https://golang.org/pkg/net/http/#example_ResponseWriter_trailers
|
|
|
+const TrailerPrefix = "Trailer:"
|
|
|
+
|
|
|
+// promoteUndeclaredTrailers permits http.Handlers to set trailers
|
|
|
+// after the header has already been flushed. Because the Go
|
|
|
+// ResponseWriter interface has no way to set Trailers (only the
|
|
|
+// Header), and because we didn't want to expand the ResponseWriter
|
|
|
+// interface, and because nobody used trailers, and because RFC 2616
|
|
|
+// says you SHOULD (but not must) predeclare any trailers in the
|
|
|
+// header, the official ResponseWriter rules said trailers in Go must
|
|
|
+// be predeclared, and then we reuse the same ResponseWriter.Header()
|
|
|
+// map to mean both Headers and Trailers. When it's time to write the
|
|
|
+// Trailers, we pick out the fields of Headers that were declared as
|
|
|
+// trailers. That worked for a while, until we found the first major
|
|
|
+// user of Trailers in the wild: gRPC (using them only over http2),
|
|
|
+// and gRPC libraries permit setting trailers mid-stream without
|
|
|
+// predeclarnig them. So: change of plans. We still permit the old
|
|
|
+// way, but we also permit this hack: if a Header() key begins with
|
|
|
+// "Trailer:", the suffix of that key is a Trailer. Because ':' is an
|
|
|
+// invalid token byte anyway, there is no ambiguity. (And it's already
|
|
|
+// filtered out) It's mildly hacky, but not terrible.
|
|
|
+//
|
|
|
+// This method runs after the Handler is done and promotes any Header
|
|
|
+// fields to be trailers.
|
|
|
+func (rws *responseWriterState) promoteUndeclaredTrailers() {
|
|
|
+ for k, vv := range rws.handlerHeader {
|
|
|
+ if !strings.HasPrefix(k, TrailerPrefix) {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ trailerKey := strings.TrimPrefix(k, TrailerPrefix)
|
|
|
+ rws.declareTrailer(trailerKey)
|
|
|
+ rws.handlerHeader[http.CanonicalHeaderKey(trailerKey)] = vv
|
|
|
+ }
|
|
|
+ sort.Strings(rws.trailers)
|
|
|
+}
|
|
|
+
|
|
|
func (w *responseWriter) Flush() {
|
|
|
rws := w.rws
|
|
|
if rws == nil {
|