mirror of
https://github.com/tinygo-org/net.git
synced 2026-08-09 14:13:39 +00:00
@@ -0,0 +1,339 @@
|
||||
// TINYGO: The following is copied from Go 1.21.5 official implementation.
|
||||
|
||||
// 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 httputil
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 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(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
|
||||
if b == nil || b == http.NoBody {
|
||||
// No copying needed. Preserve the magic sentinel meaning of NoBody.
|
||||
return http.NoBody, http.NoBody, nil
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if _, err = buf.ReadFrom(b); err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
if err = b.Close(); err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil
|
||||
}
|
||||
|
||||
// dumpConn is a net.Conn which writes to Writer and reads from Reader
|
||||
type dumpConn struct {
|
||||
io.Writer
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func (c *dumpConn) Close() error { return nil }
|
||||
func (c *dumpConn) LocalAddr() net.Addr { return nil }
|
||||
func (c *dumpConn) RemoteAddr() net.Addr { return nil }
|
||||
func (c *dumpConn) SetDeadline(t time.Time) error { return nil }
|
||||
func (c *dumpConn) SetReadDeadline(t time.Time) error { return nil }
|
||||
func (c *dumpConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||
|
||||
type neverEnding byte
|
||||
|
||||
func (b neverEnding) Read(p []byte) (n int, err error) {
|
||||
for i := range p {
|
||||
p[i] = byte(b)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
// outgoingLength is a copy of the unexported
|
||||
// (*http.Request).outgoingLength method.
|
||||
func outgoingLength(req *http.Request) int64 {
|
||||
if req.Body == nil || req.Body == http.NoBody {
|
||||
return 0
|
||||
}
|
||||
if req.ContentLength != 0 {
|
||||
return req.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 DumpRequestOut(req *http.Request, body bool) ([]byte, error) {
|
||||
save := req.Body
|
||||
dummyBody := false
|
||||
if !body {
|
||||
contentLength := outgoingLength(req)
|
||||
if contentLength != 0 {
|
||||
req.Body = io.NopCloser(io.LimitReader(neverEnding('x'), contentLength))
|
||||
dummyBody = true
|
||||
}
|
||||
} else {
|
||||
var err error
|
||||
save, req.Body, err = drainBody(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
reqSend := req
|
||||
if req.URL.Scheme == "https" {
|
||||
reqSend = new(http.Request)
|
||||
*reqSend = *req
|
||||
reqSend.URL = new(url.URL)
|
||||
*reqSend.URL = *req.URL
|
||||
reqSend.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.
|
||||
var buf bytes.Buffer // records the output
|
||||
pr, pw := io.Pipe()
|
||||
defer pr.Close()
|
||||
defer pw.Close()
|
||||
dr := &delegateReader{c: make(chan io.Reader)}
|
||||
|
||||
t := &http.Transport{
|
||||
Dial: func(net, addr string) (net.Conn, error) {
|
||||
return &dumpConn{io.MultiWriter(&buf, pw), dr}, nil
|
||||
},
|
||||
}
|
||||
defer t.CloseIdleConnections()
|
||||
|
||||
// We need this channel to ensure that the reader
|
||||
// goroutine exits if t.RoundTrip returns an error.
|
||||
// See golang.org/issue/32571.
|
||||
quitReadCh := make(chan struct{})
|
||||
// Wait for the request before replying with a dummy response:
|
||||
go func() {
|
||||
req, err := http.ReadRequest(bufio.NewReader(pr))
|
||||
if err == nil {
|
||||
// Ensure all the body is read; otherwise
|
||||
// we'll get a partial dump.
|
||||
io.Copy(io.Discard, req.Body)
|
||||
req.Body.Close()
|
||||
}
|
||||
select {
|
||||
case dr.c <- strings.NewReader("HTTP/1.1 204 No Content\r\nConnection: close\r\n\r\n"):
|
||||
case <-quitReadCh:
|
||||
// Ensure delegateReader.Read doesn't block forever if we get an error.
|
||||
close(dr.c)
|
||||
}
|
||||
}()
|
||||
|
||||
_, err := t.RoundTrip(reqSend)
|
||||
|
||||
req.Body = save
|
||||
if err != nil {
|
||||
pw.Close()
|
||||
dr.err = err
|
||||
close(quitReadCh)
|
||||
return nil, err
|
||||
}
|
||||
dump := buf.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 dummyBody {
|
||||
if i := bytes.Index(dump, []byte("\r\n\r\n")); i >= 0 {
|
||||
dump = dump[:i+4]
|
||||
}
|
||||
}
|
||||
return dump, nil
|
||||
}
|
||||
|
||||
// delegateReader is a reader that delegates to another reader,
|
||||
// once it arrives on a channel.
|
||||
type delegateReader struct {
|
||||
c chan io.Reader
|
||||
err error // only used if r is nil and c is closed.
|
||||
r io.Reader // nil until received from c
|
||||
}
|
||||
|
||||
func (r *delegateReader) Read(p []byte) (int, error) {
|
||||
if r.r == nil {
|
||||
var ok bool
|
||||
if r.r, ok = <-r.c; !ok {
|
||||
return 0, r.err
|
||||
}
|
||||
}
|
||||
return r.r.Read(p)
|
||||
}
|
||||
|
||||
// Return value if nonempty, def otherwise.
|
||||
func valueOrDefault(value, def string) string {
|
||||
if value != "" {
|
||||
return value
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
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 DumpRequest(req *http.Request, body bool) ([]byte, error) {
|
||||
var err error
|
||||
save := req.Body
|
||||
if !body || req.Body == nil {
|
||||
req.Body = nil
|
||||
} else {
|
||||
save, req.Body, err = drainBody(req.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var b bytes.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.
|
||||
reqURI := req.RequestURI
|
||||
if reqURI == "" {
|
||||
reqURI = req.URL.RequestURI()
|
||||
}
|
||||
|
||||
fmt.Fprintf(&b, "%s %s HTTP/%d.%d\r\n", valueOrDefault(req.Method, "GET"),
|
||||
reqURI, req.ProtoMajor, req.ProtoMinor)
|
||||
|
||||
absRequestURI := strings.HasPrefix(req.RequestURI, "http://") || strings.HasPrefix(req.RequestURI, "https://")
|
||||
if !absRequestURI {
|
||||
host := req.Host
|
||||
if host == "" && req.URL != nil {
|
||||
host = req.URL.Host
|
||||
}
|
||||
if host != "" {
|
||||
fmt.Fprintf(&b, "Host: %s\r\n", host)
|
||||
}
|
||||
}
|
||||
|
||||
chunked := len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked"
|
||||
if len(req.TransferEncoding) > 0 {
|
||||
fmt.Fprintf(&b, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ","))
|
||||
}
|
||||
|
||||
err = req.Header.WriteSubset(&b, reqWriteExcludeHeaderDump)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
io.WriteString(&b, "\r\n")
|
||||
|
||||
if req.Body != nil {
|
||||
var dest io.Writer = &b
|
||||
if chunked {
|
||||
dest = NewChunkedWriter(dest)
|
||||
}
|
||||
_, err = io.Copy(dest, req.Body)
|
||||
if chunked {
|
||||
dest.(io.Closer).Close()
|
||||
io.WriteString(&b, "\r\n")
|
||||
}
|
||||
}
|
||||
|
||||
req.Body = save
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.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) Read([]byte) (int, error) { return 0, errNoBody }
|
||||
func (failureToReadBody) Close() error { return nil }
|
||||
|
||||
// emptyBody is an instance of empty reader.
|
||||
var emptyBody = io.NopCloser(strings.NewReader(""))
|
||||
|
||||
// DumpResponse is like DumpRequest but dumps a response.
|
||||
func DumpResponse(resp *http.Response, body bool) ([]byte, error) {
|
||||
var b bytes.Buffer
|
||||
var err error
|
||||
save := resp.Body
|
||||
savecl := resp.ContentLength
|
||||
|
||||
if !body {
|
||||
// For content length of zero. Make sure the body is an empty
|
||||
// reader, instead of returning error through failureToReadBody{}.
|
||||
if resp.ContentLength == 0 {
|
||||
resp.Body = emptyBody
|
||||
} else {
|
||||
resp.Body = failureToReadBody{}
|
||||
}
|
||||
} else if resp.Body == nil {
|
||||
resp.Body = emptyBody
|
||||
} else {
|
||||
save, resp.Body, err = drainBody(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
err = resp.Write(&b)
|
||||
if err == errNoBody {
|
||||
err = nil
|
||||
}
|
||||
resp.Body = save
|
||||
resp.ContentLength = savecl
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
Reference in New Issue
Block a user