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  }