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