aboutsummaryrefslogtreecommitdiff
path: root/libgo/go/net/http/httputil
diff options
context:
space:
mode:
Diffstat (limited to 'libgo/go/net/http/httputil')
-rw-r--r--libgo/go/net/http/httputil/dump.go63
-rw-r--r--libgo/go/net/http/httputil/dump_test.go17
-rw-r--r--libgo/go/net/http/httputil/example_test.go125
-rw-r--r--libgo/go/net/http/httputil/reverseproxy.go26
-rw-r--r--libgo/go/net/http/httputil/reverseproxy_test.go116
5 files changed, 319 insertions, 28 deletions
diff --git a/libgo/go/net/http/httputil/dump.go b/libgo/go/net/http/httputil/dump.go
index ca2d1cd..e22cc66 100644
--- a/libgo/go/net/http/httputil/dump.go
+++ b/libgo/go/net/http/httputil/dump.go
@@ -25,10 +25,10 @@ import (
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
+ return nil, b, err
}
if err = b.Close(); err != nil {
- return nil, nil, err
+ return nil, b, err
}
return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
}
@@ -55,9 +55,9 @@ func (b neverEnding) Read(p []byte) (n int, err error) {
return len(p), nil
}
-// DumpRequestOut is like DumpRequest but includes
-// headers that the standard http.Transport adds,
-// such as User-Agent.
+// DumpRequestOut is like DumpRequest but for outgoing client requests. It
+// includes any 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
@@ -175,13 +175,22 @@ 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.
+// DumpRequest returns the given request in its HTTP/1.x wire
+// representation. It should only be used by servers to debug client
+// requests. The returned representation is an approximation only;
+// some details of the initial request are lost while parsing it into
+// an http.Request. In particular, the order and case of header field
+// names are lost. The order of values in multi-valued headers is kept
+// intact. HTTP/2 requests are dumped in HTTP/1.x form, not in their
+// original binary representations.
+//
+// If body is true, DumpRequest also returns the body. To do so, it
+// consumes req.Body and then replaces it with a new io.ReadCloser
+// that yields the same bytes. If DumpRequest returns an error,
+// the state of req is undefined.
+//
// The documentation for http.Request.Write details which fields
-// of req are used.
+// of req are included in the dump.
func DumpRequest(req *http.Request, body bool) (dump []byte, err error) {
save := req.Body
if !body || req.Body == nil {
@@ -189,21 +198,35 @@ func DumpRequest(req *http.Request, body bool) (dump []byte, err error) {
} else {
save, req.Body, err = drainBody(req.Body)
if err != nil {
- return
+ return nil, err
}
}
var b bytes.Buffer
+ // By default, print out the unmodified req.RequestURI, which
+ // is always set for incoming server requests. But because we
+ // previously used req.URL.RequestURI and the docs weren't
+ // always so clear about when to use DumpRequest vs
+ // DumpRequestOut, fall back to the old way if the caller
+ // provides a non-server Request.
+ reqURI := req.RequestURI
+ if reqURI == "" {
+ reqURI = req.URL.RequestURI()
+ }
+
fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
- req.URL.RequestURI(), req.ProtoMajor, req.ProtoMinor)
+ reqURI, 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)
+ absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://")
+ if !absRequestURI {
+ 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"
@@ -269,7 +292,7 @@ func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) {
} else {
save, resp.Body, err = drainBody(resp.Body)
if err != nil {
- return
+ return nil, err
}
}
err = resp.Write(&b)
diff --git a/libgo/go/net/http/httputil/dump_test.go b/libgo/go/net/http/httputil/dump_test.go
index ae67e98..46bf521 100644
--- a/libgo/go/net/http/httputil/dump_test.go
+++ b/libgo/go/net/http/httputil/dump_test.go
@@ -5,6 +5,7 @@
package httputil
import (
+ "bufio"
"bytes"
"fmt"
"io"
@@ -135,6 +136,14 @@ var dumpTests = []dumpTest{
"Accept-Encoding: gzip\r\n\r\n" +
strings.Repeat("a", 8193),
},
+
+ {
+ Req: *mustReadRequest("GET http://foo.com/ HTTP/1.1\r\n" +
+ "User-Agent: blah\r\n\r\n"),
+ NoBody: true,
+ WantDump: "GET http://foo.com/ HTTP/1.1\r\n" +
+ "User-Agent: blah\r\n\r\n",
+ },
}
func TestDumpRequest(t *testing.T) {
@@ -211,6 +220,14 @@ func mustNewRequest(method, url string, body io.Reader) *http.Request {
return req
}
+func mustReadRequest(s string) *http.Request {
+ req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(s)))
+ if err != nil {
+ panic(err)
+ }
+ return req
+}
+
var dumpResTests = []struct {
res *http.Response
body bool
diff --git a/libgo/go/net/http/httputil/example_test.go b/libgo/go/net/http/httputil/example_test.go
new file mode 100644
index 0000000..8fb1a2d
--- /dev/null
+++ b/libgo/go/net/http/httputil/example_test.go
@@ -0,0 +1,125 @@
+// Copyright 2015 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.
+
+// +build ignore
+
+package httputil_test
+
+import (
+ "fmt"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "net/http/httputil"
+ "net/url"
+ "strings"
+)
+
+func ExampleDumpRequest() {
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ dump, err := httputil.DumpRequest(r, true)
+ if err != nil {
+ http.Error(w, fmt.Sprint(err), http.StatusInternalServerError)
+ return
+ }
+
+ fmt.Fprintf(w, "%q", dump)
+ }))
+ defer ts.Close()
+
+ const body = "Go is a general-purpose language designed with systems programming in mind."
+ req, err := http.NewRequest("POST", ts.URL, strings.NewReader(body))
+ if err != nil {
+ log.Fatal(err)
+ }
+ req.Host = "www.example.org"
+ resp, err := http.DefaultClient.Do(req)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer resp.Body.Close()
+
+ b, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("%s", b)
+
+ // Output:
+ // "POST / HTTP/1.1\r\nHost: www.example.org\r\nAccept-Encoding: gzip\r\nUser-Agent: Go-http-client/1.1\r\n\r\nGo is a general-purpose language designed with systems programming in mind."
+}
+
+func ExampleDumpRequestOut() {
+ const body = "Go is a general-purpose language designed with systems programming in mind."
+ req, err := http.NewRequest("PUT", "http://www.example.org", strings.NewReader(body))
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ dump, err := httputil.DumpRequestOut(req, true)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("%q", dump)
+
+ // Output:
+ // "PUT / HTTP/1.1\r\nHost: www.example.org\r\nUser-Agent: Go-http-client/1.1\r\nContent-Length: 75\r\nAccept-Encoding: gzip\r\n\r\nGo is a general-purpose language designed with systems programming in mind."
+}
+
+func ExampleDumpResponse() {
+ const body = "Go is a general-purpose language designed with systems programming in mind."
+ ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Date", "Wed, 19 Jul 1972 19:00:00 GMT")
+ fmt.Fprintln(w, body)
+ }))
+ defer ts.Close()
+
+ resp, err := http.Get(ts.URL)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer resp.Body.Close()
+
+ dump, err := httputil.DumpResponse(resp, true)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("%q", dump)
+
+ // Output:
+ // "HTTP/1.1 200 OK\r\nContent-Length: 76\r\nContent-Type: text/plain; charset=utf-8\r\nDate: Wed, 19 Jul 1972 19:00:00 GMT\r\n\r\nGo is a general-purpose language designed with systems programming in mind.\n"
+}
+
+func ExampleReverseProxy() {
+ backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ fmt.Fprintln(w, "this call was relayed by the reverse proxy")
+ }))
+ defer backendServer.Close()
+
+ rpURL, err := url.Parse(backendServer.URL)
+ if err != nil {
+ log.Fatal(err)
+ }
+ frontendProxy := httptest.NewServer(httputil.NewSingleHostReverseProxy(rpURL))
+ defer frontendProxy.Close()
+
+ resp, err := http.Get(frontendProxy.URL)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ b, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ fmt.Printf("%s", b)
+
+ // Output:
+ // this call was relayed by the reverse proxy
+}
diff --git a/libgo/go/net/http/httputil/reverseproxy.go b/libgo/go/net/http/httputil/reverseproxy.go
index c8e1132..4dba352 100644
--- a/libgo/go/net/http/httputil/reverseproxy.go
+++ b/libgo/go/net/http/httputil/reverseproxy.go
@@ -46,6 +46,18 @@ type ReverseProxy struct {
// If nil, logging goes to os.Stderr via the log package's
// standard logger.
ErrorLog *log.Logger
+
+ // BufferPool optionally specifies a buffer pool to
+ // get byte slices for use by io.CopyBuffer when
+ // copying HTTP response bodies.
+ BufferPool BufferPool
+}
+
+// A BufferPool is an interface for getting and returning temporary
+// byte slices for use by io.CopyBuffer.
+type BufferPool interface {
+ Get() []byte
+ Put([]byte)
}
func singleJoiningSlash(a, b string) string {
@@ -60,10 +72,13 @@ func singleJoiningSlash(a, b string) string {
return a + b
}
-// NewSingleHostReverseProxy returns a new ReverseProxy that rewrites
+// NewSingleHostReverseProxy returns a new ReverseProxy that routes
// 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.
+// NewSingleHostReverseProxy does not rewrite the Host header.
+// To rewrite Host headers, use ReverseProxy directly with a custom
+// Director policy.
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
targetQuery := target.RawQuery
director := func(req *http.Request) {
@@ -242,7 +257,14 @@ func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader) {
}
}
- io.Copy(dst, src)
+ var buf []byte
+ if p.BufferPool != nil {
+ buf = p.BufferPool.Get()
+ }
+ io.CopyBuffer(dst, src, buf)
+ if p.BufferPool != nil {
+ p.BufferPool.Put(buf)
+ }
}
func (p *ReverseProxy) logf(format string, args ...interface{}) {
diff --git a/libgo/go/net/http/httputil/reverseproxy_test.go b/libgo/go/net/http/httputil/reverseproxy_test.go
index 80a26ab..7f203d8 100644
--- a/libgo/go/net/http/httputil/reverseproxy_test.go
+++ b/libgo/go/net/http/httputil/reverseproxy_test.go
@@ -8,14 +8,17 @@ package httputil
import (
"bufio"
+ "bytes"
+ "io"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
- "runtime"
+ "strconv"
"strings"
+ "sync"
"testing"
"time"
)
@@ -102,7 +105,6 @@ func TestReverseProxy(t *testing.T) {
if g, e := res.Trailer.Get("X-Trailer"), "trailer_value"; g != e {
t.Errorf("Trailer(X-Trailer) = %q ; want %q", g, e)
}
-
}
func TestXForwardedFor(t *testing.T) {
@@ -225,10 +227,7 @@ func TestReverseProxyFlushInterval(t *testing.T) {
}
}
-func TestReverseProxyCancellation(t *testing.T) {
- if runtime.GOOS == "plan9" {
- t.Skip("skipping test; see https://golang.org/issue/9554")
- }
+func TestReverseProxyCancelation(t *testing.T) {
const backendResponse = "I am the backend"
reqInFlight := make(chan struct{})
@@ -320,3 +319,108 @@ func TestNilBody(t *testing.T) {
t.Errorf("Got %q; want %q", slurp, "hi")
}
}
+
+type bufferPool struct {
+ get func() []byte
+ put func([]byte)
+}
+
+func (bp bufferPool) Get() []byte { return bp.get() }
+func (bp bufferPool) Put(v []byte) { bp.put(v) }
+
+func TestReverseProxyGetPutBuffer(t *testing.T) {
+ const msg = "hi"
+ backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, msg)
+ }))
+ defer backend.Close()
+
+ backendURL, err := url.Parse(backend.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var (
+ mu sync.Mutex
+ log []string
+ )
+ addLog := func(event string) {
+ mu.Lock()
+ defer mu.Unlock()
+ log = append(log, event)
+ }
+ rp := NewSingleHostReverseProxy(backendURL)
+ const size = 1234
+ rp.BufferPool = bufferPool{
+ get: func() []byte {
+ addLog("getBuf")
+ return make([]byte, size)
+ },
+ put: func(p []byte) {
+ addLog("putBuf-" + strconv.Itoa(len(p)))
+ },
+ }
+ frontend := httptest.NewServer(rp)
+ 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)
+ }
+ slurp, err := ioutil.ReadAll(res.Body)
+ res.Body.Close()
+ if err != nil {
+ t.Fatalf("reading body: %v", err)
+ }
+ if string(slurp) != msg {
+ t.Errorf("msg = %q; want %q", slurp, msg)
+ }
+ wantLog := []string{"getBuf", "putBuf-" + strconv.Itoa(size)}
+ mu.Lock()
+ defer mu.Unlock()
+ if !reflect.DeepEqual(log, wantLog) {
+ t.Errorf("Log events = %q; want %q", log, wantLog)
+ }
+}
+
+func TestReverseProxy_Post(t *testing.T) {
+ const backendResponse = "I am the backend"
+ const backendStatus = 200
+ var requestBody = bytes.Repeat([]byte("a"), 1<<20)
+ backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ slurp, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ t.Errorf("Backend body read = %v", err)
+ }
+ if len(slurp) != len(requestBody) {
+ t.Errorf("Backend read %d request body bytes; want %d", len(slurp), len(requestBody))
+ }
+ if !bytes.Equal(slurp, requestBody) {
+ t.Error("Backend read wrong request body.") // 1MB; omitting details
+ }
+ 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()
+
+ postReq, _ := http.NewRequest("POST", frontend.URL, bytes.NewReader(requestBody))
+ res, err := http.DefaultClient.Do(postReq)
+ if err != nil {
+ t.Fatalf("Do: %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)
+ }
+}