| // 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, b, err | |
| } | |
| if err = b.Close(); err != nil { | |
| return nil, b, 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 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 | |
| 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 | |
| "Transfer-Encoding": true, | |
| "Trailer": true, | |
| } | |
| // 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 included in the dump. | |
| func DumpRequest(req *http.Request, body bool) ([]byte, error) { | |
| var 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 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"), | |
| reqURI, req.ProtoMajor, req.ProtoMinor) | |
| 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" | |
| 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 nil, err | |
| } | |
| 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 nil, err | |
| } | |
| return b.Bytes(), nil | |
| } | |
| // 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 } | |
| // emptyBody is an instance of empty reader. | |
| var emptyBody = ioutil.NopCloser(strings.NewReader("")) | |
| // DumpResponse is like DumpRequest but dumps a response. | |
| func DumpResponse(resp *http.Response, body bool) ([]byte, error) { | |
| var b bytes.Buffer | |
| var err error | |
| save := resp.Body | |
| savecl := resp.ContentLength | |
| if !body { | |
| // For content length of zero. Make sure the body is an empty | |
| // reader, instead of returning error through failureToReadBody{}. | |
| if resp.ContentLength == 0 { | |
| resp.Body = emptyBody | |
| } else { | |
| resp.Body = failureToReadBody{} | |
| } | |
| } else if resp.Body == nil { | |
| resp.Body = emptyBody | |
| } else { | |
| save, resp.Body, err = drainBody(resp.Body) | |
| if err != nil { | |
| return nil, err | |
| } | |
| } | |
| 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 | |
| } |