// Copyright 2009 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.package httputilimport ()// drainBody reads all of b to memory and then returns two equivalent// ReadClosers yielding the same bytes.//// It returns an error if the initial slurp of all bytes fails. It does not attempt// to make the returned ReadClosers have identical error-matching behavior.func drainBody( io.ReadCloser) (, io.ReadCloser, error) {if == nil || == http.NoBody {// No copying needed. Preserve the magic sentinel meaning of NoBody.returnhttp.NoBody, http.NoBody, nil }varbytes.Bufferif _, = .ReadFrom(); != nil {returnnil, , }if = .Close(); != nil {returnnil, , }returnio.NopCloser(&), io.NopCloser(bytes.NewReader(.Bytes())), nil}// dumpConn is a net.Conn which writes to Writer and reads from Readertype dumpConn struct {io.Writerio.Reader}func ( *dumpConn) () error { returnnil }func ( *dumpConn) () net.Addr { returnnil }func ( *dumpConn) () net.Addr { returnnil }func ( *dumpConn) ( time.Time) error { returnnil }func ( *dumpConn) ( time.Time) error { returnnil }func ( *dumpConn) ( time.Time) error { returnnil }type neverEnding bytefunc ( neverEnding) ( []byte) ( int, error) {for := range { [] = byte() }returnlen(), nil}// outgoingLength is a copy of the unexported// (*http.Request).outgoingLength method.func outgoingLength( *http.Request) int64 {if .Body == nil || .Body == http.NoBody {return0 }if .ContentLength != 0 {return .ContentLength }return -1}// DumpRequestOut is like [DumpRequest] but for outgoing client requests. It// includes any headers that the standard [http.Transport] adds, such as// User-Agent.func ( *http.Request, bool) ([]byte, error) { := .Body := falseif ! { := outgoingLength()if != 0 { .Body = io.NopCloser(io.LimitReader(neverEnding('x'), )) = true } } else {varerror , .Body, = drainBody(.Body)if != nil {returnnil, } }// Since we're using the actual Transport code to write the request, // switch to http so the Transport doesn't try to do an SSL // negotiation with our dumpConn and its bytes.Buffer & pipe. // The wire format for https and http are the same, anyway. := if .URL.Scheme == "https" { = new(http.Request) * = * .URL = new(url.URL) *.URL = *.URL .URL.Scheme = "http" }// Use the actual Transport code to record what we would send // on the wire, but not using TCP. Use a Transport with a // custom dialer that returns a fake net.Conn that waits // for the full input (and recording it), and then responds // with a dummy response.varbytes.Buffer// records the output , := io.Pipe()defer .Close()defer .Close() := &delegateReader{c: make(chanio.Reader)} := &http.Transport{Dial: func(, string) (net.Conn, error) {return &dumpConn{io.MultiWriter(&, ), }, nil }, }defer .CloseIdleConnections()// We need this channel to ensure that the reader // goroutine exits if t.RoundTrip returns an error. // See golang.org/issue/32571. := make(chanstruct{})// Wait for the request before replying with a dummy response:gofunc() { , := http.ReadRequest(bufio.NewReader())if == nil {// Ensure all the body is read; otherwise // we'll get a partial dump.io.Copy(io.Discard, .Body) .Body.Close() }select {case .c<-strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n"):case<-:// Ensure delegateReader.Read doesn't block forever if we get an error.close(.c) } }() , := .RoundTrip() .Body = if != nil { .Close() .err = close()returnnil, } := .Bytes()// If we used a dummy body above, remove it now. // TODO: if the req.ContentLength is large, we allocate memory // unnecessarily just to slice it off here. But this is just // a debug function, so this is acceptable for now. We could // discard the body earlier if this matters.if {if := bytes.Index(, []byte("\r\n\r\n")); >= 0 { = [:+4] } }return , nil}// delegateReader is a reader that delegates to another reader,// once it arrives on a channel.type delegateReader struct { c chanio.Reader err error// only used if r is nil and c is closed. r io.Reader// nil until received from c}func ( *delegateReader) ( []byte) (int, error) {if .r == nil {varboolif .r, = <-.c; ! {return0, .err } }return .r.Read()}// Return value if nonempty, def otherwise.func valueOrDefault(, string) string {if != "" {return }return}var reqWriteExcludeHeaderDump = map[string]bool{"Host": true, // not in Header map anyway"Transfer-Encoding": true,"Trailer": true,}// DumpRequest returns the given request in its HTTP/1.x wire// representation. It should only be used by servers to debug client// requests. The returned representation is an approximation only;// some details of the initial request are lost while parsing it into// an [http.Request]. In particular, the order and case of header field// names are lost. The order of values in multi-valued headers is kept// intact. HTTP/2 requests are dumped in HTTP/1.x form, not in their// original binary representations.//// If body is true, DumpRequest also returns the body. To do so, it// consumes req.Body and then replaces it with a new [io.ReadCloser]// that yields the same bytes. If DumpRequest returns an error,// the state of req is undefined.//// The documentation for [http.Request.Write] details which fields// of req are included in the dump.func ( *http.Request, bool) ([]byte, error) {varerror := .Bodyif ! || .Body == nil { .Body = nil } else { , .Body, = drainBody(.Body)if != nil {returnnil, } }varbytes.Buffer// By default, print out the unmodified req.RequestURI, which // is always set for incoming server requests. But because we // previously used req.URL.RequestURI and the docs weren't // always so clear about when to use DumpRequest vs // DumpRequestOut, fall back to the old way if the caller // provides a non-server Request. := .RequestURIif == "" { = .URL.RequestURI() }fmt.Fprintf(&, "%s %s HTTP/%d.%d\r\n", valueOrDefault(.Method, "GET"), , .ProtoMajor, .ProtoMinor) := strings.HasPrefix(.RequestURI, "http://") || strings.HasPrefix(.RequestURI, "https://")if ! { := .Hostif == "" && .URL != nil { = .URL.Host }if != "" {fmt.Fprintf(&, "Host: %s\r\n", ) } } := len(.TransferEncoding) > 0 && .TransferEncoding[0] == "chunked"iflen(.TransferEncoding) > 0 {fmt.Fprintf(&, "Transfer-Encoding: %s\r\n", strings.Join(.TransferEncoding, ",")) } = .Header.WriteSubset(&, reqWriteExcludeHeaderDump)if != nil {returnnil, }io.WriteString(&, "\r\n")if .Body != nil {vario.Writer = &if { = NewChunkedWriter() } _, = io.Copy(, .Body)if { .(io.Closer).Close()io.WriteString(&, "\r\n") } } .Body = if != nil {returnnil, }return .Bytes(), nil}// errNoBody is a sentinel error value used by failureToReadBody so we// can detect that the lack of body was intentional.var errNoBody = errors.New("sentinel error value")// failureToReadBody is an io.ReadCloser that just returns errNoBody on// Read. It's swapped in when we don't actually want to consume// the body, but need a non-nil one, and want to distinguish the// error from reading the dummy body.type failureToReadBody struct{}func (failureToReadBody) ([]byte) (int, error) { return0, errNoBody }func (failureToReadBody) () error { returnnil }// emptyBody is an instance of empty reader.var emptyBody = io.NopCloser(strings.NewReader(""))// DumpResponse is like DumpRequest but dumps a response.func ( *http.Response, bool) ([]byte, error) {varbytes.Buffervarerror := .Body := .ContentLengthif ! {// For content length of zero. Make sure the body is an empty // reader, instead of returning error through failureToReadBody{}.if .ContentLength == 0 { .Body = emptyBody } else { .Body = failureToReadBody{} } } elseif .Body == nil { .Body = emptyBody } else { , .Body, = drainBody(.Body)if != nil {returnnil, } } = .Write(&)if == errNoBody { = nil } .Body = .ContentLength = if != nil {returnnil, }return .Bytes(), nil}
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.