Index: src/pkg/net/http/serve_test.go |
=================================================================== |
--- a/src/pkg/net/http/serve_test.go |
+++ b/src/pkg/net/http/serve_test.go |
@@ -10,6 +10,7 @@ |
"bufio" |
"bytes" |
"crypto/tls" |
+ "errors" |
"fmt" |
"io" |
"io/ioutil" |
@@ -1451,6 +1452,198 @@ |
} |
} |
+// Tests regarding the ordering of Write, WriteHeader, Header, and |
+// Flush calls. In Go 1.0, rw.WriteHeader immediately flushed the |
+// (*response).header to the wire. In Go 1.1, the actual wire flush is |
+// delayed, so we could maybe tack on a Content-Length and better |
+// Content-Type after we see more (or all) of the output. To preserve |
+// compatibility with Go 1, we need to be careful to track which |
+// headers were live at the time of WriteHeader, so we write the same |
+// ones, even if the handler modifies them (~erroneously) after the |
+// first Write. |
+func TestHeaderToWire(t *testing.T) { |
+ req := []byte(strings.Replace(`GET / HTTP/1.1 |
+Host: golang.org |
+ |
+`, "\n", "\r\n", -1)) |
+ |
+ tests := []struct { |
+ name string |
+ handler func(ResponseWriter, *Request) |
+ check func(output string) error |
+ }{ |
+ { |
+ name: "write without Header", |
+ handler: func(rw ResponseWriter, r *Request) { |
+ rw.Write([]byte("hello world")) |
+ }, |
+ check: func(got string) error { |
+ if !strings.Contains(got, "Content-Length:") { |
+ return errors.New("no content-length") |
+ } |
+ if !strings.Contains(got, "Content-Type: text/plain") { |
+ return errors.New("no content-length") |
+ } |
+ return nil |
+ }, |
+ }, |
+ { |
+ name: "Header mutation before write", |
+ handler: func(rw ResponseWriter, r *Request) { |
+ h := rw.Header() |
+ h.Set("Content-Type", "some/type") |
+ rw.Write([]byte("hello world")) |
+ h.Set("Too-Late", "bogus") |
+ }, |
+ check: func(got string) error { |
+ if !strings.Contains(got, "Content-Length:") { |
+ return errors.New("no content-length") |
+ } |
+ if !strings.Contains(got, "Content-Type: some/type") { |
+ return errors.New("wrong content-type") |
+ } |
+ if strings.Contains(got, "Too-Late") { |
+ return errors.New("don't want too-late header") |
+ } |
+ return nil |
+ }, |
+ }, |
+ { |
+ name: "write then useless Header mutation", |
+ handler: func(rw ResponseWriter, r *Request) { |
+ rw.Write([]byte("hello world")) |
+ rw.Header().Set("Too-Late", "Write already wrote headers") |
+ }, |
+ check: func(got string) error { |
+ if strings.Contains(got, "Too-Late") { |
+ return errors.New("header appeared from after WriteHeader") |
+ } |
+ return nil |
+ }, |
+ }, |
+ { |
+ name: "flush then write", |
+ handler: func(rw ResponseWriter, r *Request) { |
+ rw.(Flusher).Flush() |
+ rw.Write([]byte("post-flush")) |
+ rw.Header().Set("Too-Late", "Write already wrote headers") |
+ }, |
+ check: func(got string) error { |
+ if !strings.Contains(got, "Transfer-Encoding: chunked") { |
+ return errors.New("not chunked") |
+ } |
+ if strings.Contains(got, "Too-Late") { |
+ return errors.New("header appeared from after WriteHeader") |
+ } |
+ return nil |
+ }, |
+ }, |
+ { |
+ name: "header then flush", |
+ handler: func(rw ResponseWriter, r *Request) { |
+ rw.Header().Set("Content-Type", "some/type") |
+ rw.(Flusher).Flush() |
+ rw.Write([]byte("post-flush")) |
+ rw.Header().Set("Too-Late", "Write already wrote headers") |
+ }, |
+ check: func(got string) error { |
+ if !strings.Contains(got, "Transfer-Encoding: chunked") { |
+ return errors.New("not chunked") |
+ } |
+ if strings.Contains(got, "Too-Late") { |
+ return errors.New("header appeared from after WriteHeader") |
+ } |
+ if !strings.Contains(got, "Content-Type: some/type") { |
+ return errors.New("wrong content-length") |
+ } |
+ return nil |
+ }, |
+ }, |
+ { |
+ name: "sniff-on-first-write content-type", |
+ handler: func(rw ResponseWriter, r *Request) { |
+ rw.Write([]byte("<html><head></head><body>some html</body></html>")) |
+ rw.Header().Set("Content-Type", "x/wrong") |
+ }, |
+ check: func(got string) error { |
+ if !strings.Contains(got, "Content-Type: text/html") { |
+ return errors.New("wrong content-length; want html") |
+ } |
+ return nil |
+ }, |
+ }, |
+ { |
+ name: "explicit content-type wins", |
+ handler: func(rw ResponseWriter, r *Request) { |
+ rw.Header().Set("Content-Type", "some/type") |
+ rw.Write([]byte("<html><head></head><body>some html</body></html>")) |
+ }, |
+ check: func(got string) error { |
+ if !strings.Contains(got, "Content-Type: some/type") { |
+ return errors.New("wrong content-length; want html") |
+ } |
+ return nil |
+ }, |
+ }, |
+ { |
+ name: "empty handler", |
+ handler: func(rw ResponseWriter, r *Request) { |
+ }, |
+ check: func(got string) error { |
+ if !strings.Contains(got, "Content-Type: text/plain") { |
+ return errors.New("wrong content-length; want text/plain") |
+ } |
+ if !strings.Contains(got, "Content-Length: 0") { |
+ return errors.New("want 0 content-length") |
+ } |
+ return nil |
+ }, |
+ }, |
+ { |
+ name: "only Header, no write", |
+ handler: func(rw ResponseWriter, r *Request) { |
+ rw.Header().Set("Some-Header", "some-value") |
+ }, |
+ check: func(got string) error { |
+ if !strings.Contains(got, "Some-Header") { |
+ return errors.New("didn't get header") |
+ } |
+ return nil |
+ }, |
+ }, |
+ { |
+ name: "WriteHeader call", |
+ handler: func(rw ResponseWriter, r *Request) { |
+ rw.WriteHeader(404) |
+ rw.Header().Set("Too-Late", "some-value") |
+ }, |
+ check: func(got string) error { |
+ if !strings.Contains(got, "404") { |
+ return errors.New("wrong status") |
+ } |
+ if strings.Contains(got, "Some-Header") { |
+ return errors.New("shouldn't have seen Too-Late") |
+ } |
+ return nil |
+ }, |
+ }, |
+ } |
+ for _, tc := range tests { |
+ var output bytes.Buffer |
+ conn := &rwTestConn{ |
+ Reader: bytes.NewReader(req), |
+ Writer: &output, |
+ closec: make(chan bool, 1), |
+ } |
+ ln := &oneConnListener{conn: conn} |
+ go Serve(ln, HandlerFunc(tc.handler)) |
+ <-conn.closec |
+ if err := tc.check(output.String()); err != nil { |
+ t.Errorf("%s: %v\nGot response:\n%s", tc.name, err, output.Bytes()) |
+ } |
+ } |
+} |
+ |
// goTimeout runs f, failing t if f takes more than ns to complete. |
func goTimeout(t *testing.T, d time.Duration, f func()) { |
ch := make(chan bool, 2) |
@@ -1710,3 +1903,32 @@ |
b.Errorf("b.N=%d but handled %d", b.N, handled) |
} |
} |
+ |
+// same as above, but representing the most simple possible request |
+// and handler. Notably: the handler does not call rw.Header(). |
+func BenchmarkServerFakeConnWithKeepAliveLite(b *testing.B) { |
+ b.ReportAllocs() |
+ |
+ req := []byte(strings.Replace(`GET / HTTP/1.1 |
+Host: golang.org |
+ |
+`, "\n", "\r\n", -1)) |
+ res := []byte("Hello world!\n") |
+ |
+ conn := &rwTestConn{ |
+ Reader: &repeatReader{content: req, count: b.N}, |
+ Writer: ioutil.Discard, |
+ closec: make(chan bool, 1), |
+ } |
+ handled := 0 |
+ handler := HandlerFunc(func(rw ResponseWriter, r *Request) { |
+ handled++ |
+ rw.Write(res) |
+ }) |
+ ln := &oneConnListener{conn: conn} |
+ go Serve(ln, handler) |
+ <-conn.closec |
+ if b.N != handled { |
+ b.Errorf("b.N=%d but handled %d", b.N, handled) |
+ } |
+} |