Home | History | Annotate | Download | only in httputil
      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