...

Source file src/pkg/net/http/httputil/dump.go

     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	// drainBody reads all of b to memory and then returns two equivalent
    22	// ReadClosers yielding the same bytes.
    23	//
    24	// It returns an error if the initial slurp of all bytes fails. It does not attempt
    25	// to make the returned ReadClosers have identical error-matching behavior.
    26	func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
    27		if b == http.NoBody {
    28			// No copying needed. Preserve the magic sentinel meaning of NoBody.
    29			return http.NoBody, http.NoBody, nil
    30		}
    31		var buf bytes.Buffer
    32		if _, err = buf.ReadFrom(b); err != nil {
    33			return nil, b, err
    34		}
    35		if err = b.Close(); err != nil {
    36			return nil, b, err
    37		}
    38		return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
    39	}
    40	
    41	// dumpConn is a net.Conn which writes to Writer and reads from Reader
    42	type dumpConn struct {
    43		io.Writer
    44		io.Reader
    45	}
    46	
    47	func (c *dumpConn) Close() error                       { return nil }
    48	func (c *dumpConn) LocalAddr() net.Addr                { return nil }
    49	func (c *dumpConn) RemoteAddr() net.Addr               { return nil }
    50	func (c *dumpConn) SetDeadline(t time.Time) error      { return nil }
    51	func (c *dumpConn) SetReadDeadline(t time.Time) error  { return nil }
    52	func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
    53	
    54	type neverEnding byte
    55	
    56	func (b neverEnding) Read(p []byte) (n int, err error) {
    57		for i := range p {
    58			p[i] = byte(b)
    59		}
    60		return len(p), nil
    61	}
    62	
    63	// DumpRequestOut is like DumpRequest but for outgoing client requests. It
    64	// includes any headers that the standard http.Transport adds, such as
    65	// User-Agent.
    66	func DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
    67		save := req.Body
    68		dummyBody := false
    69		if !body || req.Body == nil {
    70			req.Body = nil
    71			if req.ContentLength != 0 {
    72				req.Body = ioutil.NopCloser(io.LimitReader(neverEnding('x'), req.ContentLength))
    73				dummyBody = true
    74			}
    75		} else {
    76			var err error
    77			save, req.Body, err = drainBody(req.Body)
    78			if err != nil {
    79				return nil, err
    80			}
    81		}
    82	
    83		// Since we're using the actual Transport code to write the request,
    84		// switch to http so the Transport doesn't try to do an SSL
    85		// negotiation with our dumpConn and its bytes.Buffer & pipe.
    86		// The wire format for https and http are the same, anyway.
    87		reqSend := req
    88		if req.URL.Scheme == "https" {
    89			reqSend = new(http.Request)
    90			*reqSend = *req
    91			reqSend.URL = new(url.URL)
    92			*reqSend.URL = *req.URL
    93			reqSend.URL.Scheme = "http"
    94		}
    95	
    96		// Use the actual Transport code to record what we would send
    97		// on the wire, but not using TCP.  Use a Transport with a
    98		// custom dialer that returns a fake net.Conn that waits
    99		// for the full input (and recording it), and then responds
   100		// with a dummy response.
   101		var buf bytes.Buffer // records the output
   102		pr, pw := io.Pipe()
   103		defer pr.Close()
   104		defer pw.Close()
   105		dr := &delegateReader{c: make(chan io.Reader)}
   106	
   107		t := &http.Transport{
   108			Dial: func(net, addr string) (net.Conn, error) {
   109				return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
   110			},
   111		}
   112		defer t.CloseIdleConnections()
   113	
   114		// Wait for the request before replying with a dummy response:
   115		go func() {
   116			req, err := http.ReadRequest(bufio.NewReader(pr))
   117			if err == nil {
   118				// Ensure all the body is read; otherwise
   119				// we'll get a partial dump.
   120				io.Copy(ioutil.Discard, req.Body)
   121				req.Body.Close()
   122			}
   123			dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n")
   124		}()
   125	
   126		_, err := t.RoundTrip(reqSend)
   127	
   128		req.Body = save
   129		if err != nil {
   130			return nil, err
   131		}
   132		dump := buf.Bytes()
   133	
   134		// If we used a dummy body above, remove it now.
   135		// TODO: if the req.ContentLength is large, we allocate memory
   136		// unnecessarily just to slice it off here. But this is just
   137		// a debug function, so this is acceptable for now. We could
   138		// discard the body earlier if this matters.
   139		if dummyBody {
   140			if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 {
   141				dump = dump[:i+4]
   142			}
   143		}
   144		return dump, nil
   145	}
   146	
   147	// delegateReader is a reader that delegates to another reader,
   148	// once it arrives on a channel.
   149	type delegateReader struct {
   150		c chan io.Reader
   151		r io.Reader // nil until received from c
   152	}
   153	
   154	func (r *delegateReader) Read(p []byte) (int, error) {
   155		if r.r == nil {
   156			r.r = <-r.c
   157		}
   158		return r.r.Read(p)
   159	}
   160	
   161	// Return value if nonempty, def otherwise.
   162	func valueOrDefault(value, def string) string {
   163		if value != "" {
   164			return value
   165		}
   166		return def
   167	}
   168	
   169	var reqWriteExcludeHeaderDump = map[string]bool{
   170		"Host":              true, // not in Header map anyway
   171		"Transfer-Encoding": true,
   172		"Trailer":           true,
   173	}
   174	
   175	// DumpRequest returns the given request in its HTTP/1.x wire
   176	// representation. It should only be used by servers to debug client
   177	// requests. The returned representation is an approximation only;
   178	// some details of the initial request are lost while parsing it into
   179	// an http.Request. In particular, the order and case of header field
   180	// names are lost. The order of values in multi-valued headers is kept
   181	// intact. HTTP/2 requests are dumped in HTTP/1.x form, not in their
   182	// original binary representations.
   183	//
   184	// If body is true, DumpRequest also returns the body. To do so, it
   185	// consumes req.Body and then replaces it with a new io.ReadCloser
   186	// that yields the same bytes. If DumpRequest returns an error,
   187	// the state of req is undefined.
   188	//
   189	// The documentation for http.Request.Write details which fields
   190	// of req are included in the dump.
   191	func DumpRequest(req *http.Request, body bool) ([]byte, error) {
   192		var err error
   193		save := req.Body
   194		if !body || req.Body == nil {
   195			req.Body = nil
   196		} else {
   197			save, req.Body, err = drainBody(req.Body)
   198			if err != nil {
   199				return nil, err
   200			}
   201		}
   202	
   203		var b bytes.Buffer
   204	
   205		// By default, print out the unmodified req.RequestURI, which
   206		// is always set for incoming server requests. But because we
   207		// previously used req.URL.RequestURI and the docs weren't
   208		// always so clear about when to use DumpRequest vs
   209		// DumpRequestOut, fall back to the old way if the caller
   210		// provides a non-server Request.
   211		reqURI := req.RequestURI
   212		if reqURI == "" {
   213			reqURI = req.URL.RequestURI()
   214		}
   215	
   216		fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
   217			reqURI, req.ProtoMajor, req.ProtoMinor)
   218	
   219		absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://")
   220		if !absRequestURI {
   221			host := req.Host
   222			if host == "" && req.URL != nil {
   223				host = req.URL.Host
   224			}
   225			if host != "" {
   226				fmt.Fprintf(&b, "Host: %s\r\n", host)
   227			}
   228		}
   229	
   230		chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
   231		if len(req.TransferEncoding) > 0 {
   232			fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
   233		}
   234		if req.Close {
   235			fmt.Fprintf(&b, "Connection: close\r\n")
   236		}
   237	
   238		err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
   239		if err != nil {
   240			return nil, err
   241		}
   242	
   243		io.WriteString(&b, "\r\n")
   244	
   245		if req.Body != nil {
   246			var dest io.Writer = &b
   247			if chunked {
   248				dest = NewChunkedWriter(dest)
   249			}
   250			_, err = io.Copy(dest, req.Body)
   251			if chunked {
   252				dest.(io.Closer).Close()
   253				io.WriteString(&b, "\r\n")
   254			}
   255		}
   256	
   257		req.Body = save
   258		if err != nil {
   259			return nil, err
   260		}
   261		return b.Bytes(), nil
   262	}
   263	
   264	// errNoBody is a sentinel error value used by failureToReadBody so we
   265	// can detect that the lack of body was intentional.
   266	var errNoBody = errors.New("sentinel error value")
   267	
   268	// failureToReadBody is a io.ReadCloser that just returns errNoBody on
   269	// Read. It's swapped in when we don't actually want to consume
   270	// the body, but need a non-nil one, and want to distinguish the
   271	// error from reading the dummy body.
   272	type failureToReadBody struct{}
   273	
   274	func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody }
   275	func (failureToReadBody) Close() error             { return nil }
   276	
   277	// emptyBody is an instance of empty reader.
   278	var emptyBody = ioutil.NopCloser(strings.NewReader(""))
   279	
   280	// DumpResponse is like DumpRequest but dumps a response.
   281	func DumpResponse(resp *http.Response, body bool) ([]byte, error) {
   282		var b bytes.Buffer
   283		var err error
   284		save := resp.Body
   285		savecl := resp.ContentLength
   286	
   287		if !body {
   288			// For content length of zero. Make sure the body is an empty
   289			// reader, instead of returning error through failureToReadBody{}.
   290			if resp.ContentLength == 0 {
   291				resp.Body = emptyBody
   292			} else {
   293				resp.Body = failureToReadBody{}
   294			}
   295		} else if resp.Body == nil {
   296			resp.Body = emptyBody
   297		} else {
   298			save, resp.Body, err = drainBody(resp.Body)
   299			if err != nil {
   300				return nil, err
   301			}
   302		}
   303		err = resp.Write(&b)
   304		if err == errNoBody {
   305			err = nil
   306		}
   307		resp.Body = save
   308		resp.ContentLength = savecl
   309		if err != nil {
   310			return nil, err
   311		}
   312		return b.Bytes(), nil
   313	}
   314	

View as plain text