github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/pkg/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 "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 // 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. 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 }