github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/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 "github.com/hxx258456/ccgo/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 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 if req.Close { 263 fmt.Fprintf(&b, "Connection: close\r\n") 264 } 265 266 err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump) 267 if err != nil { 268 return nil, err 269 } 270 271 io.WriteString(&b, "\r\n") 272 273 if req.Body != nil { 274 var dest io.Writer = &b 275 if chunked { 276 dest = NewChunkedWriter(dest) 277 } 278 _, err = io.Copy(dest, req.Body) 279 if chunked { 280 dest.(io.Closer).Close() 281 io.WriteString(&b, "\r\n") 282 } 283 } 284 285 req.Body = save 286 if err != nil { 287 return nil, err 288 } 289 return b.Bytes(), nil 290 } 291 292 // errNoBody is a sentinel error value used by failureToReadBody so we 293 // can detect that the lack of body was intentional. 294 var errNoBody = errors.New("sentinel error value") 295 296 // failureToReadBody is a io.ReadCloser that just returns errNoBody on 297 // Read. It's swapped in when we don't actually want to consume 298 // the body, but need a non-nil one, and want to distinguish the 299 // error from reading the dummy body. 300 type failureToReadBody struct{} 301 302 func (failureToReadBody) Read([]byte) (int, error) { return 0, errNoBody } 303 func (failureToReadBody) Close() error { return nil } 304 305 // emptyBody is an instance of empty reader. 306 var emptyBody = io.NopCloser(strings.NewReader("")) 307 308 // DumpResponse is like DumpRequest but dumps a response. 309 func DumpResponse(resp *http.Response, body bool) ([]byte, error) { 310 var b bytes.Buffer 311 var err error 312 save := resp.Body 313 savecl := resp.ContentLength 314 315 if !body { 316 // For content length of zero. Make sure the body is an empty 317 // reader, instead of returning error through failureToReadBody{}. 318 if resp.ContentLength == 0 { 319 resp.Body = emptyBody 320 } else { 321 resp.Body = failureToReadBody{} 322 } 323 } else if resp.Body == nil { 324 resp.Body = emptyBody 325 } else { 326 save, resp.Body, err = drainBody(resp.Body) 327 if err != nil { 328 return nil, err 329 } 330 } 331 err = resp.Write(&b) 332 if err == errNoBody { 333 err = nil 334 } 335 resp.Body = save 336 resp.ContentLength = savecl 337 if err != nil { 338 return nil, err 339 } 340 return b.Bytes(), nil 341 }