github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/pkg/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  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"net"
    14  	"net/http"
    15  	"net/url"
    16  	"strings"
    17  	"time"
    18  )
    19  
    20  // One of the copies, say from b to r2, could be avoided by using a more
    21  // elaborate trick where the other copy is made during Request/Response.Write.
    22  // This would complicate things too much, given that these functions are for
    23  // debugging only.
    24  func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
    25  	var buf bytes.Buffer
    26  	if _, err = buf.ReadFrom(b); err != nil {
    27  		return nil, nil, err
    28  	}
    29  	if err = b.Close(); err != nil {
    30  		return nil, nil, err
    31  	}
    32  	return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewBuffer(buf.Bytes())), nil
    33  }
    34  
    35  // dumpConn is a net.Conn which writes to Writer and reads from Reader
    36  type dumpConn struct {
    37  	io.Writer
    38  	io.Reader
    39  }
    40  
    41  func (c *dumpConn) Close() error                       { return nil }
    42  func (c *dumpConn) LocalAddr() net.Addr                { return nil }
    43  func (c *dumpConn) RemoteAddr() net.Addr               { return nil }
    44  func (c *dumpConn) SetDeadline(t time.Time) error      { return nil }
    45  func (c *dumpConn) SetReadDeadline(t time.Time) error  { return nil }
    46  func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
    47  
    48  // DumpRequestOut is like DumpRequest but includes
    49  // headers that the standard http.Transport adds,
    50  // such as User-Agent.
    51  func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
    52  	save := req.Body
    53  	if !body || req.Body == nil {
    54  		req.Body = nil
    55  	} else {
    56  		var err error
    57  		save, req.Body, err = drainBody(req.Body)
    58  		if err != nil {
    59  			return nil, err
    60  		}
    61  	}
    62  
    63  	// Since we're using the actual Transport code to write the request,
    64  	// switch to http so the Transport doesn't try to do an SSL
    65  	// negotiation with our dumpConn and its bytes.Buffer & pipe.
    66  	// The wire format for https and http are the same, anyway.
    67  	reqSend := req
    68  	if req.URL.Scheme == "https" {
    69  		reqSend = new(http.Request)
    70  		*reqSend = *req
    71  		reqSend.URL = new(url.URL)
    72  		*reqSend.URL = *req.URL
    73  		reqSend.URL.Scheme = "http"
    74  	}
    75  
    76  	// Use the actual Transport code to record what we would send
    77  	// on the wire, but not using TCP.  Use a Transport with a
    78  	// custom dialer that returns a fake net.Conn that waits
    79  	// for the full input (and recording it), and then responds
    80  	// with a dummy response.
    81  	var buf bytes.Buffer // records the output
    82  	pr, pw := io.Pipe()
    83  	dr := &delegateReader{c: make(chan io.Reader)}
    84  	// Wait for the request before replying with a dummy response:
    85  	go func() {
    86  		http.ReadRequest(bufio.NewReader(pr))
    87  		dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\n\r\n")
    88  	}()
    89  
    90  	t := &http.Transport{
    91  		Dial: func(net, addr string) (net.Conn, error) {
    92  			return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
    93  		},
    94  	}
    95  
    96  	_, err := t.RoundTrip(reqSend)
    97  
    98  	req.Body = save
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	return buf.Bytes(), nil
   103  }
   104  
   105  // delegateReader is a reader that delegates to another reader,
   106  // once it arrives on a channel.
   107  type delegateReader struct {
   108  	c chan io.Reader
   109  	r io.Reader // nil until received from c
   110  }
   111  
   112  func (r *delegateReader) Read(p []byte) (int, error) {
   113  	if r.r == nil {
   114  		r.r = <-r.c
   115  	}
   116  	return r.r.Read(p)
   117  }
   118  
   119  // Return value if nonempty, def otherwise.
   120  func valueOrDefault(value, def string) string {
   121  	if value != "" {
   122  		return value
   123  	}
   124  	return def
   125  }
   126  
   127  var reqWriteExcludeHeaderDump = map[string]bool{
   128  	"Host":              true, // not in Header map anyway
   129  	"Content-Length":    true,
   130  	"Transfer-Encoding": true,
   131  	"Trailer":           true,
   132  }
   133  
   134  // dumpAsReceived writes req to w in the form as it was received, or
   135  // at least as accurately as possible from the information retained in
   136  // the request.
   137  func dumpAsReceived(req *http.Request, w io.Writer) error {
   138  	return nil
   139  }
   140  
   141  // DumpRequest returns the as-received wire representation of req,
   142  // optionally including the request body, for debugging.
   143  // DumpRequest is semantically a no-op, but in order to
   144  // dump the body, it reads the body data into memory and
   145  // changes req.Body to refer to the in-memory copy.
   146  // The documentation for http.Request.Write details which fields
   147  // of req are used.
   148  func DumpRequest(req *http.Request, body bool) (dump []byte, err error) {
   149  	save := req.Body
   150  	if !body || req.Body == nil {
   151  		req.Body = nil
   152  	} else {
   153  		save, req.Body, err = drainBody(req.Body)
   154  		if err != nil {
   155  			return
   156  		}
   157  	}
   158  
   159  	var b bytes.Buffer
   160  
   161  	fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
   162  		req.URL.RequestURI(), req.ProtoMajor, req.ProtoMinor)
   163  
   164  	host := req.Host
   165  	if host == "" && req.URL != nil {
   166  		host = req.URL.Host
   167  	}
   168  	if host != "" {
   169  		fmt.Fprintf(&b, "Host: %s\r\n", host)
   170  	}
   171  
   172  	chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
   173  	if len(req.TransferEncoding) > 0 {
   174  		fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
   175  	}
   176  	if req.Close {
   177  		fmt.Fprintf(&b, "Connection: close\r\n")
   178  	}
   179  
   180  	err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
   181  	if err != nil {
   182  		return
   183  	}
   184  
   185  	io.WriteString(&b, "\r\n")
   186  
   187  	if req.Body != nil {
   188  		var dest io.Writer = &b
   189  		if chunked {
   190  			dest = NewChunkedWriter(dest)
   191  		}
   192  		_, err = io.Copy(dest, req.Body)
   193  		if chunked {
   194  			dest.(io.Closer).Close()
   195  			io.WriteString(&b, "\r\n")
   196  		}
   197  	}
   198  
   199  	req.Body = save
   200  	if err != nil {
   201  		return
   202  	}
   203  	dump = b.Bytes()
   204  	return
   205  }
   206  
   207  // DumpResponse is like DumpRequest but dumps a response.
   208  func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) {
   209  	var b bytes.Buffer
   210  	save := resp.Body
   211  	savecl := resp.ContentLength
   212  	if !body || resp.Body == nil {
   213  		resp.Body = nil
   214  		resp.ContentLength = 0
   215  	} else {
   216  		save, resp.Body, err = drainBody(resp.Body)
   217  		if err != nil {
   218  			return
   219  		}
   220  	}
   221  	err = resp.Write(&b)
   222  	resp.Body = save
   223  	resp.ContentLength = savecl
   224  	if err != nil {
   225  		return
   226  	}
   227  	dump = b.Bytes()
   228  	return
   229  }