mirror of
https://github.com/golang/go
synced 2024-11-19 05:24:42 -07:00
net/http: support for setting trailers from a server Handler
We already had client support for trailers, but no way for a server to set them short of hijacking the connection. Fixes #7759 Change-Id: Ic83976437739ec6c1acad5f209ed45e501dbb93a Reviewed-on: https://go-review.googlesource.com/2157 Reviewed-by: Andrew Gerrand <adg@golang.org>
This commit is contained in:
parent
5cc29ab95e
commit
4b96409aac
@ -1027,24 +1027,12 @@ func TestClientTrailers(t *testing.T) {
|
|||||||
r.Trailer.Get("Client-Trailer-B"))
|
r.Trailer.Get("Client-Trailer-B"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: golang.org/issue/7759: there's no way yet for
|
// How handlers set Trailers: declare it ahead of time
|
||||||
// the server to set trailers without hijacking, so do
|
// with the Trailer header, and then mutate the
|
||||||
// that for now, just to test the client. Later, in
|
// Header() of those values later, after the response
|
||||||
// Go 1.4, it should be implicit that any mutations
|
// has been written (we wrote to w above).
|
||||||
// to w.Header() after the initial write are the
|
w.Header().Set("Server-Trailer-A", "valuea")
|
||||||
// trailers to be sent, if and only if they were
|
w.Header().Set("Server-Trailer-C", "valuec") // skipping B
|
||||||
// previously declared with w.Header().Set("Trailer",
|
|
||||||
// ..keys..)
|
|
||||||
w.(Flusher).Flush()
|
|
||||||
conn, buf, _ := w.(Hijacker).Hijack()
|
|
||||||
t := Header{}
|
|
||||||
t.Set("Server-Trailer-A", "valuea")
|
|
||||||
t.Set("Server-Trailer-C", "valuec") // skipping B
|
|
||||||
buf.WriteString("0\r\n") // eof
|
|
||||||
t.Write(buf)
|
|
||||||
buf.WriteString("\r\n") // end of trailers
|
|
||||||
buf.Flush()
|
|
||||||
conn.Close()
|
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
|
41
src/net/http/http_test.go
Normal file
41
src/net/http/http_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2014 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Tests of internal functions with no better homes.
|
||||||
|
|
||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestForeachHeaderElement(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
want []string
|
||||||
|
}{
|
||||||
|
{"Foo", []string{"Foo"}},
|
||||||
|
{" Foo", []string{"Foo"}},
|
||||||
|
{"Foo ", []string{"Foo"}},
|
||||||
|
{" Foo ", []string{"Foo"}},
|
||||||
|
|
||||||
|
{"foo", []string{"foo"}},
|
||||||
|
{"anY-cAsE", []string{"anY-cAsE"}},
|
||||||
|
|
||||||
|
{"", nil},
|
||||||
|
{",,,, , ,, ,,, ,", nil},
|
||||||
|
|
||||||
|
{" Foo,Bar, Baz,lower,,Quux ", []string{"Foo", "Bar", "Baz", "lower", "Quux"}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
var got []string
|
||||||
|
foreachHeaderElement(tt.in, func(v string) {
|
||||||
|
got = append(got, v)
|
||||||
|
})
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("foreachHeaderElement(%q) = %q; want %q", tt.in, got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"net/textproto"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -55,9 +56,11 @@ type Handler interface {
|
|||||||
// A ResponseWriter interface is used by an HTTP handler to
|
// A ResponseWriter interface is used by an HTTP handler to
|
||||||
// construct an HTTP response.
|
// construct an HTTP response.
|
||||||
type ResponseWriter interface {
|
type ResponseWriter interface {
|
||||||
// Header returns the header map that will be sent by WriteHeader.
|
// Header returns the header map that will be sent by
|
||||||
// Changing the header after a call to WriteHeader (or Write) has
|
// WriteHeader. Changing the header after a call to
|
||||||
// no effect.
|
// WriteHeader (or Write) has no effect unless the modified
|
||||||
|
// headers were declared as trailers by setting the
|
||||||
|
// "Trailer" header before the call to WriteHeader.
|
||||||
Header() Header
|
Header() Header
|
||||||
|
|
||||||
// Write writes the data to the connection as part of an HTTP reply.
|
// Write writes the data to the connection as part of an HTTP reply.
|
||||||
@ -288,10 +291,21 @@ func (cw *chunkWriter) close() {
|
|||||||
cw.writeHeader(nil)
|
cw.writeHeader(nil)
|
||||||
}
|
}
|
||||||
if cw.chunking {
|
if cw.chunking {
|
||||||
// zero EOF chunk, trailer key/value pairs (currently
|
bw := cw.res.conn.buf // conn's bufio writer
|
||||||
// unsupported in Go's server), followed by a blank
|
// zero chunk to mark EOF
|
||||||
// line.
|
bw.WriteString("0\r\n")
|
||||||
cw.res.conn.buf.WriteString("0\r\n\r\n")
|
if len(cw.res.trailers) > 0 {
|
||||||
|
trailers := make(Header)
|
||||||
|
for _, h := range cw.res.trailers {
|
||||||
|
if vv := cw.res.handlerHeader[h]; len(vv) > 0 {
|
||||||
|
trailers[h] = vv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trailers.Write(bw) // the writer handles noting errors
|
||||||
|
}
|
||||||
|
// final blank line after the trailers (whether
|
||||||
|
// present or not)
|
||||||
|
bw.WriteString("\r\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,6 +346,12 @@ type response struct {
|
|||||||
// input from it.
|
// input from it.
|
||||||
requestBodyLimitHit bool
|
requestBodyLimitHit bool
|
||||||
|
|
||||||
|
// trailers are the headers to be sent after the handler
|
||||||
|
// finishes writing the body. This field is initialized from
|
||||||
|
// the Trailer response header when the response header is
|
||||||
|
// written.
|
||||||
|
trailers []string
|
||||||
|
|
||||||
handlerDone bool // set true when the handler exits
|
handlerDone bool // set true when the handler exits
|
||||||
|
|
||||||
// Buffers for Date and Content-Length
|
// Buffers for Date and Content-Length
|
||||||
@ -339,6 +359,19 @@ type response struct {
|
|||||||
clenBuf [10]byte
|
clenBuf [10]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// declareTrailer is called for each Trailer header when the
|
||||||
|
// response header is written. It notes that a header will need to be
|
||||||
|
// written in the trailers at the end of the response.
|
||||||
|
func (w *response) declareTrailer(k string) {
|
||||||
|
k = CanonicalHeaderKey(k)
|
||||||
|
switch k {
|
||||||
|
case "Transfer-Encoding", "Content-Length", "Trailer":
|
||||||
|
// Forbidden by RFC 2616 14.40.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.trailers = append(w.trailers, k)
|
||||||
|
}
|
||||||
|
|
||||||
// requestTooLarge is called by maxBytesReader when too much input has
|
// requestTooLarge is called by maxBytesReader when too much input has
|
||||||
// been read from the client.
|
// been read from the client.
|
||||||
func (w *response) requestTooLarge() {
|
func (w *response) requestTooLarge() {
|
||||||
@ -747,6 +780,12 @@ func (cw *chunkWriter) writeHeader(p []byte) {
|
|||||||
}
|
}
|
||||||
var setHeader extraHeader
|
var setHeader extraHeader
|
||||||
|
|
||||||
|
trailers := false
|
||||||
|
for _, v := range cw.header["Trailer"] {
|
||||||
|
trailers = true
|
||||||
|
foreachHeaderElement(v, cw.res.declareTrailer)
|
||||||
|
}
|
||||||
|
|
||||||
// If the handler is done but never sent a Content-Length
|
// If the handler is done but never sent a Content-Length
|
||||||
// response header and this is our first (and last) write, set
|
// response header and this is our first (and last) write, set
|
||||||
// it, even to zero. This helps HTTP/1.0 clients keep their
|
// it, even to zero. This helps HTTP/1.0 clients keep their
|
||||||
@ -759,7 +798,7 @@ func (cw *chunkWriter) writeHeader(p []byte) {
|
|||||||
// write non-zero bytes. If it's actually 0 bytes and the
|
// write non-zero bytes. If it's actually 0 bytes and the
|
||||||
// handler never looked at the Request.Method, we just don't
|
// handler never looked at the Request.Method, we just don't
|
||||||
// send a Content-Length header.
|
// send a Content-Length header.
|
||||||
if w.handlerDone && bodyAllowedForStatus(w.status) && header.get("Content-Length") == "" && (!isHEAD || len(p) > 0) {
|
if w.handlerDone && !trailers && bodyAllowedForStatus(w.status) && header.get("Content-Length") == "" && (!isHEAD || len(p) > 0) {
|
||||||
w.contentLength = int64(len(p))
|
w.contentLength = int64(len(p))
|
||||||
setHeader.contentLength = strconv.AppendInt(cw.res.clenBuf[:0], int64(len(p)), 10)
|
setHeader.contentLength = strconv.AppendInt(cw.res.clenBuf[:0], int64(len(p)), 10)
|
||||||
}
|
}
|
||||||
@ -885,6 +924,24 @@ func (cw *chunkWriter) writeHeader(p []byte) {
|
|||||||
w.conn.buf.Write(crlf)
|
w.conn.buf.Write(crlf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// foreachHeaderElement splits v according to the "#rule" construction
|
||||||
|
// in RFC 2616 section 2.1 and calls fn for each non-empty element.
|
||||||
|
func foreachHeaderElement(v string, fn func(string)) {
|
||||||
|
v = textproto.TrimString(v)
|
||||||
|
if v == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.Contains(v, ",") {
|
||||||
|
fn(v)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, f := range strings.Split(v, ",") {
|
||||||
|
if f = textproto.TrimString(f); f != "" {
|
||||||
|
fn(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// statusLines is a cache of Status-Line strings, keyed by code (for
|
// statusLines is a cache of Status-Line strings, keyed by code (for
|
||||||
// HTTP/1.1) or negative code (for HTTP/1.0). This is faster than a
|
// HTTP/1.1) or negative code (for HTTP/1.0). This is faster than a
|
||||||
// map keyed by struct of two fields. This map's max size is bounded
|
// map keyed by struct of two fields. This map's max size is bounded
|
||||||
|
@ -232,7 +232,6 @@ func (t *transferWriter) WriteBody(w io.Writer) error {
|
|||||||
t.ContentLength, ncopy)
|
t.ContentLength, ncopy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(petar): Place trailer writer code here.
|
|
||||||
if chunked(t.TransferEncoding) {
|
if chunked(t.TransferEncoding) {
|
||||||
// Write Trailer header
|
// Write Trailer header
|
||||||
if t.Trailer != nil {
|
if t.Trailer != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user