diff options
Diffstat (limited to 'libgo/go/http/serve_test.go')
-rw-r--r-- | libgo/go/http/serve_test.go | 332 |
1 files changed, 298 insertions, 34 deletions
diff --git a/libgo/go/http/serve_test.go b/libgo/go/http/serve_test.go index 7ff6ef0..ac04033 100644 --- a/libgo/go/http/serve_test.go +++ b/libgo/go/http/serve_test.go @@ -12,13 +12,17 @@ import ( "fmt" . "http" "http/httptest" + "io" "io/ioutil" + "log" "os" "net" "reflect" "strings" + "syscall" "testing" "time" + "url" ) type dummyAddr string @@ -107,7 +111,6 @@ func TestConsumingBodyOnNextConn(t *testing.T) { listener := &oneConnListener{conn} handler := func(res ResponseWriter, req *Request) { reqNum++ - t.Logf("Got request #%d: %v", reqNum, req) ch <- req } @@ -116,7 +119,6 @@ func TestConsumingBodyOnNextConn(t *testing.T) { }() var req *Request - t.Log("Waiting for first request.") req = <-ch if req == nil { t.Fatal("Got nil first request.") @@ -126,7 +128,6 @@ func TestConsumingBodyOnNextConn(t *testing.T) { req.Method, "POST") } - t.Log("Waiting for second request.") req = <-ch if req == nil { t.Fatal("Got nil first request.") @@ -136,7 +137,6 @@ func TestConsumingBodyOnNextConn(t *testing.T) { req.Method, "POST") } - t.Log("Waiting for EOF.") if serveerr := <-servech; serveerr != os.EOF { t.Errorf("Serve returned %q; expected EOF", serveerr) } @@ -184,7 +184,7 @@ func TestHostHandlers(t *testing.T) { for _, vt := range vtests { var r *Response var req Request - if req.URL, err = ParseURL(vt.url); err != nil { + if req.URL, err = url.Parse(vt.url); err != nil { t.Errorf("cannot parse url: %v", err) continue } @@ -252,7 +252,7 @@ func TestServerTimeouts(t *testing.T) { // Hit the HTTP server successfully. tr := &Transport{DisableKeepAlives: true} // they interfere with this test c := &Client{Transport: tr} - r, _, err := c.Get(url) + r, err := c.Get(url) if err != nil { t.Fatalf("http Get #1: %v", err) } @@ -282,7 +282,7 @@ func TestServerTimeouts(t *testing.T) { // Hit the HTTP server successfully again, verifying that the // previous slow connection didn't run our handler. (that we // get "req=2", not "req=3") - r, _, err = Get(url) + r, err = Get(url) if err != nil { t.Fatalf("http Get #2: %v", err) } @@ -323,7 +323,7 @@ func TestIdentityResponse(t *testing.T) { // responses. for _, te := range []string{"", "identity"} { url := ts.URL + "/?te=" + te - res, _, err := Get(url) + res, err := Get(url) if err != nil { t.Fatalf("error with Get of %s: %v", url, err) } @@ -342,7 +342,7 @@ func TestIdentityResponse(t *testing.T) { // Verify that ErrContentLength is returned url := ts.URL + "/?overwrite=1" - _, _, err := Get(url) + _, err := Get(url) if err != nil { t.Fatalf("error with Get of %s: %v", url, err) } @@ -370,11 +370,8 @@ func TestIdentityResponse(t *testing.T) { } } -// TestServeHTTP10Close verifies that HTTP/1.0 requests won't be kept alive. -func TestServeHTTP10Close(t *testing.T) { - s := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { - ServeFile(w, r, "testdata/file") - })) +func testTcpConnectionCloses(t *testing.T, req string, h Handler) { + s := httptest.NewServer(h) defer s.Close() conn, err := net.Dial("tcp", s.Listener.Addr().String()) @@ -383,13 +380,13 @@ func TestServeHTTP10Close(t *testing.T) { } defer conn.Close() - _, err = fmt.Fprint(conn, "GET / HTTP/1.0\r\n\r\n") + _, err = fmt.Fprint(conn, req) if err != nil { t.Fatal("print error:", err) } r := bufio.NewReader(conn) - _, err = ReadResponse(r, "GET") + _, err = ReadResponse(r, &Request{Method: "GET"}) if err != nil { t.Fatal("ReadResponse error:", err) } @@ -411,13 +408,34 @@ func TestServeHTTP10Close(t *testing.T) { success <- true } +// TestServeHTTP10Close verifies that HTTP/1.0 requests won't be kept alive. +func TestServeHTTP10Close(t *testing.T) { + testTcpConnectionCloses(t, "GET / HTTP/1.0\r\n\r\n", HandlerFunc(func(w ResponseWriter, r *Request) { + ServeFile(w, r, "testdata/file") + })) +} + +// TestHandlersCanSetConnectionClose verifies that handlers can force a connection to close, +// even for HTTP/1.1 requests. +func TestHandlersCanSetConnectionClose11(t *testing.T) { + testTcpConnectionCloses(t, "GET / HTTP/1.1\r\n\r\n", HandlerFunc(func(w ResponseWriter, r *Request) { + w.Header().Set("Connection", "close") + })) +} + +func TestHandlersCanSetConnectionClose10(t *testing.T) { + testTcpConnectionCloses(t, "GET / HTTP/1.0\r\nConnection: keep-alive\r\n\r\n", HandlerFunc(func(w ResponseWriter, r *Request) { + w.Header().Set("Connection", "close") + })) +} + func TestSetsRemoteAddr(t *testing.T) { ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { fmt.Fprintf(w, "%s", r.RemoteAddr) })) defer ts.Close() - res, _, err := Get(ts.URL) + res, err := Get(ts.URL) if err != nil { t.Fatalf("Get error: %v", err) } @@ -432,13 +450,16 @@ func TestSetsRemoteAddr(t *testing.T) { } func TestChunkedResponseHeaders(t *testing.T) { + log.SetOutput(ioutil.Discard) // is noisy otherwise + defer log.SetOutput(os.Stderr) + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { w.Header().Set("Content-Length", "intentional gibberish") // we check that this is deleted fmt.Fprintf(w, "I am a chunked response.") })) defer ts.Close() - res, _, err := Get(ts.URL) + res, err := Get(ts.URL) if err != nil { t.Fatalf("Get error: %v", err) } @@ -465,7 +486,7 @@ func Test304Responses(t *testing.T) { } })) defer ts.Close() - res, _, err := Get(ts.URL) + res, err := Get(ts.URL) if err != nil { t.Error(err) } @@ -490,6 +511,12 @@ func TestHeadResponses(t *testing.T) { if err != ErrBodyNotAllowed { t.Errorf("on Write, expected ErrBodyNotAllowed, got %v", err) } + + // Also exercise the ReaderFrom path + _, err = io.Copy(w, strings.NewReader("Ignored body")) + if err != ErrBodyNotAllowed { + t.Errorf("on Copy, expected ErrBodyNotAllowed, got %v", err) + } })) defer ts.Close() res, err := Head(ts.URL) @@ -510,28 +537,30 @@ func TestHeadResponses(t *testing.T) { func TestTLSServer(t *testing.T) { ts := httptest.NewTLSServer(HandlerFunc(func(w ResponseWriter, r *Request) { - fmt.Fprintf(w, "tls=%v", r.TLS != nil) + if r.TLS != nil { + w.Header().Set("X-TLS-Set", "true") + if r.TLS.HandshakeComplete { + w.Header().Set("X-TLS-HandshakeComplete", "true") + } + } })) defer ts.Close() if !strings.HasPrefix(ts.URL, "https://") { t.Fatalf("expected test TLS server to start with https://, got %q", ts.URL) } - res, _, err := Get(ts.URL) + res, err := Get(ts.URL) if err != nil { - t.Error(err) + t.Fatal(err) } if res == nil { t.Fatalf("got nil Response") } - if res.Body == nil { - t.Fatalf("got nil Response.Body") + defer res.Body.Close() + if res.Header.Get("X-TLS-Set") != "true" { + t.Errorf("expected X-TLS-Set response header") } - body, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Error(err) - } - if e, g := "tls=true", string(body); e != g { - t.Errorf("expected body %q; got %q", e, g) + if res.Header.Get("X-TLS-HandshakeComplete") != "true" { + t.Errorf("expected X-TLS-HandshakeComplete header") } } @@ -551,7 +580,7 @@ var serverExpectTests = []serverExpectTest{ {100, "", true, "200 OK"}, // 100-continue but requesting client to deny us, - // so it never eads the body. + // so it never reads the body. {100, "100-continue", false, "401 Unauthorized"}, // Likewise without 100-continue: {100, "", false, "401 Unauthorized"}, @@ -624,7 +653,7 @@ func TestServerConsumesRequestBody(t *testing.T) { "POST / HTTP/1.1\r\n"+ "Host: test\r\n"+ "Content-Length: %d\r\n"+ - "\r\n",len(body)))) + "\r\n", len(body)))) conn.readBuf.Write([]byte(body)) done := make(chan bool) @@ -657,7 +686,7 @@ func TestTimeoutHandler(t *testing.T) { // Succeed without timing out: sendHi <- true - res, _, err := Get(ts.URL) + res, err := Get(ts.URL) if err != nil { t.Error(err) } @@ -674,7 +703,7 @@ func TestTimeoutHandler(t *testing.T) { // Times out: timeout <- 1 - res, _, err = Get(ts.URL) + res, err = Get(ts.URL) if err != nil { t.Error(err) } @@ -693,3 +722,238 @@ func TestTimeoutHandler(t *testing.T) { t.Errorf("expected Write error of %v; got %v", e, g) } } + +// Verifies we don't path.Clean() on the wrong parts in redirects. +func TestRedirectMunging(t *testing.T) { + req, _ := NewRequest("GET", "http://example.com/", nil) + + resp := httptest.NewRecorder() + Redirect(resp, req, "/foo?next=http://bar.com/", 302) + if g, e := resp.Header().Get("Location"), "/foo?next=http://bar.com/"; g != e { + t.Errorf("Location header was %q; want %q", g, e) + } + + resp = httptest.NewRecorder() + Redirect(resp, req, "http://localhost:8080/_ah/login?continue=http://localhost:8080/", 302) + if g, e := resp.Header().Get("Location"), "http://localhost:8080/_ah/login?continue=http://localhost:8080/"; g != e { + t.Errorf("Location header was %q; want %q", g, e) + } +} + +// TestZeroLengthPostAndResponse exercises an optimization done by the Transport: +// when there is no body (either because the method doesn't permit a body, or an +// explicit Content-Length of zero is present), then the transport can re-use the +// connection immediately. But when it re-uses the connection, it typically closes +// the previous request's body, which is not optimal for zero-lengthed bodies, +// as the client would then see http.ErrBodyReadAfterClose and not 0, os.EOF. +func TestZeroLengthPostAndResponse(t *testing.T) { + ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) { + all, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatalf("handler ReadAll: %v", err) + } + if len(all) != 0 { + t.Errorf("handler got %d bytes; expected 0", len(all)) + } + rw.Header().Set("Content-Length", "0") + })) + defer ts.Close() + + req, err := NewRequest("POST", ts.URL, strings.NewReader("")) + if err != nil { + t.Fatal(err) + } + req.ContentLength = 0 + + var resp [5]*Response + for i := range resp { + resp[i], err = DefaultClient.Do(req) + if err != nil { + t.Fatalf("client post #%d: %v", i, err) + } + } + + for i := range resp { + all, err := ioutil.ReadAll(resp[i].Body) + if err != nil { + t.Fatalf("req #%d: client ReadAll: %v", i, err) + } + if len(all) != 0 { + t.Errorf("req #%d: client got %d bytes; expected 0", i, len(all)) + } + } +} + +func TestHandlerPanic(t *testing.T) { + // Unlike the other tests that set the log output to ioutil.Discard + // to quiet the output, this test uses a pipe. The pipe serves three + // purposes: + // + // 1) The log.Print from the http server (generated by the caught + // panic) will go to the pipe instead of stderr, making the + // output quiet. + // + // 2) We read from the pipe to verify that the handler + // actually caught the panic and logged something. + // + // 3) The blocking Read call prevents this TestHandlerPanic + // function from exiting before the HTTP server handler + // finishes crashing. If this text function exited too + // early (and its defer log.SetOutput(os.Stderr) ran), + // then the crash output could spill into the next test. + pr, pw := io.Pipe() + log.SetOutput(pw) + defer log.SetOutput(os.Stderr) + + ts := httptest.NewServer(HandlerFunc(func(ResponseWriter, *Request) { + panic("intentional death for testing") + })) + defer ts.Close() + _, err := Get(ts.URL) + if err == nil { + t.Logf("expected an error") + } + + // Do a blocking read on the log output pipe so its logging + // doesn't bleed into the next test. But wait only 5 seconds + // for it. + done := make(chan bool) + go func() { + buf := make([]byte, 1024) + _, err := pr.Read(buf) + pr.Close() + if err != nil { + t.Fatal(err) + } + done <- true + }() + select { + case <-done: + return + case <-time.After(5e9): + t.Fatal("expected server handler to log an error") + } +} + +func TestNoDate(t *testing.T) { + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + w.Header()["Date"] = nil + })) + defer ts.Close() + res, err := Get(ts.URL) + if err != nil { + t.Fatal(err) + } + _, present := res.Header["Date"] + if present { + t.Fatalf("Expected no Date header; got %v", res.Header["Date"]) + } +} + +func TestStripPrefix(t *testing.T) { + h := HandlerFunc(func(w ResponseWriter, r *Request) { + w.Header().Set("X-Path", r.URL.Path) + }) + ts := httptest.NewServer(StripPrefix("/foo", h)) + defer ts.Close() + + res, err := Get(ts.URL + "/foo/bar") + if err != nil { + t.Fatal(err) + } + if g, e := res.Header.Get("X-Path"), "/bar"; g != e { + t.Errorf("test 1: got %s, want %s", g, e) + } + + res, err = Get(ts.URL + "/bar") + if err != nil { + t.Fatal(err) + } + if g, e := res.StatusCode, 404; g != e { + t.Errorf("test 2: got status %v, want %v", g, e) + } +} + +func TestRequestLimit(t *testing.T) { + ts := httptest.NewServer(HandlerFunc(func(w ResponseWriter, r *Request) { + t.Fatalf("didn't expect to get request in Handler") + })) + defer ts.Close() + req, _ := NewRequest("GET", ts.URL, nil) + var bytesPerHeader = len("header12345: val12345\r\n") + for i := 0; i < ((DefaultMaxHeaderBytes+4096)/bytesPerHeader)+1; i++ { + req.Header.Set(fmt.Sprintf("header%05d", i), fmt.Sprintf("val%05d", i)) + } + res, err := DefaultClient.Do(req) + if err != nil { + // Some HTTP clients may fail on this undefined behavior (server replying and + // closing the connection while the request is still being written), but + // we do support it (at least currently), so we expect a response below. + t.Fatalf("Do: %v", err) + } + if res.StatusCode != 400 { + t.Fatalf("expected 400 response status; got: %d %s", res.StatusCode, res.Status) + } +} + +type errorListener struct { + errs []os.Error +} + +func (l *errorListener) Accept() (c net.Conn, err os.Error) { + if len(l.errs) == 0 { + return nil, os.EOF + } + err = l.errs[0] + l.errs = l.errs[1:] + return +} + +func (l *errorListener) Close() os.Error { + return nil +} + +func (l *errorListener) Addr() net.Addr { + return dummyAddr("test-address") +} + +func TestAcceptMaxFds(t *testing.T) { + log.SetOutput(ioutil.Discard) // is noisy otherwise + defer log.SetOutput(os.Stderr) + + ln := &errorListener{[]os.Error{ + &net.OpError{ + Op: "accept", + Error: os.Errno(syscall.EMFILE), + }}} + err := Serve(ln, HandlerFunc(HandlerFunc(func(ResponseWriter, *Request) {}))) + if err != os.EOF { + t.Errorf("got error %v, want EOF", err) + } +} + +func BenchmarkClientServer(b *testing.B) { + b.StopTimer() + ts := httptest.NewServer(HandlerFunc(func(rw ResponseWriter, r *Request) { + fmt.Fprintf(rw, "Hello world.\n") + })) + defer ts.Close() + b.StartTimer() + + for i := 0; i < b.N; i++ { + res, err := Get(ts.URL) + if err != nil { + panic("Get: " + err.String()) + } + all, err := ioutil.ReadAll(res.Body) + if err != nil { + panic("ReadAll: " + err.String()) + } + body := string(all) + if body != "Hello world.\n" { + panic("Got body: " + body) + } + } + + b.StopTimer() +} |