src/pkg/net/http/httputil/dump.go - The Go Programming Language

Golang

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		"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		// customer 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(pw, &buf), 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	}