mirror of
https://github.com/golang/go
synced 2024-09-30 04:24:29 -06:00
net/http: split Trailers tests into two halves
The old test was in client_test.go but was a mix of four things: - clients writing trailers - servers reading trailers - servers writing trailers - clients reading trailers It definitely wasn't just about clients. This moves it into clientserver_test.go and separates it into two halves: - servers writing trailers + clients reading trailers - clients writing trailers + servers reading trailers Which still isn't ideal, but is much better, and easier to read. Updates #13557 Change-Id: I8c3e58a1f974c1b10bb11ef9b588cfa0f73ff5d9 Reviewed-on: https://go-review.googlesource.com/17895 Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org> Reviewed-by: Blake Mizerany <blake.mizerany@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
6bcec09ceb
commit
654daac3bc
@ -20,8 +20,6 @@ import (
|
|||||||
. "net/http"
|
. "net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -1096,81 +1094,6 @@ func (f eofReaderFunc) Read(p []byte) (n int, err error) {
|
|||||||
return 0, io.EOF
|
return 0, io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestClientTrailers_h1(t *testing.T) { testClientTrailers(t, h1Mode) }
|
|
||||||
func TestClientTrailers_h2(t *testing.T) {
|
|
||||||
t.Skip("skipping in http2 mode; golang.org/issue/13557")
|
|
||||||
testClientTrailers(t, h2Mode)
|
|
||||||
}
|
|
||||||
func testClientTrailers(t *testing.T, h2 bool) {
|
|
||||||
defer afterTest(t)
|
|
||||||
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
||||||
w.Header().Set("Connection", "close")
|
|
||||||
w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B")
|
|
||||||
w.Header().Add("Trailer", "Server-Trailer-C")
|
|
||||||
|
|
||||||
var decl []string
|
|
||||||
for k := range r.Trailer {
|
|
||||||
decl = append(decl, k)
|
|
||||||
}
|
|
||||||
sort.Strings(decl)
|
|
||||||
|
|
||||||
slurp, err := ioutil.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Server reading request body: %v", err)
|
|
||||||
}
|
|
||||||
if string(slurp) != "foo" {
|
|
||||||
t.Errorf("Server read request body %q; want foo", slurp)
|
|
||||||
}
|
|
||||||
if r.Trailer == nil {
|
|
||||||
io.WriteString(w, "nil Trailer")
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(w, "decl: %v, vals: %s, %s",
|
|
||||||
decl,
|
|
||||||
r.Trailer.Get("Client-Trailer-A"),
|
|
||||||
r.Trailer.Get("Client-Trailer-B"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// How handlers set Trailers: declare it ahead of time
|
|
||||||
// with the Trailer header, and then mutate the
|
|
||||||
// Header() of those values later, after the response
|
|
||||||
// has been written (we wrote to w above).
|
|
||||||
w.Header().Set("Server-Trailer-A", "valuea")
|
|
||||||
w.Header().Set("Server-Trailer-C", "valuec") // skipping B
|
|
||||||
}))
|
|
||||||
defer cst.close()
|
|
||||||
|
|
||||||
var req *Request
|
|
||||||
req, _ = NewRequest("POST", cst.ts.URL, io.MultiReader(
|
|
||||||
eofReaderFunc(func() {
|
|
||||||
req.Trailer["Client-Trailer-A"] = []string{"valuea"}
|
|
||||||
}),
|
|
||||||
strings.NewReader("foo"),
|
|
||||||
eofReaderFunc(func() {
|
|
||||||
req.Trailer["Client-Trailer-B"] = []string{"valueb"}
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
req.Trailer = Header{
|
|
||||||
"Client-Trailer-A": nil, // to be set later
|
|
||||||
"Client-Trailer-B": nil, // to be set later
|
|
||||||
}
|
|
||||||
req.ContentLength = -1
|
|
||||||
res, err := cst.c.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
want := Header{
|
|
||||||
"Server-Trailer-A": []string{"valuea"},
|
|
||||||
"Server-Trailer-B": nil,
|
|
||||||
"Server-Trailer-C": []string{"valuec"},
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(res.Trailer, want) {
|
|
||||||
t.Errorf("Response trailers = %#v; want %#v", res.Trailer, want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReferer(t *testing.T) {
|
func TestReferer(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
lastReq, newReq string // from -> to URLs
|
lastReq, newReq string // from -> to URLs
|
||||||
|
@ -18,6 +18,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
@ -465,3 +466,128 @@ func testCancelRequestMidBody(t *testing.T, h2 bool) {
|
|||||||
t.Errorf("ReadAll error = %v; want %v", err, ExportErrRequestCanceled)
|
t.Errorf("ReadAll error = %v; want %v", err, ExportErrRequestCanceled)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that clients can send trailers to a server and that the server can read them.
|
||||||
|
func TestTrailersClientToServer_h1(t *testing.T) { testTrailersClientToServer(t, h1Mode) }
|
||||||
|
func TestTrailersClientToServer_h2(t *testing.T) {
|
||||||
|
t.Skip("skipping in http2 mode; golang.org/issue/13557")
|
||||||
|
testTrailersClientToServer(t, h2Mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTrailersClientToServer(t *testing.T, h2 bool) {
|
||||||
|
defer afterTest(t)
|
||||||
|
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
|
||||||
|
var decl []string
|
||||||
|
for k := range r.Trailer {
|
||||||
|
decl = append(decl, k)
|
||||||
|
}
|
||||||
|
sort.Strings(decl)
|
||||||
|
|
||||||
|
slurp, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Server reading request body: %v", err)
|
||||||
|
}
|
||||||
|
if string(slurp) != "foo" {
|
||||||
|
t.Errorf("Server read request body %q; want foo", slurp)
|
||||||
|
}
|
||||||
|
if r.Trailer == nil {
|
||||||
|
io.WriteString(w, "nil Trailer")
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(w, "decl: %v, vals: %s, %s",
|
||||||
|
decl,
|
||||||
|
r.Trailer.Get("Client-Trailer-A"),
|
||||||
|
r.Trailer.Get("Client-Trailer-B"))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer cst.close()
|
||||||
|
|
||||||
|
var req *Request
|
||||||
|
req, _ = NewRequest("POST", cst.ts.URL, io.MultiReader(
|
||||||
|
eofReaderFunc(func() {
|
||||||
|
req.Trailer["Client-Trailer-A"] = []string{"valuea"}
|
||||||
|
}),
|
||||||
|
strings.NewReader("foo"),
|
||||||
|
eofReaderFunc(func() {
|
||||||
|
req.Trailer["Client-Trailer-B"] = []string{"valueb"}
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
req.Trailer = Header{
|
||||||
|
"Client-Trailer-A": nil, // to be set later
|
||||||
|
"Client-Trailer-B": nil, // to be set later
|
||||||
|
}
|
||||||
|
req.ContentLength = -1
|
||||||
|
res, err := cst.c.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := wantBody(res, err, "decl: [Client-Trailer-A Client-Trailer-B], vals: valuea, valueb"); err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that servers send trailers to a client and that the client can read them.
|
||||||
|
func TestTrailersServerToClient_h1(t *testing.T) { testTrailersServerToClient(t, h1Mode, false) }
|
||||||
|
func TestTrailersServerToClient_h2(t *testing.T) {
|
||||||
|
t.Skip("skipping in http2 mode; golang.org/issue/13557")
|
||||||
|
testTrailersServerToClient(t, h2Mode, false)
|
||||||
|
}
|
||||||
|
func TestTrailersServerToClient_Flush_h1(t *testing.T) { testTrailersServerToClient(t, h1Mode, true) }
|
||||||
|
func TestTrailersServerToClient_Flush_h2(t *testing.T) {
|
||||||
|
t.Skip("skipping in http2 mode; golang.org/issue/13557")
|
||||||
|
testTrailersServerToClient(t, h2Mode, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTrailersServerToClient(t *testing.T, h2, flush bool) {
|
||||||
|
defer afterTest(t)
|
||||||
|
const body = "Some body"
|
||||||
|
cst := newClientServerTest(t, h2, HandlerFunc(func(w ResponseWriter, r *Request) {
|
||||||
|
w.Header().Set("Trailer", "Server-Trailer-A, Server-Trailer-B")
|
||||||
|
w.Header().Add("Trailer", "Server-Trailer-C")
|
||||||
|
|
||||||
|
io.WriteString(w, body)
|
||||||
|
if flush {
|
||||||
|
w.(Flusher).Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
// How handlers set Trailers: declare it ahead of time
|
||||||
|
// with the Trailer header, and then mutate the
|
||||||
|
// Header() of those values later, after the response
|
||||||
|
// has been written (we wrote to w above).
|
||||||
|
w.Header().Set("Server-Trailer-A", "valuea")
|
||||||
|
w.Header().Set("Server-Trailer-C", "valuec") // skipping B
|
||||||
|
w.Header().Set("Server-Trailer-NotDeclared", "should be omitted")
|
||||||
|
}))
|
||||||
|
defer cst.close()
|
||||||
|
|
||||||
|
res, err := cst.c.Get(cst.ts.URL)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(res.Header, "Date") // irrelevant for test
|
||||||
|
if got, want := res.Header, (Header{
|
||||||
|
"Content-Type": {"text/plain; charset=utf-8"},
|
||||||
|
}); !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("Header = %v; want %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := res.Trailer, (Header{
|
||||||
|
"Server-Trailer-A": nil,
|
||||||
|
"Server-Trailer-B": nil,
|
||||||
|
"Server-Trailer-C": nil,
|
||||||
|
}); !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("Trailer before body read = %v; want %v", got, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wantBody(res, nil, body); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := res.Trailer, (Header{
|
||||||
|
"Server-Trailer-A": {"valuea"},
|
||||||
|
"Server-Trailer-B": nil,
|
||||||
|
"Server-Trailer-C": {"valuec"},
|
||||||
|
}); !reflect.DeepEqual(got, want) {
|
||||||
|
t.Errorf("Trailer after body read = %v; want %v", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -577,21 +577,29 @@ func shouldClose(major, minor int, header Header, removeCloseHeader bool) bool {
|
|||||||
|
|
||||||
// Parse the trailer header
|
// Parse the trailer header
|
||||||
func fixTrailer(header Header, te []string) (Header, error) {
|
func fixTrailer(header Header, te []string) (Header, error) {
|
||||||
raw := header.get("Trailer")
|
vv, ok := header["Trailer"]
|
||||||
if raw == "" {
|
if !ok {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
header.Del("Trailer")
|
header.Del("Trailer")
|
||||||
|
|
||||||
trailer := make(Header)
|
trailer := make(Header)
|
||||||
keys := strings.Split(raw, ",")
|
var err error
|
||||||
for _, key := range keys {
|
for _, v := range vv {
|
||||||
key = CanonicalHeaderKey(strings.TrimSpace(key))
|
foreachHeaderElement(v, func(key string) {
|
||||||
switch key {
|
key = CanonicalHeaderKey(key)
|
||||||
case "Transfer-Encoding", "Trailer", "Content-Length":
|
switch key {
|
||||||
return nil, &badStringError{"bad trailer key", key}
|
case "Transfer-Encoding", "Trailer", "Content-Length":
|
||||||
}
|
if err == nil {
|
||||||
trailer[key] = nil
|
err = &badStringError{"bad trailer key", key}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trailer[key] = nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(trailer) == 0 {
|
if len(trailer) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
Loading…
Reference in New Issue
Block a user