| // 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" |
| ) |
| |
| // drainBody reads all of b to memory and then returns two equivalent |
| // ReadClosers yielding the same bytes. |
| // |
| // It returns an error if the initial slurp of all bytes fails. It does not attempt |
| // to make the returned ReadClosers have identical error-matching behavior. |
| func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) { |
| if b == nil || b == http.NoBody { |
| // No copying needed. Preserve the magic sentinel meaning of NoBody. |
| return http.NoBody, http.NoBody, nil |
| } |
| 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 |
| } |
| |
| // outGoingLength is a copy of the unexported |
| // (*http.Request).outgoingLength method. |
| func outgoingLength(req *http.Request) int64 { |
| if req.Body == nil || req.Body == http.NoBody { |
| return 0 |
| } |
| if req.ContentLength != 0 { |
| return req.ContentLength |
| } |
| return -1 |
| } |
| |
| // 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 { |
| contentLength := outgoingLength(req) |
| if contentLength != 0 { |
| req.Body = ioutil.NopCloser(io.LimitReader(neverEnding('x'), 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() |
| |
| // We need this channel to ensure that the reader |
| // goroutine exits if t.RoundTrip returns an error. |
| // See golang.org/issue/32571. |
| quitReadCh := make(chan struct{}) |
| // 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() |
| } |
| select { |
| case dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n"): |
| case <-quitReadCh: |
| } |
| }() |
| |
| _, err := t.RoundTrip(reqSend) |
| |
| req.Body = save |
| if err != nil { |
| pw.Close() |
| quitReadCh <- struct{}{} |
| 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 |
| } |