| // 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() |
| defer pr.Close() |
| defer pw.Close() |
| dr := &delegateReader{c: make(chan io.Reader)} |
| |
| t := &http.Transport{ |
| Dial: func(net, addr string) (net.Conn, error) { |
| return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil |
| }, |
| } |
| defer t.CloseIdleConnections() |
| |
| // Wait for the request before replying with a dummy response: |
| go func() { |
| req, err := http.ReadRequest(bufio.NewReader(pr)) |
| if err == nil { |
| // Ensure all the body is read; otherwise |
| // we'll get a partial dump. |
| io.Copy(ioutil.Discard, req.Body) |
| req.Body.Close() |
| } |
| dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n") |
| }() |
| |
| _, 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 |
| } |