123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- // 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"
- "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 io.NopCloser(&buf), io.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 = io.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(io.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:
- // Ensure delegateReader.Read doesn't block forever if we get an error.
- close(dr.c)
- }
- }()
- _, err := t.RoundTrip(reqSend)
- req.Body = save
- if err != nil {
- pw.Close()
- dr.err = err
- close(quitReadCh)
- 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
- err error // only used if r is nil and c is closed.
- r io.Reader // nil until received from c
- }
- func (r *delegateReader) Read(p []byte) (int, error) {
- if r.r == nil {
- var ok bool
- if r.r, ok = <-r.c; !ok {
- return 0, r.err
- }
- }
- 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 an 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 = io.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
- }
|