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 5package httputil 6 7import ( 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. 24func 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 36type dumpConn struct { 37 io.Writer 38 io.Reader 39} 40 41func (c *dumpConn) Close() error { return nil } 42func (c *dumpConn) LocalAddr() net.Addr { return nil } 43func (c *dumpConn) RemoteAddr() net.Addr { return nil } 44func (c *dumpConn) SetDeadline(t time.Time) error { return nil } 45func (c *dumpConn) SetReadDeadline(t time.Time) error { return nil } 46func (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. 51func 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. 107type delegateReader struct { 108 c chan io.Reader 109 r io.Reader // nil until received from c 110} 111 112func (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. 120func valueOrDefault(value, def string) string { 121 if value != "" { 122 return value 123 } 124 return def 125} 126 127var 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. 137func 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. 148func 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. 208func 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} 230