github.com/mdempsky/go@v0.0.0-20151201204031-5dd372bd1e70/src/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 "errors" 11 "fmt" 12 "io" 13 "io/ioutil" 14 "net" 15 "net/http" 16 "net/url" 17 "strings" 18 "time" 19 ) 20 21 // One of the copies, say from b to r2, could be avoided by using a more 22 // elaborate trick where the other copy is made during Request/Response.Write. 23 // This would complicate things too much, given that these functions are for 24 // debugging only. 25 func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) { 26 var buf bytes.Buffer 27 if _, err = buf.ReadFrom(b); err != nil { 28 return nil, nil, err 29 } 30 if err = b.Close(); err != nil { 31 return nil, nil, err 32 } 33 return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil 34 } 35 36 // dumpConn is a net.Conn which writes to Writer and reads from Reader 37 type dumpConn struct { 38 io.Writer 39 io.Reader 40 } 41 42 func (c *dumpConn) Close() error { return nil } 43 func (c *dumpConn) LocalAddr() net.Addr { return nil } 44 func (c *dumpConn) RemoteAddr() net.Addr { return nil } 45 func (c *dumpConn) SetDeadline(t time.Time) error { return nil } 46 func (c *dumpConn) SetReadDeadline(t time.Time) error { return nil } 47 func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil } 48 49 type neverEnding byte 50 51 func (b neverEnding) Read(p []byte) (n int, err error) { 52 for i := range p { 53 p[i] = byte(b) 54 } 55 return len(p), nil 56 } 57 58 // DumpRequestOut is like DumpRequest but for outgoing client requests. It 59 // includes any headers that the standard http.Transport adds, such as 60 // User-Agent. 61 func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { 62 save := req.Body 63 dummyBody := false 64 if !body || req.Body == nil { 65 req.Body = nil 66 if req.ContentLength != 0 { 67 req.Body = ioutil.NopCloser(io.LimitReader(neverEnding('x'), req.ContentLength)) 68 dummyBody = true 69 } 70 } else { 71 var err error 72 save, req.Body, err = drainBody(req.Body) 73 if err != nil { 74 return nil, err 75 } 76 } 77 78 // Since we're using the actual Transport code to write the request, 79 // switch to http so the Transport doesn't try to do an SSL 80 // negotiation with our dumpConn and its bytes.Buffer & pipe. 81 // The wire format for https and http are the same, anyway. 82 reqSend := req 83 if req.URL.Scheme == "https" { 84 reqSend = new(http.Request) 85 *reqSend = *req 86 reqSend.URL = new(url.URL) 87 *reqSend.URL = *req.URL 88 reqSend.URL.Scheme = "http" 89 } 90 91 // Use the actual Transport code to record what we would send 92 // on the wire, but not using TCP. Use a Transport with a 93 // custom dialer that returns a fake net.Conn that waits 94 // for the full input (and recording it), and then responds 95 // with a dummy response. 96 var buf bytes.Buffer // records the output 97 pr, pw := io.Pipe() 98 defer pr.Close() 99 defer pw.Close() 100 dr := &delegateReader{c: make(chan io.Reader)} 101 102 t := &http.Transport{ 103 Dial: func(net, addr string) (net.Conn, error) { 104 return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil 105 }, 106 } 107 defer t.CloseIdleConnections() 108 109 // Wait for the request before replying with a dummy response: 110 go func() { 111 req, err := http.ReadRequest(bufio.NewReader(pr)) 112 if err == nil { 113 // Ensure all the body is read; otherwise 114 // we'll get a partial dump. 115 io.Copy(ioutil.Discard, req.Body) 116 req.Body.Close() 117 } 118 dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n") 119 }() 120 121 _, err := t.RoundTrip(reqSend) 122 123 req.Body = save 124 if err != nil { 125 return nil, err 126 } 127 dump := buf.Bytes() 128 129 // If we used a dummy body above, remove it now. 130 // TODO: if the req.ContentLength is large, we allocate memory 131 // unnecessarily just to slice it off here. But this is just 132 // a debug function, so this is acceptable for now. We could 133 // discard the body earlier if this matters. 134 if dummyBody { 135 if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 { 136 dump = dump[:i+4] 137 } 138 } 139 return dump, nil 140 } 141 142 // delegateReader is a reader that delegates to another reader, 143 // once it arrives on a channel. 144 type delegateReader struct { 145 c chan io.Reader 146 r io.Reader // nil until received from c 147 } 148 149 func (r *delegateReader) Read(p []byte) (int, error) { 150 if r.r == nil { 151 r.r = <-r.c 152 } 153 return r.r.Read(p) 154 } 155 156 // Return value if nonempty, def otherwise. 157 func valueOrDefault(value, def string) string { 158 if value != "" { 159 return value 160 } 161 return def 162 } 163 164 var reqWriteExcludeHeaderDump = map[string]bool{ 165 "Host": true, // not in Header map anyway 166 "Content-Length": true, 167 "Transfer-Encoding": true, 168 "Trailer": true, 169 } 170 171 // dumpAsReceived writes req to w in the form as it was received, or 172 // at least as accurately as possible from the information retained in 173 // the request. 174 func dumpAsReceived(req *http.Request, w io.Writer) error { 175 return nil 176 } 177 178 // DumpRequest returns the as-received wire representation of req, optionally 179 // including the request body, for debugging. It is for use in servers; use 180 // DumpRequestOut for client requests. 181 // 182 // DumpRequest is semantically a no-op, but in order to 183 // dump the body, it reads the body data into memory and 184 // changes req.Body to refer to the in-memory copy. 185 // The documentation for http.Request.Write details which fields 186 // of req are used. 187 func DumpRequest(req *http.Request, body bool) (dump []byte, err error) { 188 save := req.Body 189 if !body || req.Body == nil { 190 req.Body = nil 191 } else { 192 save, req.Body, err = drainBody(req.Body) 193 if err != nil { 194 return 195 } 196 } 197 198 var b bytes.Buffer 199 200 fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"), 201 req.URL.RequestURI(), req.ProtoMajor, req.ProtoMinor) 202 203 host := req.Host 204 if host == "" && req.URL != nil { 205 host = req.URL.Host 206 } 207 if host != "" { 208 fmt.Fprintf(&b, "Host: %s\r\n", host) 209 } 210 211 chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" 212 if len(req.TransferEncoding) > 0 { 213 fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ",")) 214 } 215 if req.Close { 216 fmt.Fprintf(&b, "Connection: close\r\n") 217 } 218 219 err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump) 220 if err != nil { 221 return 222 } 223 224 io.WriteString(&b, "\r\n") 225 226 if req.Body != nil { 227 var dest io.Writer = &b 228 if chunked { 229 dest = NewChunkedWriter(dest) 230 } 231 _, err = io.Copy(dest, req.Body) 232 if chunked { 233 dest.(io.Closer).Close() 234 io.WriteString(&b, "\r\n") 235 } 236 } 237 238 req.Body = save 239 if err != nil { 240 return 241 } 242 dump = b.Bytes() 243 return 244 } 245 246 // errNoBody is a sentinel error value used by failureToReadBody so we can detect 247 // that the lack of body was intentional. 248 var errNoBody = errors.New("sentinel error value") 249 250 // failureToReadBody is a io.ReadCloser that just returns errNoBody on 251 // Read. It's swapped in when we don't actually want to consume the 252 // body, but need a non-nil one, and want to distinguish the error 253 // from reading the dummy body. 254 type failureToReadBody struct{} 255 256 func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody } 257 func (failureToReadBody) Close() error { return nil } 258 259 var emptyBody = ioutil.NopCloser(strings.NewReader("")) 260 261 // DumpResponse is like DumpRequest but dumps a response. 262 func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) { 263 var b bytes.Buffer 264 save := resp.Body 265 savecl := resp.ContentLength 266 267 if !body { 268 resp.Body = failureToReadBody{} 269 } else if resp.Body == nil { 270 resp.Body = emptyBody 271 } else { 272 save, resp.Body, err = drainBody(resp.Body) 273 if err != nil { 274 return 275 } 276 } 277 err = resp.Write(&b) 278 if err == errNoBody { 279 err = nil 280 } 281 resp.Body = save 282 resp.ContentLength = savecl 283 if err != nil { 284 return nil, err 285 } 286 return b.Bytes(), nil 287 }