github.com/aloncn/graphics-go@v0.0.1/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, b, err 29 } 30 if err = b.Close(); err != nil { 31 return nil, b, 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 given request in its HTTP/1.x wire 179 // representation. It should only be used by servers to debug client 180 // requests. The returned representation is an approximation only; 181 // some details of the initial request are lost while parsing it into 182 // an http.Request. In particular, the order and case of header field 183 // names are lost. The order of values in multi-valued headers is kept 184 // intact. HTTP/2 requests are dumped in HTTP/1.x form, not in their 185 // original binary representations. 186 // 187 // If body is true, DumpRequest also returns the body. To do so, it 188 // consumes req.Body and then replaces it with a new io.ReadCloser 189 // that yields the same bytes. If DumpRequest returns an error, 190 // the state of req is undefined. 191 // 192 // The documentation for http.Request.Write details which fields 193 // of req are included in the dump. 194 func DumpRequest(req *http.Request, body bool) (dump []byte, err error) { 195 save := req.Body 196 if !body || req.Body == nil { 197 req.Body = nil 198 } else { 199 save, req.Body, err = drainBody(req.Body) 200 if err != nil { 201 return nil, err 202 } 203 } 204 205 var b bytes.Buffer 206 207 // By default, print out the unmodified req.RequestURI, which 208 // is always set for incoming server requests. But because we 209 // previously used req.URL.RequestURI and the docs weren't 210 // always so clear about when to use DumpRequest vs 211 // DumpRequestOut, fall back to the old way if the caller 212 // provides a non-server Request. 213 reqURI := req.RequestURI 214 if reqURI == "" { 215 reqURI = req.URL.RequestURI() 216 } 217 218 fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"), 219 reqURI, req.ProtoMajor, req.ProtoMinor) 220 221 absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://") 222 if !absRequestURI { 223 host := req.Host 224 if host == "" && req.URL != nil { 225 host = req.URL.Host 226 } 227 if host != "" { 228 fmt.Fprintf(&b, "Host: %s\r\n", host) 229 } 230 } 231 232 chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" 233 if len(req.TransferEncoding) > 0 { 234 fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ",")) 235 } 236 if req.Close { 237 fmt.Fprintf(&b, "Connection: close\r\n") 238 } 239 240 err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump) 241 if err != nil { 242 return 243 } 244 245 io.WriteString(&b, "\r\n") 246 247 if req.Body != nil { 248 var dest io.Writer = &b 249 if chunked { 250 dest = NewChunkedWriter(dest) 251 } 252 _, err = io.Copy(dest, req.Body) 253 if chunked { 254 dest.(io.Closer).Close() 255 io.WriteString(&b, "\r\n") 256 } 257 } 258 259 req.Body = save 260 if err != nil { 261 return 262 } 263 dump = b.Bytes() 264 return 265 } 266 267 // errNoBody is a sentinel error value used by failureToReadBody so we can detect 268 // that the lack of body was intentional. 269 var errNoBody = errors.New("sentinel error value") 270 271 // failureToReadBody is a io.ReadCloser that just returns errNoBody on 272 // Read. It's swapped in when we don't actually want to consume the 273 // body, but need a non-nil one, and want to distinguish the error 274 // from reading the dummy body. 275 type failureToReadBody struct{} 276 277 func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody } 278 func (failureToReadBody) Close() error { return nil } 279 280 var emptyBody = ioutil.NopCloser(strings.NewReader("")) 281 282 // DumpResponse is like DumpRequest but dumps a response. 283 func DumpResponse(resp *http.Response, body bool) (dump []byte, err error) { 284 var b bytes.Buffer 285 save := resp.Body 286 savecl := resp.ContentLength 287 288 if !body { 289 resp.Body = failureToReadBody{} 290 } else if resp.Body == nil { 291 resp.Body = emptyBody 292 } else { 293 save, resp.Body, err = drainBody(resp.Body) 294 if err != nil { 295 return nil, err 296 } 297 } 298 err = resp.Write(&b) 299 if err == errNoBody { 300 err = nil 301 } 302 resp.Body = save 303 resp.ContentLength = savecl 304 if err != nil { 305 return nil, err 306 } 307 return b.Bytes(), nil 308 }