github.com/Kolosok86/http@v0.1.2/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 "net" 14 "net/url" 15 "strings" 16 "time" 17 18 "github.com/Kolosok86/http" 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 == nil || 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 io.NopCloser(&buf), io.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 // outgoingLength is a copy of the unexported 64 // (*http.Request).outgoingLength method. 65 func outgoingLength(req *http.Request) int64 { 66 if req.Body == nil || req.Body == http.NoBody { 67 return 0 68 } 69 if req.ContentLength != 0 { 70 return req.ContentLength 71 } 72 return -1 73 } 74 75 // DumpRequestOut is like DumpRequest but for outgoing client requests. It 76 // includes any headers that the standard http.Transport adds, such as 77 // User-Agent. 78 func DumpRequestOut(req *http.Request, body bool) ([]byte, error) { 79 save := req.Body 80 dummyBody := false 81 if !body { 82 contentLength := outgoingLength(req) 83 if contentLength != 0 { 84 req.Body = io.NopCloser(io.LimitReader(neverEnding('x'), contentLength)) 85 dummyBody = true 86 } 87 } else { 88 var err error 89 save, req.Body, err = drainBody(req.Body) 90 if err != nil { 91 return nil, err 92 } 93 } 94 95 // Since we're using the actual Transport code to write the request, 96 // switch to http so the Transport doesn't try to do an SSL 97 // negotiation with our dumpConn and its bytes.Buffer & pipe. 98 // The wire format for https and http are the same, anyway. 99 reqSend := req 100 if req.URL.Scheme == "https" { 101 reqSend = new(http.Request) 102 *reqSend = *req 103 reqSend.URL = new(url.URL) 104 *reqSend.URL = *req.URL 105 reqSend.URL.Scheme = "http" 106 } 107 108 // Use the actual Transport code to record what we would send 109 // on the wire, but not using TCP. Use a Transport with a 110 // custom dialer that returns a fake net.Conn that waits 111 // for the full input (and recording it), and then responds 112 // with a dummy response. 113 var buf bytes.Buffer // records the output 114 pr, pw := io.Pipe() 115 defer pr.Close() 116 defer pw.Close() 117 dr := &delegateReader{c: make(chan io.Reader)} 118 119 t := &http.Transport{ 120 Dial: func(net, addr string) (net.Conn, error) { 121 return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil 122 }, 123 } 124 defer t.CloseIdleConnections() 125 126 // We need this channel to ensure that the reader 127 // goroutine exits if t.RoundTrip returns an error. 128 // See golang.org/issue/32571. 129 quitReadCh := make(chan struct{}) 130 // Wait for the request before replying with a dummy response: 131 go func() { 132 req, err := http.ReadRequest(bufio.NewReader(pr)) 133 if err == nil { 134 // Ensure all the body is read; otherwise 135 // we'll get a partial dump. 136 io.Copy(io.Discard, req.Body) 137 req.Body.Close() 138 } 139 select { 140 case dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n"): 141 case <-quitReadCh: 142 // Ensure delegateReader.Read doesn't block forever if we get an error. 143 close(dr.c) 144 } 145 }() 146 147 _, err := t.RoundTrip(reqSend) 148 149 req.Body = save 150 if err != nil { 151 pw.Close() 152 dr.err = err 153 close(quitReadCh) 154 return nil, err 155 } 156 dump := buf.Bytes() 157 158 // If we used a dummy body above, remove it now. 159 // TODO: if the req.ContentLength is large, we allocate memory 160 // unnecessarily just to slice it off here. But this is just 161 // a debug function, so this is acceptable for now. We could 162 // discard the body earlier if this matters. 163 if dummyBody { 164 if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 { 165 dump = dump[:i+4] 166 } 167 } 168 return dump, nil 169 } 170 171 // delegateReader is a reader that delegates to another reader, 172 // once it arrives on a channel. 173 type delegateReader struct { 174 c chan io.Reader 175 err error // only used if r is nil and c is closed. 176 r io.Reader // nil until received from c 177 } 178 179 func (r *delegateReader) Read(p []byte) (int, error) { 180 if r.r == nil { 181 var ok bool 182 if r.r, ok = <-r.c; !ok { 183 return 0, r.err 184 } 185 } 186 return r.r.Read(p) 187 } 188 189 // Return value if nonempty, def otherwise. 190 func valueOrDefault(value, def string) string { 191 if value != "" { 192 return value 193 } 194 return def 195 } 196 197 var reqWriteExcludeHeaderDump = map[string]bool{ 198 "Host": true, // not in Header map anyway 199 "Transfer-Encoding": true, 200 "Trailer": true, 201 } 202 203 // DumpRequest returns the given request in its HTTP/1.x wire 204 // representation. It should only be used by servers to debug client 205 // requests. The returned representation is an approximation only; 206 // some details of the initial request are lost while parsing it into 207 // an http.Request. In particular, the order and case of header field 208 // names are lost. The order of values in multi-valued headers is kept 209 // intact. HTTP/2 requests are dumped in HTTP/1.x form, not in their 210 // original binary representations. 211 // 212 // If body is true, DumpRequest also returns the body. To do so, it 213 // consumes req.Body and then replaces it with a new io.ReadCloser 214 // that yields the same bytes. If DumpRequest returns an error, 215 // the state of req is undefined. 216 // 217 // The documentation for http.Request.Write details which fields 218 // of req are included in the dump. 219 func DumpRequest(req *http.Request, body bool) ([]byte, error) { 220 var err error 221 save := req.Body 222 if !body || req.Body == nil { 223 req.Body = nil 224 } else { 225 save, req.Body, err = drainBody(req.Body) 226 if err != nil { 227 return nil, err 228 } 229 } 230 231 var b bytes.Buffer 232 233 // By default, print out the unmodified req.RequestURI, which 234 // is always set for incoming server requests. But because we 235 // previously used req.URL.RequestURI and the docs weren't 236 // always so clear about when to use DumpRequest vs 237 // DumpRequestOut, fall back to the old way if the caller 238 // provides a non-server Request. 239 reqURI := req.RequestURI 240 if reqURI == "" { 241 reqURI = req.URL.RequestURI() 242 } 243 244 fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"), 245 reqURI, req.ProtoMajor, req.ProtoMinor) 246 247 absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://") 248 if !absRequestURI { 249 host := req.Host 250 if host == "" && req.URL != nil { 251 host = req.URL.Host 252 } 253 if host != "" { 254 fmt.Fprintf(&b, "Host: %s\r\n", host) 255 } 256 } 257 258 chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" 259 if len(req.TransferEncoding) > 0 { 260 fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ",")) 261 } 262 263 err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump) 264 if err != nil { 265 return nil, err 266 } 267 268 io.WriteString(&b, "\r\n") 269 270 if req.Body != nil { 271 var dest io.Writer = &b 272 if chunked { 273 dest = NewChunkedWriter(dest) 274 } 275 _, err = io.Copy(dest, req.Body) 276 if chunked { 277 dest.(io.Closer).Close() 278 io.WriteString(&b, "\r\n") 279 } 280 } 281 282 req.Body = save 283 if err != nil { 284 return nil, err 285 } 286 return b.Bytes(), nil 287 } 288 289 // errNoBody is a sentinel error value used by failureToReadBody so we 290 // can detect that the lack of body was intentional. 291 var errNoBody = errors.New("sentinel error value") 292 293 // failureToReadBody is an io.ReadCloser that just returns errNoBody on 294 // Read. It's swapped in when we don't actually want to consume 295 // the body, but need a non-nil one, and want to distinguish the 296 // error from reading the dummy body. 297 type failureToReadBody struct{} 298 299 func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody } 300 func (failureToReadBody) Close() error { return nil } 301 302 // emptyBody is an instance of empty reader. 303 var emptyBody = io.NopCloser(strings.NewReader("")) 304 305 // DumpResponse is like DumpRequest but dumps a response. 306 func DumpResponse(resp *http.Response, body bool) ([]byte, error) { 307 var b bytes.Buffer 308 var err error 309 save := resp.Body 310 savecl := resp.ContentLength 311 312 if !body { 313 // For content length of zero. Make sure the body is an empty 314 // reader, instead of returning error through failureToReadBody{}. 315 if resp.ContentLength == 0 { 316 resp.Body = emptyBody 317 } else { 318 resp.Body = failureToReadBody{} 319 } 320 } else if resp.Body == nil { 321 resp.Body = emptyBody 322 } else { 323 save, resp.Body, err = drainBody(resp.Body) 324 if err != nil { 325 return nil, err 326 } 327 } 328 err = resp.Write(&b) 329 if err == errNoBody { 330 err = nil 331 } 332 resp.Body = save 333 resp.ContentLength = savecl 334 if err != nil { 335 return nil, err 336 } 337 return b.Bytes(), nil 338 }