diff options
Diffstat (limited to 'src/pkg/net/http/httputil')
-rw-r--r-- | src/pkg/net/http/httputil/chunked.go | 203 | ||||
-rw-r--r-- | src/pkg/net/http/httputil/chunked_test.go | 159 | ||||
-rw-r--r-- | src/pkg/net/http/httputil/dump.go | 276 | ||||
-rw-r--r-- | src/pkg/net/http/httputil/dump_test.go | 263 | ||||
-rw-r--r-- | src/pkg/net/http/httputil/httputil.go | 32 | ||||
-rw-r--r-- | src/pkg/net/http/httputil/persist.go | 429 | ||||
-rw-r--r-- | src/pkg/net/http/httputil/reverseproxy.go | 211 | ||||
-rw-r--r-- | src/pkg/net/http/httputil/reverseproxy_test.go | 213 |
8 files changed, 0 insertions, 1786 deletions
diff --git a/src/pkg/net/http/httputil/chunked.go b/src/pkg/net/http/httputil/chunked.go deleted file mode 100644 index 9632bfd19..000000000 --- a/src/pkg/net/http/httputil/chunked.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2009 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. - -// The wire protocol for HTTP's "chunked" Transfer-Encoding. - -// This code is duplicated in net/http and net/http/httputil. -// Please make any changes in both files. - -package httputil - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" -) - -const maxLineLength = 4096 // assumed <= bufio.defaultBufSize - -var ErrLineTooLong = errors.New("header line too long") - -// newChunkedReader returns a new chunkedReader that translates the data read from r -// out of HTTP "chunked" format before returning it. -// The chunkedReader returns io.EOF when the final 0-length chunk is read. -// -// newChunkedReader is not needed by normal applications. The http package -// automatically decodes chunking when reading response bodies. -func newChunkedReader(r io.Reader) io.Reader { - br, ok := r.(*bufio.Reader) - if !ok { - br = bufio.NewReader(r) - } - return &chunkedReader{r: br} -} - -type chunkedReader struct { - r *bufio.Reader - n uint64 // unread bytes in chunk - err error - buf [2]byte -} - -func (cr *chunkedReader) beginChunk() { - // chunk-size CRLF - var line []byte - line, cr.err = readLine(cr.r) - if cr.err != nil { - return - } - cr.n, cr.err = parseHexUint(line) - if cr.err != nil { - return - } - if cr.n == 0 { - cr.err = io.EOF - } -} - -func (cr *chunkedReader) chunkHeaderAvailable() bool { - n := cr.r.Buffered() - if n > 0 { - peek, _ := cr.r.Peek(n) - return bytes.IndexByte(peek, '\n') >= 0 - } - return false -} - -func (cr *chunkedReader) Read(b []uint8) (n int, err error) { - for cr.err == nil { - if cr.n == 0 { - if n > 0 && !cr.chunkHeaderAvailable() { - // We've read enough. Don't potentially block - // reading a new chunk header. - break - } - cr.beginChunk() - continue - } - if len(b) == 0 { - break - } - rbuf := b - if uint64(len(rbuf)) > cr.n { - rbuf = rbuf[:cr.n] - } - var n0 int - n0, cr.err = cr.r.Read(rbuf) - n += n0 - b = b[n0:] - cr.n -= uint64(n0) - // If we're at the end of a chunk, read the next two - // bytes to verify they are "\r\n". - if cr.n == 0 && cr.err == nil { - if _, cr.err = io.ReadFull(cr.r, cr.buf[:2]); cr.err == nil { - if cr.buf[0] != '\r' || cr.buf[1] != '\n' { - cr.err = errors.New("malformed chunked encoding") - } - } - } - } - return n, cr.err -} - -// Read a line of bytes (up to \n) from b. -// Give up if the line exceeds maxLineLength. -// The returned bytes are a pointer into storage in -// the bufio, so they are only valid until the next bufio read. -func readLine(b *bufio.Reader) (p []byte, err error) { - if p, err = b.ReadSlice('\n'); err != nil { - // We always know when EOF is coming. - // If the caller asked for a line, there should be a line. - if err == io.EOF { - err = io.ErrUnexpectedEOF - } else if err == bufio.ErrBufferFull { - err = ErrLineTooLong - } - return nil, err - } - if len(p) >= maxLineLength { - return nil, ErrLineTooLong - } - return trimTrailingWhitespace(p), nil -} - -func trimTrailingWhitespace(b []byte) []byte { - for len(b) > 0 && isASCIISpace(b[len(b)-1]) { - b = b[:len(b)-1] - } - return b -} - -func isASCIISpace(b byte) bool { - return b == ' ' || b == '\t' || b == '\n' || b == '\r' -} - -// newChunkedWriter returns a new chunkedWriter that translates writes into HTTP -// "chunked" format before writing them to w. Closing the returned chunkedWriter -// sends the final 0-length chunk that marks the end of the stream. -// -// newChunkedWriter is not needed by normal applications. The http -// package adds chunking automatically if handlers don't set a -// Content-Length header. Using newChunkedWriter inside a handler -// would result in double chunking or chunking with a Content-Length -// length, both of which are wrong. -func newChunkedWriter(w io.Writer) io.WriteCloser { - return &chunkedWriter{w} -} - -// Writing to chunkedWriter translates to writing in HTTP chunked Transfer -// Encoding wire format to the underlying Wire chunkedWriter. -type chunkedWriter struct { - Wire io.Writer -} - -// Write the contents of data as one chunk to Wire. -// NOTE: Note that the corresponding chunk-writing procedure in Conn.Write has -// a bug since it does not check for success of io.WriteString -func (cw *chunkedWriter) Write(data []byte) (n int, err error) { - - // Don't send 0-length data. It looks like EOF for chunked encoding. - if len(data) == 0 { - return 0, nil - } - - if _, err = fmt.Fprintf(cw.Wire, "%x\r\n", len(data)); err != nil { - return 0, err - } - if n, err = cw.Wire.Write(data); err != nil { - return - } - if n != len(data) { - err = io.ErrShortWrite - return - } - _, err = io.WriteString(cw.Wire, "\r\n") - - return -} - -func (cw *chunkedWriter) Close() error { - _, err := io.WriteString(cw.Wire, "0\r\n") - return err -} - -func parseHexUint(v []byte) (n uint64, err error) { - for _, b := range v { - n <<= 4 - switch { - case '0' <= b && b <= '9': - b = b - '0' - case 'a' <= b && b <= 'f': - b = b - 'a' + 10 - case 'A' <= b && b <= 'F': - b = b - 'A' + 10 - default: - return 0, errors.New("invalid byte in chunk length") - } - n |= uint64(b) - } - return -} diff --git a/src/pkg/net/http/httputil/chunked_test.go b/src/pkg/net/http/httputil/chunked_test.go deleted file mode 100644 index a7a577468..000000000 --- a/src/pkg/net/http/httputil/chunked_test.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2011 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. - -// This code is duplicated in net/http and net/http/httputil. -// Please make any changes in both files. - -package httputil - -import ( - "bufio" - "bytes" - "fmt" - "io" - "io/ioutil" - "strings" - "testing" -) - -func TestChunk(t *testing.T) { - var b bytes.Buffer - - w := newChunkedWriter(&b) - const chunk1 = "hello, " - const chunk2 = "world! 0123456789abcdef" - w.Write([]byte(chunk1)) - w.Write([]byte(chunk2)) - w.Close() - - if g, e := b.String(), "7\r\nhello, \r\n17\r\nworld! 0123456789abcdef\r\n0\r\n"; g != e { - t.Fatalf("chunk writer wrote %q; want %q", g, e) - } - - r := newChunkedReader(&b) - data, err := ioutil.ReadAll(r) - if err != nil { - t.Logf(`data: "%s"`, data) - t.Fatalf("ReadAll from reader: %v", err) - } - if g, e := string(data), chunk1+chunk2; g != e { - t.Errorf("chunk reader read %q; want %q", g, e) - } -} - -func TestChunkReadMultiple(t *testing.T) { - // Bunch of small chunks, all read together. - { - var b bytes.Buffer - w := newChunkedWriter(&b) - w.Write([]byte("foo")) - w.Write([]byte("bar")) - w.Close() - - r := newChunkedReader(&b) - buf := make([]byte, 10) - n, err := r.Read(buf) - if n != 6 || err != io.EOF { - t.Errorf("Read = %d, %v; want 6, EOF", n, err) - } - buf = buf[:n] - if string(buf) != "foobar" { - t.Errorf("Read = %q; want %q", buf, "foobar") - } - } - - // One big chunk followed by a little chunk, but the small bufio.Reader size - // should prevent the second chunk header from being read. - { - var b bytes.Buffer - w := newChunkedWriter(&b) - // fillBufChunk is 11 bytes + 3 bytes header + 2 bytes footer = 16 bytes, - // the same as the bufio ReaderSize below (the minimum), so even - // though we're going to try to Read with a buffer larger enough to also - // receive "foo", the second chunk header won't be read yet. - const fillBufChunk = "0123456789a" - const shortChunk = "foo" - w.Write([]byte(fillBufChunk)) - w.Write([]byte(shortChunk)) - w.Close() - - r := newChunkedReader(bufio.NewReaderSize(&b, 16)) - buf := make([]byte, len(fillBufChunk)+len(shortChunk)) - n, err := r.Read(buf) - if n != len(fillBufChunk) || err != nil { - t.Errorf("Read = %d, %v; want %d, nil", n, err, len(fillBufChunk)) - } - buf = buf[:n] - if string(buf) != fillBufChunk { - t.Errorf("Read = %q; want %q", buf, fillBufChunk) - } - - n, err = r.Read(buf) - if n != len(shortChunk) || err != io.EOF { - t.Errorf("Read = %d, %v; want %d, EOF", n, err, len(shortChunk)) - } - } - - // And test that we see an EOF chunk, even though our buffer is already full: - { - r := newChunkedReader(bufio.NewReader(strings.NewReader("3\r\nfoo\r\n0\r\n"))) - buf := make([]byte, 3) - n, err := r.Read(buf) - if n != 3 || err != io.EOF { - t.Errorf("Read = %d, %v; want 3, EOF", n, err) - } - if string(buf) != "foo" { - t.Errorf("buf = %q; want foo", buf) - } - } -} - -func TestChunkReaderAllocs(t *testing.T) { - if testing.Short() { - t.Skip("skipping in short mode") - } - var buf bytes.Buffer - w := newChunkedWriter(&buf) - a, b, c := []byte("aaaaaa"), []byte("bbbbbbbbbbbb"), []byte("cccccccccccccccccccccccc") - w.Write(a) - w.Write(b) - w.Write(c) - w.Close() - - readBuf := make([]byte, len(a)+len(b)+len(c)+1) - byter := bytes.NewReader(buf.Bytes()) - bufr := bufio.NewReader(byter) - mallocs := testing.AllocsPerRun(100, func() { - byter.Seek(0, 0) - bufr.Reset(byter) - r := newChunkedReader(bufr) - n, err := io.ReadFull(r, readBuf) - if n != len(readBuf)-1 { - t.Fatalf("read %d bytes; want %d", n, len(readBuf)-1) - } - if err != io.ErrUnexpectedEOF { - t.Fatalf("read error = %v; want ErrUnexpectedEOF", err) - } - }) - if mallocs > 1.5 { - t.Errorf("mallocs = %v; want 1", mallocs) - } -} - -func TestParseHexUint(t *testing.T) { - for i := uint64(0); i <= 1234; i++ { - line := []byte(fmt.Sprintf("%x", i)) - got, err := parseHexUint(line) - if err != nil { - t.Fatalf("on %d: %v", i, err) - } - if got != i { - t.Errorf("for input %q = %d; want %d", line, got, i) - } - } - _, err := parseHexUint([]byte("bogus")) - if err == nil { - t.Error("expected error on bogus input") - } -} diff --git a/src/pkg/net/http/httputil/dump.go b/src/pkg/net/http/httputil/dump.go deleted file mode 100644 index 2a7a413d0..000000000 --- a/src/pkg/net/http/httputil/dump.go +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright 2009 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. - -package httputil - -import ( - "bufio" - "bytes" - "errors" - "fmt" - "io" - "io/ioutil" - "net" - "net/http" - "net/url" - "strings" - "time" -) - -// One of the copies, say from b to r2, could be avoided by using a more -// elaborate trick where the other copy is made during Request/Response.Write. -// This would complicate things too much, given that these functions are for -// debugging only. -func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) { - var buf bytes.Buffer - if _, err = buf.ReadFrom(b); err != nil { - return nil, nil, err - } - if err = b.Close(); err != nil { - return nil, nil, err - } - return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil -} - -// dumpConn is a net.Conn which writes to Writer and reads from Reader -type dumpConn struct { - io.Writer - io.Reader -} - -func (c *dumpConn) Close() error { return nil } -func (c *dumpConn) LocalAddr() net.Addr { return nil } -func (c *dumpConn) RemoteAddr() net.Addr { return nil } -func (c *dumpConn) SetDeadline(t time.Time) error { return nil } -func (c *dumpConn) SetReadDeadline(t time.Time) error { return nil } -func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil } - -type neverEnding byte - -func (b neverEnding) Read(p []byte) (n int, err error) { - for i := range p { - p[i] = byte(b) - } - return len(p), nil -} - -// DumpRequestOut is like DumpRequest but includes -// headers that the standard http.Transport adds, -// such as User-Agent. -func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { - save := req.Body - dummyBody := false - if !body || req.Body == nil { - req.Body = nil - if req.ContentLength != 0 { - req.Body = ioutil.NopCloser(io.LimitReader(neverEnding('x'), req.ContentLength)) - dummyBody = true - } - } else { - var err error - save, req.Body, err = drainBody(req.Body) - if err != nil { - return nil, err - } - } - - // Since we're using the actual Transport code to write the request, - // switch to http so the Transport doesn't try to do an SSL - // negotiation with our dumpConn and its bytes.Buffer & pipe. - // The wire format for https and http are the same, anyway. - reqSend := req - if req.URL.Scheme == "https" { - reqSend = new(http.Request) - *reqSend = *req - reqSend.URL = new(url.URL) - *reqSend.URL = *req.URL - reqSend.URL.Scheme = "http" - } - - // Use the actual Transport code to record what we would send - // on the wire, but not using TCP. Use a Transport with a - // custom dialer that returns a fake net.Conn that waits - // for the full input (and recording it), and then responds - // with a dummy response. - var buf bytes.Buffer // records the output - pr, pw := io.Pipe() - dr := &delegateReader{c: make(chan io.Reader)} - // Wait for the request before replying with a dummy response: - go func() { - http.ReadRequest(bufio.NewReader(pr)) - dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\n\r\n") - }() - - t := &http.Transport{ - Dial: func(net, addr string) (net.Conn, error) { - return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil - }, - } - defer t.CloseIdleConnections() - - _, err := t.RoundTrip(reqSend) - - req.Body = save - if err != nil { - return nil, err - } - dump := buf.Bytes() - - // If we used a dummy body above, remove it now. - // TODO: if the req.ContentLength is large, we allocate memory - // unnecessarily just to slice it off here. But this is just - // a debug function, so this is acceptable for now. We could - // discard the body earlier if this matters. - if dummyBody { - if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 { - dump = dump[:i+4] - } - } - return dump, nil -} - -// delegateReader is a reader that delegates to another reader, -// once it arrives on a channel. -type delegateReader struct { - c chan io.Reader - r io.Reader // nil until received from c -} - -func (r *delegateReader) Read(p []byte) (int, error) { - if r.r == nil { - r.r = <-r.c - } - return r.r.Read(p) -} - -// Return value if nonempty, def otherwise. -func valueOrDefault(value, def string) string { - if value != "" { - return value - } - return def -} - -var reqWriteExcludeHeaderDump = map[string]bool{ - "Host": true, // not in Header map anyway - "Content-Length": true, - "Transfer-Encoding": true, - "Trailer": true, -} - -// dumpAsReceived writes req to w in the form as it was received, or -// at least as accurately as possible from the information retained in -// the request. -func dumpAsReceived(req *http.Request, w io.Writer) error { - return nil -} - -// DumpRequest returns the as-received wire representation of req, -// optionally including the request body, for debugging. -// DumpRequest is semantically a no-op, but in order to -// dump the body, it reads the body data into memory and -// changes req.Body to refer to the in-memory copy. -// The documentation for http.Request.Write details which fields -// of req are used. -func DumpRequest(req *http.Request, body bool) (dump []byte, err error) { - save := req.Body - if !body || req.Body == nil { - req.Body = nil - } else { - save, req.Body, err = drainBody(req.Body) - if err != nil { - return - } - } - - var b bytes.Buffer - - fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"), - req.URL.RequestURI(), req.ProtoMajor, req.ProtoMinor) - - host := req.Host - if host == "" && req.URL != nil { - host = req.URL.Host - } - if host != "" { - fmt.Fprintf(&b, "Host: %s\r\n", host) - } - - chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" - if len(req.TransferEncoding) > 0 { - fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ",")) - } - if req.Close { - fmt.Fprintf(&b, "Connection: close\r\n") - } - - err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump) - if err != nil { - return - } - - io.WriteString(&b, "\r\n") - - if req.Body != nil { - var dest io.Writer = &b - if chunked { - dest = NewChunkedWriter(dest) - } - _, err = io.Copy(dest, req.Body) - if chunked { - dest.(io.Closer).Close() - io.WriteString(&b, "\r\n") - } - } - - req.Body = save - if err != nil { - return - } - dump = b.Bytes() - return -} - -// errNoBody is a sentinel error value used by failureToReadBody so we can detect -// that the lack of body was intentional. -var errNoBody = errors.New("sentinel error value") - -// failureToReadBody is a io.ReadCloser that just returns errNoBody on -// Read. It's swapped in when we don't actually want to consume the -// body, but need a non-nil one, and want to distinguish the error -// from reading the dummy body. -type failureToReadBody struct{} - -func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody } -func (failureToReadBody) Close() error { return nil } - -var emptyBody = ioutil.NopCloser(strings.NewReader("")) - -// DumpResponse is like DumpRequest but dumps a response. -func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) { - var b bytes.Buffer - save := resp.Body - savecl := resp.ContentLength - - if !body { - resp.Body = failureToReadBody{} - } else if resp.Body == nil { - resp.Body = emptyBody - } else { - save, resp.Body, err = drainBody(resp.Body) - if err != nil { - return - } - } - err = resp.Write(&b) - if err == errNoBody { - err = nil - } - resp.Body = save - resp.ContentLength = savecl - if err != nil { - return nil, err - } - return b.Bytes(), nil -} diff --git a/src/pkg/net/http/httputil/dump_test.go b/src/pkg/net/http/httputil/dump_test.go deleted file mode 100644 index e1ffb3935..000000000 --- a/src/pkg/net/http/httputil/dump_test.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2011 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. - -package httputil - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "runtime" - "strings" - "testing" -) - -type dumpTest struct { - Req http.Request - Body interface{} // optional []byte or func() io.ReadCloser to populate Req.Body - - WantDump string - WantDumpOut string - NoBody bool // if true, set DumpRequest{,Out} body to false -} - -var dumpTests = []dumpTest{ - - // HTTP/1.1 => chunked coding; body; empty trailer - { - Req: http.Request{ - Method: "GET", - URL: &url.URL{ - Scheme: "http", - Host: "www.google.com", - Path: "/search", - }, - ProtoMajor: 1, - ProtoMinor: 1, - TransferEncoding: []string{"chunked"}, - }, - - Body: []byte("abcdef"), - - WantDump: "GET /search HTTP/1.1\r\n" + - "Host: www.google.com\r\n" + - "Transfer-Encoding: chunked\r\n\r\n" + - chunk("abcdef") + chunk(""), - }, - - // Verify that DumpRequest preserves the HTTP version number, doesn't add a Host, - // and doesn't add a User-Agent. - { - Req: http.Request{ - Method: "GET", - URL: mustParseURL("/foo"), - ProtoMajor: 1, - ProtoMinor: 0, - Header: http.Header{ - "X-Foo": []string{"X-Bar"}, - }, - }, - - WantDump: "GET /foo HTTP/1.0\r\n" + - "X-Foo: X-Bar\r\n\r\n", - }, - - { - Req: *mustNewRequest("GET", "http://example.com/foo", nil), - - WantDumpOut: "GET /foo HTTP/1.1\r\n" + - "Host: example.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + - "Accept-Encoding: gzip\r\n\r\n", - }, - - // Test that an https URL doesn't try to do an SSL negotiation - // with a bytes.Buffer and hang with all goroutines not - // runnable. - { - Req: *mustNewRequest("GET", "https://example.com/foo", nil), - - WantDumpOut: "GET /foo HTTP/1.1\r\n" + - "Host: example.com\r\n" + - "User-Agent: Go 1.1 package http\r\n" + - "Accept-Encoding: gzip\r\n\r\n", - }, - - // Request with Body, but Dump requested without it. - { - Req: http.Request{ - Method: "POST", - URL: &url.URL{ - Scheme: "http", - Host: "post.tld", - Path: "/", - }, - ContentLength: 6, - ProtoMajor: 1, - ProtoMinor: 1, - }, - - Body: []byte("abcdef"), - - WantDumpOut: "POST / HTTP/1.1\r\n" + - "Host: post.tld\r\n" + - "User-Agent: Go 1.1 package http\r\n" + - "Content-Length: 6\r\n" + - "Accept-Encoding: gzip\r\n\r\n", - - NoBody: true, - }, -} - -func TestDumpRequest(t *testing.T) { - numg0 := runtime.NumGoroutine() - for i, tt := range dumpTests { - setBody := func() { - if tt.Body == nil { - return - } - switch b := tt.Body.(type) { - case []byte: - tt.Req.Body = ioutil.NopCloser(bytes.NewReader(b)) - case func() io.ReadCloser: - tt.Req.Body = b() - } - } - setBody() - if tt.Req.Header == nil { - tt.Req.Header = make(http.Header) - } - - if tt.WantDump != "" { - setBody() - dump, err := DumpRequest(&tt.Req, !tt.NoBody) - if err != nil { - t.Errorf("DumpRequest #%d: %s", i, err) - continue - } - if string(dump) != tt.WantDump { - t.Errorf("DumpRequest %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDump, string(dump)) - continue - } - } - - if tt.WantDumpOut != "" { - setBody() - dump, err := DumpRequestOut(&tt.Req, !tt.NoBody) - if err != nil { - t.Errorf("DumpRequestOut #%d: %s", i, err) - continue - } - if string(dump) != tt.WantDumpOut { - t.Errorf("DumpRequestOut %d, expecting:\n%s\nGot:\n%s\n", i, tt.WantDumpOut, string(dump)) - continue - } - } - } - if dg := runtime.NumGoroutine() - numg0; dg > 4 { - t.Errorf("Unexpectedly large number of new goroutines: %d new", dg) - } -} - -func chunk(s string) string { - return fmt.Sprintf("%x\r\n%s\r\n", len(s), s) -} - -func mustParseURL(s string) *url.URL { - u, err := url.Parse(s) - if err != nil { - panic(fmt.Sprintf("Error parsing URL %q: %v", s, err)) - } - return u -} - -func mustNewRequest(method, url string, body io.Reader) *http.Request { - req, err := http.NewRequest(method, url, body) - if err != nil { - panic(fmt.Sprintf("NewRequest(%q, %q, %p) err = %v", method, url, body, err)) - } - return req -} - -var dumpResTests = []struct { - res *http.Response - body bool - want string -}{ - { - res: &http.Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - ContentLength: 50, - Header: http.Header{ - "Foo": []string{"Bar"}, - }, - Body: ioutil.NopCloser(strings.NewReader("foo")), // shouldn't be used - }, - body: false, // to verify we see 50, not empty or 3. - want: `HTTP/1.1 200 OK -Content-Length: 50 -Foo: Bar`, - }, - - { - res: &http.Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - ContentLength: 3, - Body: ioutil.NopCloser(strings.NewReader("foo")), - }, - body: true, - want: `HTTP/1.1 200 OK -Content-Length: 3 - -foo`, - }, - - { - res: &http.Response{ - Status: "200 OK", - StatusCode: 200, - Proto: "HTTP/1.1", - ProtoMajor: 1, - ProtoMinor: 1, - ContentLength: -1, - Body: ioutil.NopCloser(strings.NewReader("foo")), - TransferEncoding: []string{"chunked"}, - }, - body: true, - want: `HTTP/1.1 200 OK -Transfer-Encoding: chunked - -3 -foo -0`, - }, -} - -func TestDumpResponse(t *testing.T) { - for i, tt := range dumpResTests { - gotb, err := DumpResponse(tt.res, tt.body) - if err != nil { - t.Errorf("%d. DumpResponse = %v", i, err) - continue - } - got := string(gotb) - got = strings.TrimSpace(got) - got = strings.Replace(got, "\r", "", -1) - - if got != tt.want { - t.Errorf("%d.\nDumpResponse got:\n%s\n\nWant:\n%s\n", i, got, tt.want) - } - } -} diff --git a/src/pkg/net/http/httputil/httputil.go b/src/pkg/net/http/httputil/httputil.go deleted file mode 100644 index 74fb6c655..000000000 --- a/src/pkg/net/http/httputil/httputil.go +++ /dev/null @@ -1,32 +0,0 @@ -// 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. - -// Package httputil provides HTTP utility functions, complementing the -// more common ones in the net/http package. -package httputil - -import "io" - -// NewChunkedReader returns a new chunkedReader that translates the data read from r -// out of HTTP "chunked" format before returning it. -// The chunkedReader returns io.EOF when the final 0-length chunk is read. -// -// NewChunkedReader is not needed by normal applications. The http package -// automatically decodes chunking when reading response bodies. -func NewChunkedReader(r io.Reader) io.Reader { - return newChunkedReader(r) -} - -// NewChunkedWriter returns a new chunkedWriter that translates writes into HTTP -// "chunked" format before writing them to w. Closing the returned chunkedWriter -// sends the final 0-length chunk that marks the end of the stream. -// -// NewChunkedWriter is not needed by normal applications. The http -// package adds chunking automatically if handlers don't set a -// Content-Length header. Using NewChunkedWriter inside a handler -// would result in double chunking or chunking with a Content-Length -// length, both of which are wrong. -func NewChunkedWriter(w io.Writer) io.WriteCloser { - return newChunkedWriter(w) -} diff --git a/src/pkg/net/http/httputil/persist.go b/src/pkg/net/http/httputil/persist.go deleted file mode 100644 index 987bcc96b..000000000 --- a/src/pkg/net/http/httputil/persist.go +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright 2009 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. - -package httputil - -import ( - "bufio" - "errors" - "io" - "net" - "net/http" - "net/textproto" - "sync" -) - -var ( - ErrPersistEOF = &http.ProtocolError{ErrorString: "persistent connection closed"} - ErrClosed = &http.ProtocolError{ErrorString: "connection closed by user"} - ErrPipeline = &http.ProtocolError{ErrorString: "pipeline error"} -) - -// This is an API usage error - the local side is closed. -// ErrPersistEOF (above) reports that the remote side is closed. -var errClosed = errors.New("i/o operation on closed connection") - -// A ServerConn reads requests and sends responses over an underlying -// connection, until the HTTP keepalive logic commands an end. ServerConn -// also allows hijacking the underlying connection by calling Hijack -// to regain control over the connection. ServerConn supports pipe-lining, -// i.e. requests can be read out of sync (but in the same order) while the -// respective responses are sent. -// -// ServerConn is low-level and old. Applications should instead use Server -// in the net/http package. -type ServerConn struct { - lk sync.Mutex // read-write protects the following fields - c net.Conn - r *bufio.Reader - re, we error // read/write errors - lastbody io.ReadCloser - nread, nwritten int - pipereq map[*http.Request]uint - - pipe textproto.Pipeline -} - -// NewServerConn returns a new ServerConn reading and writing c. If r is not -// nil, it is the buffer to use when reading c. -// -// ServerConn is low-level and old. Applications should instead use Server -// in the net/http package. -func NewServerConn(c net.Conn, r *bufio.Reader) *ServerConn { - if r == nil { - r = bufio.NewReader(c) - } - return &ServerConn{c: c, r: r, pipereq: make(map[*http.Request]uint)} -} - -// Hijack detaches the ServerConn and returns the underlying connection as well -// as the read-side bufio which may have some left over data. Hijack may be -// called before Read has signaled the end of the keep-alive logic. The user -// should not call Hijack while Read or Write is in progress. -func (sc *ServerConn) Hijack() (c net.Conn, r *bufio.Reader) { - sc.lk.Lock() - defer sc.lk.Unlock() - c = sc.c - r = sc.r - sc.c = nil - sc.r = nil - return -} - -// Close calls Hijack and then also closes the underlying connection -func (sc *ServerConn) Close() error { - c, _ := sc.Hijack() - if c != nil { - return c.Close() - } - return nil -} - -// Read returns the next request on the wire. An ErrPersistEOF is returned if -// it is gracefully determined that there are no more requests (e.g. after the -// first request on an HTTP/1.0 connection, or after a Connection:close on a -// HTTP/1.1 connection). -func (sc *ServerConn) Read() (req *http.Request, err error) { - - // Ensure ordered execution of Reads and Writes - id := sc.pipe.Next() - sc.pipe.StartRequest(id) - defer func() { - sc.pipe.EndRequest(id) - if req == nil { - sc.pipe.StartResponse(id) - sc.pipe.EndResponse(id) - } else { - // Remember the pipeline id of this request - sc.lk.Lock() - sc.pipereq[req] = id - sc.lk.Unlock() - } - }() - - sc.lk.Lock() - if sc.we != nil { // no point receiving if write-side broken or closed - defer sc.lk.Unlock() - return nil, sc.we - } - if sc.re != nil { - defer sc.lk.Unlock() - return nil, sc.re - } - if sc.r == nil { // connection closed by user in the meantime - defer sc.lk.Unlock() - return nil, errClosed - } - r := sc.r - lastbody := sc.lastbody - sc.lastbody = nil - sc.lk.Unlock() - - // Make sure body is fully consumed, even if user does not call body.Close - if lastbody != nil { - // body.Close is assumed to be idempotent and multiple calls to - // it should return the error that its first invocation - // returned. - err = lastbody.Close() - if err != nil { - sc.lk.Lock() - defer sc.lk.Unlock() - sc.re = err - return nil, err - } - } - - req, err = http.ReadRequest(r) - sc.lk.Lock() - defer sc.lk.Unlock() - if err != nil { - if err == io.ErrUnexpectedEOF { - // A close from the opposing client is treated as a - // graceful close, even if there was some unparse-able - // data before the close. - sc.re = ErrPersistEOF - return nil, sc.re - } else { - sc.re = err - return req, err - } - } - sc.lastbody = req.Body - sc.nread++ - if req.Close { - sc.re = ErrPersistEOF - return req, sc.re - } - return req, err -} - -// Pending returns the number of unanswered requests -// that have been received on the connection. -func (sc *ServerConn) Pending() int { - sc.lk.Lock() - defer sc.lk.Unlock() - return sc.nread - sc.nwritten -} - -// Write writes resp in response to req. To close the connection gracefully, set the -// Response.Close field to true. Write should be considered operational until -// it returns an error, regardless of any errors returned on the Read side. -func (sc *ServerConn) Write(req *http.Request, resp *http.Response) error { - - // Retrieve the pipeline ID of this request/response pair - sc.lk.Lock() - id, ok := sc.pipereq[req] - delete(sc.pipereq, req) - if !ok { - sc.lk.Unlock() - return ErrPipeline - } - sc.lk.Unlock() - - // Ensure pipeline order - sc.pipe.StartResponse(id) - defer sc.pipe.EndResponse(id) - - sc.lk.Lock() - if sc.we != nil { - defer sc.lk.Unlock() - return sc.we - } - if sc.c == nil { // connection closed by user in the meantime - defer sc.lk.Unlock() - return ErrClosed - } - c := sc.c - if sc.nread <= sc.nwritten { - defer sc.lk.Unlock() - return errors.New("persist server pipe count") - } - if resp.Close { - // After signaling a keep-alive close, any pipelined unread - // requests will be lost. It is up to the user to drain them - // before signaling. - sc.re = ErrPersistEOF - } - sc.lk.Unlock() - - err := resp.Write(c) - sc.lk.Lock() - defer sc.lk.Unlock() - if err != nil { - sc.we = err - return err - } - sc.nwritten++ - - return nil -} - -// A ClientConn sends request and receives headers over an underlying -// connection, while respecting the HTTP keepalive logic. ClientConn -// supports hijacking the connection calling Hijack to -// regain control of the underlying net.Conn and deal with it as desired. -// -// ClientConn is low-level and old. Applications should instead use -// Client or Transport in the net/http package. -type ClientConn struct { - lk sync.Mutex // read-write protects the following fields - c net.Conn - r *bufio.Reader - re, we error // read/write errors - lastbody io.ReadCloser - nread, nwritten int - pipereq map[*http.Request]uint - - pipe textproto.Pipeline - writeReq func(*http.Request, io.Writer) error -} - -// NewClientConn returns a new ClientConn reading and writing c. If r is not -// nil, it is the buffer to use when reading c. -// -// ClientConn is low-level and old. Applications should use Client or -// Transport in the net/http package. -func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn { - if r == nil { - r = bufio.NewReader(c) - } - return &ClientConn{ - c: c, - r: r, - pipereq: make(map[*http.Request]uint), - writeReq: (*http.Request).Write, - } -} - -// NewProxyClientConn works like NewClientConn but writes Requests -// using Request's WriteProxy method. -// -// New code should not use NewProxyClientConn. See Client or -// Transport in the net/http package instead. -func NewProxyClientConn(c net.Conn, r *bufio.Reader) *ClientConn { - cc := NewClientConn(c, r) - cc.writeReq = (*http.Request).WriteProxy - return cc -} - -// Hijack detaches the ClientConn and returns the underlying connection as well -// as the read-side bufio which may have some left over data. Hijack may be -// called before the user or Read have signaled the end of the keep-alive -// logic. The user should not call Hijack while Read or Write is in progress. -func (cc *ClientConn) Hijack() (c net.Conn, r *bufio.Reader) { - cc.lk.Lock() - defer cc.lk.Unlock() - c = cc.c - r = cc.r - cc.c = nil - cc.r = nil - return -} - -// Close calls Hijack and then also closes the underlying connection -func (cc *ClientConn) Close() error { - c, _ := cc.Hijack() - if c != nil { - return c.Close() - } - return nil -} - -// Write writes a request. An ErrPersistEOF error is returned if the connection -// has been closed in an HTTP keepalive sense. If req.Close equals true, the -// keepalive connection is logically closed after this request and the opposing -// server is informed. An ErrUnexpectedEOF indicates the remote closed the -// underlying TCP connection, which is usually considered as graceful close. -func (cc *ClientConn) Write(req *http.Request) (err error) { - - // Ensure ordered execution of Writes - id := cc.pipe.Next() - cc.pipe.StartRequest(id) - defer func() { - cc.pipe.EndRequest(id) - if err != nil { - cc.pipe.StartResponse(id) - cc.pipe.EndResponse(id) - } else { - // Remember the pipeline id of this request - cc.lk.Lock() - cc.pipereq[req] = id - cc.lk.Unlock() - } - }() - - cc.lk.Lock() - if cc.re != nil { // no point sending if read-side closed or broken - defer cc.lk.Unlock() - return cc.re - } - if cc.we != nil { - defer cc.lk.Unlock() - return cc.we - } - if cc.c == nil { // connection closed by user in the meantime - defer cc.lk.Unlock() - return errClosed - } - c := cc.c - if req.Close { - // We write the EOF to the write-side error, because there - // still might be some pipelined reads - cc.we = ErrPersistEOF - } - cc.lk.Unlock() - - err = cc.writeReq(req, c) - cc.lk.Lock() - defer cc.lk.Unlock() - if err != nil { - cc.we = err - return err - } - cc.nwritten++ - - return nil -} - -// Pending returns the number of unanswered requests -// that have been sent on the connection. -func (cc *ClientConn) Pending() int { - cc.lk.Lock() - defer cc.lk.Unlock() - return cc.nwritten - cc.nread -} - -// Read reads the next response from the wire. A valid response might be -// returned together with an ErrPersistEOF, which means that the remote -// requested that this be the last request serviced. Read can be called -// concurrently with Write, but not with another Read. -func (cc *ClientConn) Read(req *http.Request) (resp *http.Response, err error) { - // Retrieve the pipeline ID of this request/response pair - cc.lk.Lock() - id, ok := cc.pipereq[req] - delete(cc.pipereq, req) - if !ok { - cc.lk.Unlock() - return nil, ErrPipeline - } - cc.lk.Unlock() - - // Ensure pipeline order - cc.pipe.StartResponse(id) - defer cc.pipe.EndResponse(id) - - cc.lk.Lock() - if cc.re != nil { - defer cc.lk.Unlock() - return nil, cc.re - } - if cc.r == nil { // connection closed by user in the meantime - defer cc.lk.Unlock() - return nil, errClosed - } - r := cc.r - lastbody := cc.lastbody - cc.lastbody = nil - cc.lk.Unlock() - - // Make sure body is fully consumed, even if user does not call body.Close - if lastbody != nil { - // body.Close is assumed to be idempotent and multiple calls to - // it should return the error that its first invocation - // returned. - err = lastbody.Close() - if err != nil { - cc.lk.Lock() - defer cc.lk.Unlock() - cc.re = err - return nil, err - } - } - - resp, err = http.ReadResponse(r, req) - cc.lk.Lock() - defer cc.lk.Unlock() - if err != nil { - cc.re = err - return resp, err - } - cc.lastbody = resp.Body - - cc.nread++ - - if resp.Close { - cc.re = ErrPersistEOF // don't send any more requests - return resp, cc.re - } - return resp, err -} - -// Do is convenience method that writes a request and reads a response. -func (cc *ClientConn) Do(req *http.Request) (resp *http.Response, err error) { - err = cc.Write(req) - if err != nil { - return - } - return cc.Read(req) -} diff --git a/src/pkg/net/http/httputil/reverseproxy.go b/src/pkg/net/http/httputil/reverseproxy.go deleted file mode 100644 index 48ada5f5f..000000000 --- a/src/pkg/net/http/httputil/reverseproxy.go +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2011 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. - -// HTTP reverse proxy handler - -package httputil - -import ( - "io" - "log" - "net" - "net/http" - "net/url" - "strings" - "sync" - "time" -) - -// onExitFlushLoop is a callback set by tests to detect the state of the -// flushLoop() goroutine. -var onExitFlushLoop func() - -// ReverseProxy is an HTTP Handler that takes an incoming request and -// sends it to another server, proxying the response back to the -// client. -type ReverseProxy struct { - // Director must be a function which modifies - // the request into a new request to be sent - // using Transport. Its response is then copied - // back to the original client unmodified. - Director func(*http.Request) - - // The transport used to perform proxy requests. - // If nil, http.DefaultTransport is used. - Transport http.RoundTripper - - // FlushInterval specifies the flush interval - // to flush to the client while copying the - // response body. - // If zero, no periodic flushing is done. - FlushInterval time.Duration -} - -func singleJoiningSlash(a, b string) string { - aslash := strings.HasSuffix(a, "/") - bslash := strings.HasPrefix(b, "/") - switch { - case aslash && bslash: - return a + b[1:] - case !aslash && !bslash: - return a + "/" + b - } - return a + b -} - -// NewSingleHostReverseProxy returns a new ReverseProxy that rewrites -// URLs to the scheme, host, and base path provided in target. If the -// target's path is "/base" and the incoming request was for "/dir", -// the target request will be for /base/dir. -func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy { - targetQuery := target.RawQuery - director := func(req *http.Request) { - req.URL.Scheme = target.Scheme - req.URL.Host = target.Host - req.URL.Path = singleJoiningSlash(target.Path, req.URL.Path) - if targetQuery == "" || req.URL.RawQuery == "" { - req.URL.RawQuery = targetQuery + req.URL.RawQuery - } else { - req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery - } - } - return &ReverseProxy{Director: director} -} - -func copyHeader(dst, src http.Header) { - for k, vv := range src { - for _, v := range vv { - dst.Add(k, v) - } - } -} - -// Hop-by-hop headers. These are removed when sent to the backend. -// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html -var hopHeaders = []string{ - "Connection", - "Keep-Alive", - "Proxy-Authenticate", - "Proxy-Authorization", - "Te", // canonicalized version of "TE" - "Trailers", - "Transfer-Encoding", - "Upgrade", -} - -func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - transport := p.Transport - if transport == nil { - transport = http.DefaultTransport - } - - outreq := new(http.Request) - *outreq = *req // includes shallow copies of maps, but okay - - p.Director(outreq) - outreq.Proto = "HTTP/1.1" - outreq.ProtoMajor = 1 - outreq.ProtoMinor = 1 - outreq.Close = false - - // Remove hop-by-hop headers to the backend. Especially - // important is "Connection" because we want a persistent - // connection, regardless of what the client sent to us. This - // is modifying the same underlying map from req (shallow - // copied above) so we only copy it if necessary. - copiedHeaders := false - for _, h := range hopHeaders { - if outreq.Header.Get(h) != "" { - if !copiedHeaders { - outreq.Header = make(http.Header) - copyHeader(outreq.Header, req.Header) - copiedHeaders = true - } - outreq.Header.Del(h) - } - } - - if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { - // If we aren't the first proxy retain prior - // X-Forwarded-For information as a comma+space - // separated list and fold multiple headers into one. - if prior, ok := outreq.Header["X-Forwarded-For"]; ok { - clientIP = strings.Join(prior, ", ") + ", " + clientIP - } - outreq.Header.Set("X-Forwarded-For", clientIP) - } - - res, err := transport.RoundTrip(outreq) - if err != nil { - log.Printf("http: proxy error: %v", err) - rw.WriteHeader(http.StatusInternalServerError) - return - } - defer res.Body.Close() - - for _, h := range hopHeaders { - res.Header.Del(h) - } - - copyHeader(rw.Header(), res.Header) - - rw.WriteHeader(res.StatusCode) - p.copyResponse(rw, res.Body) -} - -func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) { - if p.FlushInterval != 0 { - if wf, ok := dst.(writeFlusher); ok { - mlw := &maxLatencyWriter{ - dst: wf, - latency: p.FlushInterval, - done: make(chan bool), - } - go mlw.flushLoop() - defer mlw.stop() - dst = mlw - } - } - - io.Copy(dst, src) -} - -type writeFlusher interface { - io.Writer - http.Flusher -} - -type maxLatencyWriter struct { - dst writeFlusher - latency time.Duration - - lk sync.Mutex // protects Write + Flush - done chan bool -} - -func (m *maxLatencyWriter) Write(p []byte) (int, error) { - m.lk.Lock() - defer m.lk.Unlock() - return m.dst.Write(p) -} - -func (m *maxLatencyWriter) flushLoop() { - t := time.NewTicker(m.latency) - defer t.Stop() - for { - select { - case <-m.done: - if onExitFlushLoop != nil { - onExitFlushLoop() - } - return - case <-t.C: - m.lk.Lock() - m.dst.Flush() - m.lk.Unlock() - } - } -} - -func (m *maxLatencyWriter) stop() { m.done <- true } diff --git a/src/pkg/net/http/httputil/reverseproxy_test.go b/src/pkg/net/http/httputil/reverseproxy_test.go deleted file mode 100644 index e9539b44b..000000000 --- a/src/pkg/net/http/httputil/reverseproxy_test.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2011 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. - -// Reverse proxy tests. - -package httputil - -import ( - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "strings" - "testing" - "time" -) - -const fakeHopHeader = "X-Fake-Hop-Header-For-Test" - -func init() { - hopHeaders = append(hopHeaders, fakeHopHeader) -} - -func TestReverseProxy(t *testing.T) { - const backendResponse = "I am the backend" - const backendStatus = 404 - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if len(r.TransferEncoding) > 0 { - t.Errorf("backend got unexpected TransferEncoding: %v", r.TransferEncoding) - } - if r.Header.Get("X-Forwarded-For") == "" { - t.Errorf("didn't get X-Forwarded-For header") - } - if c := r.Header.Get("Connection"); c != "" { - t.Errorf("handler got Connection header value %q", c) - } - if c := r.Header.Get("Upgrade"); c != "" { - t.Errorf("handler got Upgrade header value %q", c) - } - if g, e := r.Host, "some-name"; g != e { - t.Errorf("backend got Host header %q, want %q", g, e) - } - w.Header().Set("X-Foo", "bar") - w.Header().Set("Upgrade", "foo") - w.Header().Set(fakeHopHeader, "foo") - w.Header().Add("X-Multi-Value", "foo") - w.Header().Add("X-Multi-Value", "bar") - http.SetCookie(w, &http.Cookie{Name: "flavor", Value: "chocolateChip"}) - w.WriteHeader(backendStatus) - w.Write([]byte(backendResponse)) - })) - defer backend.Close() - backendURL, err := url.Parse(backend.URL) - if err != nil { - t.Fatal(err) - } - proxyHandler := NewSingleHostReverseProxy(backendURL) - frontend := httptest.NewServer(proxyHandler) - defer frontend.Close() - - getReq, _ := http.NewRequest("GET", frontend.URL, nil) - getReq.Host = "some-name" - getReq.Header.Set("Connection", "close") - getReq.Header.Set("Upgrade", "foo") - getReq.Close = true - res, err := http.DefaultClient.Do(getReq) - if err != nil { - t.Fatalf("Get: %v", err) - } - if g, e := res.StatusCode, backendStatus; g != e { - t.Errorf("got res.StatusCode %d; expected %d", g, e) - } - if g, e := res.Header.Get("X-Foo"), "bar"; g != e { - t.Errorf("got X-Foo %q; expected %q", g, e) - } - if c := res.Header.Get(fakeHopHeader); c != "" { - t.Errorf("got %s header value %q", fakeHopHeader, c) - } - if g, e := len(res.Header["X-Multi-Value"]), 2; g != e { - t.Errorf("got %d X-Multi-Value header values; expected %d", g, e) - } - if g, e := len(res.Header["Set-Cookie"]), 1; g != e { - t.Fatalf("got %d SetCookies, want %d", g, e) - } - if cookie := res.Cookies()[0]; cookie.Name != "flavor" { - t.Errorf("unexpected cookie %q", cookie.Name) - } - bodyBytes, _ := ioutil.ReadAll(res.Body) - if g, e := string(bodyBytes), backendResponse; g != e { - t.Errorf("got body %q; expected %q", g, e) - } -} - -func TestXForwardedFor(t *testing.T) { - const prevForwardedFor = "client ip" - const backendResponse = "I am the backend" - const backendStatus = 404 - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("X-Forwarded-For") == "" { - t.Errorf("didn't get X-Forwarded-For header") - } - if !strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) { - t.Errorf("X-Forwarded-For didn't contain prior data") - } - w.WriteHeader(backendStatus) - w.Write([]byte(backendResponse)) - })) - defer backend.Close() - backendURL, err := url.Parse(backend.URL) - if err != nil { - t.Fatal(err) - } - proxyHandler := NewSingleHostReverseProxy(backendURL) - frontend := httptest.NewServer(proxyHandler) - defer frontend.Close() - - getReq, _ := http.NewRequest("GET", frontend.URL, nil) - getReq.Host = "some-name" - getReq.Header.Set("Connection", "close") - getReq.Header.Set("X-Forwarded-For", prevForwardedFor) - getReq.Close = true - res, err := http.DefaultClient.Do(getReq) - if err != nil { - t.Fatalf("Get: %v", err) - } - if g, e := res.StatusCode, backendStatus; g != e { - t.Errorf("got res.StatusCode %d; expected %d", g, e) - } - bodyBytes, _ := ioutil.ReadAll(res.Body) - if g, e := string(bodyBytes), backendResponse; g != e { - t.Errorf("got body %q; expected %q", g, e) - } -} - -var proxyQueryTests = []struct { - baseSuffix string // suffix to add to backend URL - reqSuffix string // suffix to add to frontend's request URL - want string // what backend should see for final request URL (without ?) -}{ - {"", "", ""}, - {"?sta=tic", "?us=er", "sta=tic&us=er"}, - {"", "?us=er", "us=er"}, - {"?sta=tic", "", "sta=tic"}, -} - -func TestReverseProxyQuery(t *testing.T) { - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("X-Got-Query", r.URL.RawQuery) - w.Write([]byte("hi")) - })) - defer backend.Close() - - for i, tt := range proxyQueryTests { - backendURL, err := url.Parse(backend.URL + tt.baseSuffix) - if err != nil { - t.Fatal(err) - } - frontend := httptest.NewServer(NewSingleHostReverseProxy(backendURL)) - req, _ := http.NewRequest("GET", frontend.URL+tt.reqSuffix, nil) - req.Close = true - res, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("%d. Get: %v", i, err) - } - if g, e := res.Header.Get("X-Got-Query"), tt.want; g != e { - t.Errorf("%d. got query %q; expected %q", i, g, e) - } - res.Body.Close() - frontend.Close() - } -} - -func TestReverseProxyFlushInterval(t *testing.T) { - const expected = "hi" - backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(expected)) - })) - defer backend.Close() - - backendURL, err := url.Parse(backend.URL) - if err != nil { - t.Fatal(err) - } - - proxyHandler := NewSingleHostReverseProxy(backendURL) - proxyHandler.FlushInterval = time.Microsecond - - done := make(chan bool) - onExitFlushLoop = func() { done <- true } - defer func() { onExitFlushLoop = nil }() - - frontend := httptest.NewServer(proxyHandler) - defer frontend.Close() - - req, _ := http.NewRequest("GET", frontend.URL, nil) - req.Close = true - res, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Get: %v", err) - } - defer res.Body.Close() - if bodyBytes, _ := ioutil.ReadAll(res.Body); string(bodyBytes) != expected { - t.Errorf("got body %q; expected %q", bodyBytes, expected) - } - - select { - case <-done: - // OK - case <-time.After(5 * time.Second): - t.Error("maxLatencyWriter flushLoop() never exited") - } -} |