github.com/gocuntian/go@v0.0.0-20160610041250-fee02d270bf8/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, b, err
    29  	}
    30  	if err = b.Close(); err != nil {
    31  		return nil, b, 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 for outgoing client requests. It
    59  // includes any headers that the standard http.Transport adds, such as
    60  // 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  
   102  	t := &http.Transport{
   103  		Dial: func(net, addr string) (net.Conn, error) {
   104  			return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
   105  		},
   106  	}
   107  	defer t.CloseIdleConnections()
   108  
   109  	// Wait for the request before replying with a dummy response:
   110  	go func() {
   111  		req, err := http.ReadRequest(bufio.NewReader(pr))
   112  		if err == nil {
   113  			// Ensure all the body is read; otherwise
   114  			// we'll get a partial dump.
   115  			io.Copy(ioutil.Discard, req.Body)
   116  			req.Body.Close()
   117  		}
   118  		dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
   119  	}()
   120  
   121  	_, err := t.RoundTrip(reqSend)
   122  
   123  	req.Body = save
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	dump := buf.Bytes()
   128  
   129  	// If we used a dummy body above, remove it now.
   130  	// TODO: if the req.ContentLength is large, we allocate memory
   131  	// unnecessarily just to slice it off here. But this is just
   132  	// a debug function, so this is acceptable for now. We could
   133  	// discard the body earlier if this matters.
   134  	if dummyBody {
   135  		if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 {
   136  			dump = dump[:i+4]
   137  		}
   138  	}
   139  	return dump, nil
   140  }
   141  
   142  // delegateReader is a reader that delegates to another reader,
   143  // once it arrives on a channel.
   144  type delegateReader struct {
   145  	c chan io.Reader
   146  	r io.Reader // nil until received from c
   147  }
   148  
   149  func (r *delegateReader) Read(p []byte) (int, error) {
   150  	if r.r == nil {
   151  		r.r = <-r.c
   152  	}
   153  	return r.r.Read(p)
   154  }
   155  
   156  // Return value if nonempty, def otherwise.
   157  func valueOrDefault(value, def string) string {
   158  	if value != "" {
   159  		return value
   160  	}
   161  	return def
   162  }
   163  
   164  var reqWriteExcludeHeaderDump = map[string]bool{
   165  	"Host":              true, // not in Header map anyway
   166  	"Transfer-Encoding": true,
   167  	"Trailer":           true,
   168  }
   169  
   170  // DumpRequest returns the given request in its HTTP/1.x wire
   171  // representation. It should only be used by servers to debug client
   172  // requests. The returned representation is an approximation only;
   173  // some details of the initial request are lost while parsing it into
   174  // an http.Request. In particular, the order and case of header field
   175  // names are lost. The order of values in multi-valued headers is kept
   176  // intact. HTTP/2 requests are dumped in HTTP/1.x form, not in their
   177  // original binary representations.
   178  //
   179  // If body is true, DumpRequest also returns the body. To do so, it
   180  // consumes req.Body and then replaces it with a new io.ReadCloser
   181  // that yields the same bytes. If DumpRequest returns an error,
   182  // the state of req is undefined.
   183  //
   184  // The documentation for http.Request.Write details which fields
   185  // of req are included in the dump.
   186  func DumpRequest(req *http.Request, body bool) ([]byte, error) {
   187  	var err error
   188  	save := req.Body
   189  	if !body || req.Body == nil {
   190  		req.Body = nil
   191  	} else {
   192  		save, req.Body, err = drainBody(req.Body)
   193  		if err != nil {
   194  			return nil, err
   195  		}
   196  	}
   197  
   198  	var b bytes.Buffer
   199  
   200  	// By default, print out the unmodified req.RequestURI, which
   201  	// is always set for incoming server requests. But because we
   202  	// previously used req.URL.RequestURI and the docs weren't
   203  	// always so clear about when to use DumpRequest vs
   204  	// DumpRequestOut, fall back to the old way if the caller
   205  	// provides a non-server Request.
   206  	reqURI := req.RequestURI
   207  	if reqURI == "" {
   208  		reqURI = req.URL.RequestURI()
   209  	}
   210  
   211  	fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
   212  		reqURI, req.ProtoMajor, req.ProtoMinor)
   213  
   214  	absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://")
   215  	if !absRequestURI {
   216  		host := req.Host
   217  		if host == "" && req.URL != nil {
   218  			host = req.URL.Host
   219  		}
   220  		if host != "" {
   221  			fmt.Fprintf(&b, "Host: %s\r\n", host)
   222  		}
   223  	}
   224  
   225  	chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
   226  	if len(req.TransferEncoding) > 0 {
   227  		fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
   228  	}
   229  	if req.Close {
   230  		fmt.Fprintf(&b, "Connection: close\r\n")
   231  	}
   232  
   233  	err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
   234  	if err != nil {
   235  		return nil, err
   236  	}
   237  
   238  	io.WriteString(&b, "\r\n")
   239  
   240  	if req.Body != nil {
   241  		var dest io.Writer = &b
   242  		if chunked {
   243  			dest = NewChunkedWriter(dest)
   244  		}
   245  		_, err = io.Copy(dest, req.Body)
   246  		if chunked {
   247  			dest.(io.Closer).Close()
   248  			io.WriteString(&b, "\r\n")
   249  		}
   250  	}
   251  
   252  	req.Body = save
   253  	if err != nil {
   254  		return nil, err
   255  	}
   256  	return b.Bytes(), nil
   257  }
   258  
   259  // errNoBody is a sentinel error value used by failureToReadBody so we
   260  // can detect that the lack of body was intentional.
   261  var errNoBody = errors.New("sentinel error value")
   262  
   263  // failureToReadBody is a io.ReadCloser that just returns errNoBody on
   264  // Read. It's swapped in when we don't actually want to consume
   265  // the body, but need a non-nil one, and want to distinguish the
   266  // error from reading the dummy body.
   267  type failureToReadBody struct{}
   268  
   269  func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody }
   270  func (failureToReadBody) Close() error             { return nil }
   271  
   272  // emptyBody is an instance of empty reader.
   273  var emptyBody = ioutil.NopCloser(strings.NewReader(""))
   274  
   275  // DumpResponse is like DumpRequest but dumps a response.
   276  func DumpResponse(resp *http.Response, body bool) ([]byte, error) {
   277  	var b bytes.Buffer
   278  	var err error
   279  	save := resp.Body
   280  	savecl := resp.ContentLength
   281  
   282  	if !body {
   283  		// For content length of zero. Make sure the body is an empty
   284  		// reader, instead of returning error through failureToReadBody{}.
   285  		if resp.ContentLength == 0 {
   286  			resp.Body = emptyBody
   287  		} else {
   288  			resp.Body = failureToReadBody{}
   289  		}
   290  	} else if resp.Body == nil {
   291  		resp.Body = emptyBody
   292  	} else {
   293  		save, resp.Body, err = drainBody(resp.Body)
   294  		if err != nil {
   295  			return nil, err
   296  		}
   297  	}
   298  	err = resp.Write(&b)
   299  	if err == errNoBody {
   300  		err = nil
   301  	}
   302  	resp.Body = save
   303  	resp.ContentLength = savecl
   304  	if err != nil {
   305  		return nil, err
   306  	}
   307  	return b.Bytes(), nil
   308  }