github.com/yanyiwu/go@v0.0.0-20150106053140-03d6637dbb7f/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  
   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  	"Content-Length":    true,
   167  	"Transfer-Encoding": true,
   168  	"Trailer":           true,
   169  }
   170  
   171  // dumpAsReceived writes req to w in the form as it was received, or
   172  // at least as accurately as possible from the information retained in
   173  // the request.
   174  func dumpAsReceived(req *http.Request, w io.Writer) error {
   175  	return nil
   176  }
   177  
   178  // DumpRequest returns the as-received wire representation of req,
   179  // optionally including the request body, for debugging.
   180  // DumpRequest is semantically a no-op, but in order to
   181  // dump the body, it reads the body data into memory and
   182  // changes req.Body to refer to the in-memory copy.
   183  // The documentation for http.Request.Write details which fields
   184  // of req are used.
   185  func DumpRequest(req *http.Request, body bool) (dump []byte, err error) {
   186  	save := req.Body
   187  	if !body || req.Body == nil {
   188  		req.Body = nil
   189  	} else {
   190  		save, req.Body, err = drainBody(req.Body)
   191  		if err != nil {
   192  			return
   193  		}
   194  	}
   195  
   196  	var b bytes.Buffer
   197  
   198  	fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
   199  		req.URL.RequestURI(), req.ProtoMajor, req.ProtoMinor)
   200  
   201  	host := req.Host
   202  	if host == "" && req.URL != nil {
   203  		host = req.URL.Host
   204  	}
   205  	if host != "" {
   206  		fmt.Fprintf(&b, "Host: %s\r\n", host)
   207  	}
   208  
   209  	chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
   210  	if len(req.TransferEncoding) > 0 {
   211  		fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
   212  	}
   213  	if req.Close {
   214  		fmt.Fprintf(&b, "Connection: close\r\n")
   215  	}
   216  
   217  	err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
   218  	if err != nil {
   219  		return
   220  	}
   221  
   222  	io.WriteString(&b, "\r\n")
   223  
   224  	if req.Body != nil {
   225  		var dest io.Writer = &b
   226  		if chunked {
   227  			dest = NewChunkedWriter(dest)
   228  		}
   229  		_, err = io.Copy(dest, req.Body)
   230  		if chunked {
   231  			dest.(io.Closer).Close()
   232  			io.WriteString(&b, "\r\n")
   233  		}
   234  	}
   235  
   236  	req.Body = save
   237  	if err != nil {
   238  		return
   239  	}
   240  	dump = b.Bytes()
   241  	return
   242  }
   243  
   244  // errNoBody is a sentinel error value used by failureToReadBody so we can detect
   245  // that the lack of body was intentional.
   246  var errNoBody = errors.New("sentinel error value")
   247  
   248  // failureToReadBody is a io.ReadCloser that just returns errNoBody on
   249  // Read.  It's swapped in when we don't actually want to consume the
   250  // body, but need a non-nil one, and want to distinguish the error
   251  // from reading the dummy body.
   252  type failureToReadBody struct{}
   253  
   254  func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody }
   255  func (failureToReadBody) Close() error             { return nil }
   256  
   257  var emptyBody = ioutil.NopCloser(strings.NewReader(""))
   258  
   259  // DumpResponse is like DumpRequest but dumps a response.
   260  func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) {
   261  	var b bytes.Buffer
   262  	save := resp.Body
   263  	savecl := resp.ContentLength
   264  
   265  	if !body {
   266  		resp.Body = failureToReadBody{}
   267  	} else if resp.Body == nil {
   268  		resp.Body = emptyBody
   269  	} else {
   270  		save, resp.Body, err = drainBody(resp.Body)
   271  		if err != nil {
   272  			return
   273  		}
   274  	}
   275  	err = resp.Write(&b)
   276  	if err == errNoBody {
   277  		err = nil
   278  	}
   279  	resp.Body = save
   280  	resp.ContentLength = savecl
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  	return b.Bytes(), nil
   285  }