// 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 }