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