github.com/tcnksm/go@v0.0.0-20141208075154-439b32936367/src/net/http/httputil/dump.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package httputil
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"errors"
    11  	"fmt"
    12  	"io"
    13  	"io/ioutil"
    14  	"net"
    15  	"net/http"
    16  	"net/url"
    17  	"strings"
    18  	"time"
    19  )
    20  
    21  // One of the copies, say from b to r2, could be avoided by using a more
    22  // elaborate trick where the other copy is made during Request/Response.Write.
    23  // This would complicate things too much, given that these functions are for
    24  // debugging only.
    25  func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
    26  	var buf bytes.Buffer
    27  	if _, err = buf.ReadFrom(b); err != nil {
    28  		return nil, nil, err
    29  	}
    30  	if err = b.Close(); err != nil {
    31  		return nil, nil, err
    32  	}
    33  	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
    34  }
    35  
    36  // dumpConn is a net.Conn which writes to Writer and reads from Reader
    37  type dumpConn struct {
    38  	io.Writer
    39  	io.Reader
    40  }
    41  
    42  func (c *dumpConn) Close() error                       { return nil }
    43  func (c *dumpConn) LocalAddr() net.Addr                { return nil }
    44  func (c *dumpConn) RemoteAddr() net.Addr               { return nil }
    45  func (c *dumpConn) SetDeadline(t time.Time) error      { return nil }
    46  func (c *dumpConn) SetReadDeadline(t time.Time) error  { return nil }
    47  func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
    48  
    49  type neverEnding byte
    50  
    51  func (b neverEnding) Read(p []byte) (n int, err error) {
    52  	for i := range p {
    53  		p[i] = byte(b)
    54  	}
    55  	return len(p), nil
    56  }
    57  
    58  // DumpRequestOut is like DumpRequest but includes
    59  // headers that the standard http.Transport adds,
    60  // such as User-Agent.
    61  func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
    62  	save := req.Body
    63  	dummyBody := false
    64  	if !body || req.Body == nil {
    65  		req.Body = nil
    66  		if req.ContentLength != 0 {
    67  			req.Body = ioutil.NopCloser(io.LimitReader(neverEnding('x'), req.ContentLength))
    68  			dummyBody = true
    69  		}
    70  	} else {
    71  		var err error
    72  		save, req.Body, err = drainBody(req.Body)
    73  		if err != nil {
    74  			return nil, err
    75  		}
    76  	}
    77  
    78  	// Since we're using the actual Transport code to write the request,
    79  	// switch to http so the Transport doesn't try to do an SSL
    80  	// negotiation with our dumpConn and its bytes.Buffer & pipe.
    81  	// The wire format for https and http are the same, anyway.
    82  	reqSend := req
    83  	if req.URL.Scheme == "https" {
    84  		reqSend = new(http.Request)
    85  		*reqSend = *req
    86  		reqSend.URL = new(url.URL)
    87  		*reqSend.URL = *req.URL
    88  		reqSend.URL.Scheme = "http"
    89  	}
    90  
    91  	// Use the actual Transport code to record what we would send
    92  	// on the wire, but not using TCP.  Use a Transport with a
    93  	// custom dialer that returns a fake net.Conn that waits
    94  	// for the full input (and recording it), and then responds
    95  	// with a dummy response.
    96  	var buf bytes.Buffer // records the output
    97  	pr, pw := io.Pipe()
    98  	defer pr.Close()
    99  	defer pw.Close()
   100  	dr := &delegateReader{c: make(chan io.Reader)}
   101  	// Wait for the request before replying with a dummy response:
   102  	go func() {
   103  		req, err := http.ReadRequest(bufio.NewReader(pr))
   104  		if err == nil {
   105  			// Ensure all the body is read; otherwise
   106  			// we'll get a partial dump.
   107  			io.Copy(ioutil.Discard, req.Body)
   108  			req.Body.Close()
   109  		}
   110  		dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\n\r\n")
   111  	}()
   112  
   113  	t := &http.Transport{
   114  		DisableKeepAlives: true,
   115  		Dial: func(net, addr string) (net.Conn, error) {
   116  			return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
   117  		},
   118  	}
   119  
   120  	_, err := t.RoundTrip(reqSend)
   121  
   122  	req.Body = save
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	dump := buf.Bytes()
   127  
   128  	// If we used a dummy body above, remove it now.
   129  	// TODO: if the req.ContentLength is large, we allocate memory
   130  	// unnecessarily just to slice it off here.  But this is just
   131  	// a debug function, so this is acceptable for now. We could
   132  	// discard the body earlier if this matters.
   133  	if dummyBody {
   134  		if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 {
   135  			dump = dump[:i+4]
   136  		}
   137  	}
   138  	return dump, nil
   139  }
   140  
   141  // delegateReader is a reader that delegates to another reader,
   142  // once it arrives on a channel.
   143  type delegateReader struct {
   144  	c chan io.Reader
   145  	r io.Reader // nil until received from c
   146  }
   147  
   148  func (r *delegateReader) Read(p []byte) (int, error) {
   149  	if r.r == nil {
   150  		r.r = <-r.c
   151  	}
   152  	return r.r.Read(p)
   153  }
   154  
   155  // Return value if nonempty, def otherwise.
   156  func valueOrDefault(value, def string) string {
   157  	if value != "" {
   158  		return value
   159  	}
   160  	return def
   161  }
   162  
   163  var reqWriteExcludeHeaderDump = map[string]bool{
   164  	"Host":              true, // not in Header map anyway
   165  	"Content-Length":    true,
   166  	"Transfer-Encoding": true,
   167  	"Trailer":           true,
   168  }
   169  
   170  // dumpAsReceived writes req to w in the form as it was received, or
   171  // at least as accurately as possible from the information retained in
   172  // the request.
   173  func dumpAsReceived(req *http.Request, w io.Writer) error {
   174  	return nil
   175  }
   176  
   177  // DumpRequest returns the as-received wire representation of req,
   178  // optionally including the request body, for debugging.
   179  // DumpRequest is semantically a no-op, but in order to
   180  // dump the body, it reads the body data into memory and
   181  // changes req.Body to refer to the in-memory copy.
   182  // The documentation for http.Request.Write details which fields
   183  // of req are used.
   184  func DumpRequest(req *http.Request, body bool) (dump []byte, err error) {
   185  	save := req.Body
   186  	if !body || req.Body == nil {
   187  		req.Body = nil
   188  	} else {
   189  		save, req.Body, err = drainBody(req.Body)
   190  		if err != nil {
   191  			return
   192  		}
   193  	}
   194  
   195  	var b bytes.Buffer
   196  
   197  	fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
   198  		req.URL.RequestURI(), req.ProtoMajor, req.ProtoMinor)
   199  
   200  	host := req.Host
   201  	if host == "" && req.URL != nil {
   202  		host = req.URL.Host
   203  	}
   204  	if host != "" {
   205  		fmt.Fprintf(&b, "Host: %s\r\n", host)
   206  	}
   207  
   208  	chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
   209  	if len(req.TransferEncoding) > 0 {
   210  		fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
   211  	}
   212  	if req.Close {
   213  		fmt.Fprintf(&b, "Connection: close\r\n")
   214  	}
   215  
   216  	err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
   217  	if err != nil {
   218  		return
   219  	}
   220  
   221  	io.WriteString(&b, "\r\n")
   222  
   223  	if req.Body != nil {
   224  		var dest io.Writer = &b
   225  		if chunked {
   226  			dest = NewChunkedWriter(dest)
   227  		}
   228  		_, err = io.Copy(dest, req.Body)
   229  		if chunked {
   230  			dest.(io.Closer).Close()
   231  			io.WriteString(&b, "\r\n")
   232  		}
   233  	}
   234  
   235  	req.Body = save
   236  	if err != nil {
   237  		return
   238  	}
   239  	dump = b.Bytes()
   240  	return
   241  }
   242  
   243  // errNoBody is a sentinel error value used by failureToReadBody so we can detect
   244  // that the lack of body was intentional.
   245  var errNoBody = errors.New("sentinel error value")
   246  
   247  // failureToReadBody is a io.ReadCloser that just returns errNoBody on
   248  // Read.  It's swapped in when we don't actually want to consume the
   249  // body, but need a non-nil one, and want to distinguish the error
   250  // from reading the dummy body.
   251  type failureToReadBody struct{}
   252  
   253  func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody }
   254  func (failureToReadBody) Close() error             { return nil }
   255  
   256  var emptyBody = ioutil.NopCloser(strings.NewReader(""))
   257  
   258  // DumpResponse is like DumpRequest but dumps a response.
   259  func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) {
   260  	var b bytes.Buffer
   261  	save := resp.Body
   262  	savecl := resp.ContentLength
   263  
   264  	if !body {
   265  		resp.Body = failureToReadBody{}
   266  	} else if resp.Body == nil {
   267  		resp.Body = emptyBody
   268  	} else {
   269  		save, resp.Body, err = drainBody(resp.Body)
   270  		if err != nil {
   271  			return
   272  		}
   273  	}
   274  	err = resp.Write(&b)
   275  	if err == errNoBody {
   276  		err = nil
   277  	}
   278  	resp.Body = save
   279  	resp.ContentLength = savecl
   280  	if err != nil {
   281  		return nil, err
   282  	}
   283  	return b.Bytes(), nil
   284  }