mirror of
https://github.com/tinygo-org/net.git
synced 2026-07-26 08:18:39 +00:00
@@ -34,6 +34,11 @@ src/net
|
||||
│ │ └── server.go *
|
||||
│ ├── httptest
|
||||
│ │ └── trace.go *
|
||||
│ ├── httputil
|
||||
│ │ └── dump.go
|
||||
│ │ └── httputil.go
|
||||
│ │ └── persist.go
|
||||
│ │ └── reverseproxy.go
|
||||
│ ├── client.go *
|
||||
│ ├── clone.go
|
||||
│ ├── cookie.go
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// TINYGO: The following is copied from Go 1.21.5 official implementation.
|
||||
|
||||
// Copyright 2014 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 provides HTTP utility functions, complementing the
|
||||
// more common ones in the net/http package.
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http/internal"
|
||||
)
|
||||
|
||||
// NewChunkedReader returns a new chunkedReader that translates the data read from r
|
||||
// out of HTTP "chunked" format before returning it.
|
||||
// The chunkedReader returns io.EOF when the final 0-length chunk is read.
|
||||
//
|
||||
// NewChunkedReader is not needed by normal applications. The http package
|
||||
// automatically decodes chunking when reading response bodies.
|
||||
func NewChunkedReader(r io.Reader) io.Reader {
|
||||
return internal.NewChunkedReader(r)
|
||||
}
|
||||
|
||||
// NewChunkedWriter returns a new chunkedWriter that translates writes into HTTP
|
||||
// "chunked" format before writing them to w. Closing the returned chunkedWriter
|
||||
// sends the final 0-length chunk that marks the end of the stream but does
|
||||
// not send the final CRLF that appears after trailers; trailers and the last
|
||||
// CRLF must be written separately.
|
||||
//
|
||||
// NewChunkedWriter is not needed by normal applications. The http
|
||||
// package adds chunking automatically if handlers don't set a
|
||||
// Content-Length header. Using NewChunkedWriter inside a handler
|
||||
// would result in double chunking or chunking with a Content-Length
|
||||
// length, both of which are wrong.
|
||||
func NewChunkedWriter(w io.Writer) io.WriteCloser {
|
||||
return internal.NewChunkedWriter(w)
|
||||
}
|
||||
|
||||
// ErrLineTooLong is returned when reading malformed chunked data
|
||||
// with lines that are too long.
|
||||
var ErrLineTooLong = internal.ErrLineTooLong
|
||||
@@ -0,0 +1,433 @@
|
||||
// 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"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/textproto"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
// Deprecated: No longer used.
|
||||
ErrPersistEOF = &http.ProtocolError{ErrorString: "persistent connection closed"}
|
||||
|
||||
// Deprecated: No longer used.
|
||||
ErrClosed = &http.ProtocolError{ErrorString: "connection closed by user"}
|
||||
|
||||
// Deprecated: No longer used.
|
||||
ErrPipeline = &http.ProtocolError{ErrorString: "pipeline error"}
|
||||
)
|
||||
|
||||
// This is an API usage error - the local side is closed.
|
||||
// ErrPersistEOF (above) reports that the remote side is closed.
|
||||
var errClosed = errors.New("i/o operation on closed connection")
|
||||
|
||||
// ServerConn is an artifact of Go's early HTTP implementation.
|
||||
// It is low-level, old, and unused by Go's current HTTP stack.
|
||||
// We should have deleted it before Go 1.
|
||||
//
|
||||
// Deprecated: Use the Server in package net/http instead.
|
||||
type ServerConn struct {
|
||||
mu sync.Mutex // read-write protects the following fields
|
||||
c net.Conn
|
||||
r *bufio.Reader
|
||||
re, we error // read/write errors
|
||||
lastbody io.ReadCloser
|
||||
nread, nwritten int
|
||||
pipereq map[*http.Request]uint
|
||||
|
||||
pipe textproto.Pipeline
|
||||
}
|
||||
|
||||
// NewServerConn is an artifact of Go's early HTTP implementation.
|
||||
// It is low-level, old, and unused by Go's current HTTP stack.
|
||||
// We should have deleted it before Go 1.
|
||||
//
|
||||
// Deprecated: Use the Server in package net/http instead.
|
||||
func NewServerConn(c net.Conn, r *bufio.Reader) *ServerConn {
|
||||
if r == nil {
|
||||
r = bufio.NewReader(c)
|
||||
}
|
||||
return &ServerConn{c: c, r: r, pipereq: make(map[*http.Request]uint)}
|
||||
}
|
||||
|
||||
// Hijack detaches the ServerConn and returns the underlying connection as well
|
||||
// as the read-side bufio which may have some left over data. Hijack may be
|
||||
// called before Read has signaled the end of the keep-alive logic. The user
|
||||
// should not call Hijack while Read or Write is in progress.
|
||||
func (sc *ServerConn) Hijack() (net.Conn, *bufio.Reader) {
|
||||
sc.mu.Lock()
|
||||
defer sc.mu.Unlock()
|
||||
c := sc.c
|
||||
r := sc.r
|
||||
sc.c = nil
|
||||
sc.r = nil
|
||||
return c, r
|
||||
}
|
||||
|
||||
// Close calls Hijack and then also closes the underlying connection.
|
||||
func (sc *ServerConn) Close() error {
|
||||
c, _ := sc.Hijack()
|
||||
if c != nil {
|
||||
return c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read returns the next request on the wire. An ErrPersistEOF is returned if
|
||||
// it is gracefully determined that there are no more requests (e.g. after the
|
||||
// first request on an HTTP/1.0 connection, or after a Connection:close on a
|
||||
// HTTP/1.1 connection).
|
||||
func (sc *ServerConn) Read() (*http.Request, error) {
|
||||
var req *http.Request
|
||||
var err error
|
||||
|
||||
// Ensure ordered execution of Reads and Writes
|
||||
id := sc.pipe.Next()
|
||||
sc.pipe.StartRequest(id)
|
||||
defer func() {
|
||||
sc.pipe.EndRequest(id)
|
||||
if req == nil {
|
||||
sc.pipe.StartResponse(id)
|
||||
sc.pipe.EndResponse(id)
|
||||
} else {
|
||||
// Remember the pipeline id of this request
|
||||
sc.mu.Lock()
|
||||
sc.pipereq[req] = id
|
||||
sc.mu.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
sc.mu.Lock()
|
||||
if sc.we != nil { // no point receiving if write-side broken or closed
|
||||
defer sc.mu.Unlock()
|
||||
return nil, sc.we
|
||||
}
|
||||
if sc.re != nil {
|
||||
defer sc.mu.Unlock()
|
||||
return nil, sc.re
|
||||
}
|
||||
if sc.r == nil { // connection closed by user in the meantime
|
||||
defer sc.mu.Unlock()
|
||||
return nil, errClosed
|
||||
}
|
||||
r := sc.r
|
||||
lastbody := sc.lastbody
|
||||
sc.lastbody = nil
|
||||
sc.mu.Unlock()
|
||||
|
||||
// Make sure body is fully consumed, even if user does not call body.Close
|
||||
if lastbody != nil {
|
||||
// body.Close is assumed to be idempotent and multiple calls to
|
||||
// it should return the error that its first invocation
|
||||
// returned.
|
||||
err = lastbody.Close()
|
||||
if err != nil {
|
||||
sc.mu.Lock()
|
||||
defer sc.mu.Unlock()
|
||||
sc.re = err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
req, err = http.ReadRequest(r)
|
||||
sc.mu.Lock()
|
||||
defer sc.mu.Unlock()
|
||||
if err != nil {
|
||||
if err == io.ErrUnexpectedEOF {
|
||||
// A close from the opposing client is treated as a
|
||||
// graceful close, even if there was some unparse-able
|
||||
// data before the close.
|
||||
sc.re = ErrPersistEOF
|
||||
return nil, sc.re
|
||||
} else {
|
||||
sc.re = err
|
||||
return req, err
|
||||
}
|
||||
}
|
||||
sc.lastbody = req.Body
|
||||
sc.nread++
|
||||
if req.Close {
|
||||
sc.re = ErrPersistEOF
|
||||
return req, sc.re
|
||||
}
|
||||
return req, err
|
||||
}
|
||||
|
||||
// Pending returns the number of unanswered requests
|
||||
// that have been received on the connection.
|
||||
func (sc *ServerConn) Pending() int {
|
||||
sc.mu.Lock()
|
||||
defer sc.mu.Unlock()
|
||||
return sc.nread - sc.nwritten
|
||||
}
|
||||
|
||||
// Write writes resp in response to req. To close the connection gracefully, set the
|
||||
// Response.Close field to true. Write should be considered operational until
|
||||
// it returns an error, regardless of any errors returned on the Read side.
|
||||
func (sc *ServerConn) Write(req *http.Request, resp *http.Response) error {
|
||||
|
||||
// Retrieve the pipeline ID of this request/response pair
|
||||
sc.mu.Lock()
|
||||
id, ok := sc.pipereq[req]
|
||||
delete(sc.pipereq, req)
|
||||
if !ok {
|
||||
sc.mu.Unlock()
|
||||
return ErrPipeline
|
||||
}
|
||||
sc.mu.Unlock()
|
||||
|
||||
// Ensure pipeline order
|
||||
sc.pipe.StartResponse(id)
|
||||
defer sc.pipe.EndResponse(id)
|
||||
|
||||
sc.mu.Lock()
|
||||
if sc.we != nil {
|
||||
defer sc.mu.Unlock()
|
||||
return sc.we
|
||||
}
|
||||
if sc.c == nil { // connection closed by user in the meantime
|
||||
defer sc.mu.Unlock()
|
||||
return ErrClosed
|
||||
}
|
||||
c := sc.c
|
||||
if sc.nread <= sc.nwritten {
|
||||
defer sc.mu.Unlock()
|
||||
return errors.New("persist server pipe count")
|
||||
}
|
||||
if resp.Close {
|
||||
// After signaling a keep-alive close, any pipelined unread
|
||||
// requests will be lost. It is up to the user to drain them
|
||||
// before signaling.
|
||||
sc.re = ErrPersistEOF
|
||||
}
|
||||
sc.mu.Unlock()
|
||||
|
||||
err := resp.Write(c)
|
||||
sc.mu.Lock()
|
||||
defer sc.mu.Unlock()
|
||||
if err != nil {
|
||||
sc.we = err
|
||||
return err
|
||||
}
|
||||
sc.nwritten++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClientConn is an artifact of Go's early HTTP implementation.
|
||||
// It is low-level, old, and unused by Go's current HTTP stack.
|
||||
// We should have deleted it before Go 1.
|
||||
//
|
||||
// Deprecated: Use Client or Transport in package net/http instead.
|
||||
type ClientConn struct {
|
||||
mu sync.Mutex // read-write protects the following fields
|
||||
c net.Conn
|
||||
r *bufio.Reader
|
||||
re, we error // read/write errors
|
||||
lastbody io.ReadCloser
|
||||
nread, nwritten int
|
||||
pipereq map[*http.Request]uint
|
||||
|
||||
pipe textproto.Pipeline
|
||||
writeReq func(*http.Request, io.Writer) error
|
||||
}
|
||||
|
||||
// NewClientConn is an artifact of Go's early HTTP implementation.
|
||||
// It is low-level, old, and unused by Go's current HTTP stack.
|
||||
// We should have deleted it before Go 1.
|
||||
//
|
||||
// Deprecated: Use the Client or Transport in package net/http instead.
|
||||
func NewClientConn(c net.Conn, r *bufio.Reader) *ClientConn {
|
||||
if r == nil {
|
||||
r = bufio.NewReader(c)
|
||||
}
|
||||
return &ClientConn{
|
||||
c: c,
|
||||
r: r,
|
||||
pipereq: make(map[*http.Request]uint),
|
||||
writeReq: (*http.Request).Write,
|
||||
}
|
||||
}
|
||||
|
||||
// NewProxyClientConn is an artifact of Go's early HTTP implementation.
|
||||
// It is low-level, old, and unused by Go's current HTTP stack.
|
||||
// We should have deleted it before Go 1.
|
||||
//
|
||||
// Deprecated: Use the Client or Transport in package net/http instead.
|
||||
func NewProxyClientConn(c net.Conn, r *bufio.Reader) *ClientConn {
|
||||
cc := NewClientConn(c, r)
|
||||
cc.writeReq = (*http.Request).WriteProxy
|
||||
return cc
|
||||
}
|
||||
|
||||
// Hijack detaches the ClientConn and returns the underlying connection as well
|
||||
// as the read-side bufio which may have some left over data. Hijack may be
|
||||
// called before the user or Read have signaled the end of the keep-alive
|
||||
// logic. The user should not call Hijack while Read or Write is in progress.
|
||||
func (cc *ClientConn) Hijack() (c net.Conn, r *bufio.Reader) {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
c = cc.c
|
||||
r = cc.r
|
||||
cc.c = nil
|
||||
cc.r = nil
|
||||
return
|
||||
}
|
||||
|
||||
// Close calls Hijack and then also closes the underlying connection.
|
||||
func (cc *ClientConn) Close() error {
|
||||
c, _ := cc.Hijack()
|
||||
if c != nil {
|
||||
return c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write writes a request. An ErrPersistEOF error is returned if the connection
|
||||
// has been closed in an HTTP keep-alive sense. If req.Close equals true, the
|
||||
// keep-alive connection is logically closed after this request and the opposing
|
||||
// server is informed. An ErrUnexpectedEOF indicates the remote closed the
|
||||
// underlying TCP connection, which is usually considered as graceful close.
|
||||
func (cc *ClientConn) Write(req *http.Request) error {
|
||||
var err error
|
||||
|
||||
// Ensure ordered execution of Writes
|
||||
id := cc.pipe.Next()
|
||||
cc.pipe.StartRequest(id)
|
||||
defer func() {
|
||||
cc.pipe.EndRequest(id)
|
||||
if err != nil {
|
||||
cc.pipe.StartResponse(id)
|
||||
cc.pipe.EndResponse(id)
|
||||
} else {
|
||||
// Remember the pipeline id of this request
|
||||
cc.mu.Lock()
|
||||
cc.pipereq[req] = id
|
||||
cc.mu.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
cc.mu.Lock()
|
||||
if cc.re != nil { // no point sending if read-side closed or broken
|
||||
defer cc.mu.Unlock()
|
||||
return cc.re
|
||||
}
|
||||
if cc.we != nil {
|
||||
defer cc.mu.Unlock()
|
||||
return cc.we
|
||||
}
|
||||
if cc.c == nil { // connection closed by user in the meantime
|
||||
defer cc.mu.Unlock()
|
||||
return errClosed
|
||||
}
|
||||
c := cc.c
|
||||
if req.Close {
|
||||
// We write the EOF to the write-side error, because there
|
||||
// still might be some pipelined reads
|
||||
cc.we = ErrPersistEOF
|
||||
}
|
||||
cc.mu.Unlock()
|
||||
|
||||
err = cc.writeReq(req, c)
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
if err != nil {
|
||||
cc.we = err
|
||||
return err
|
||||
}
|
||||
cc.nwritten++
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Pending returns the number of unanswered requests
|
||||
// that have been sent on the connection.
|
||||
func (cc *ClientConn) Pending() int {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
return cc.nwritten - cc.nread
|
||||
}
|
||||
|
||||
// Read reads the next response from the wire. A valid response might be
|
||||
// returned together with an ErrPersistEOF, which means that the remote
|
||||
// requested that this be the last request serviced. Read can be called
|
||||
// concurrently with Write, but not with another Read.
|
||||
func (cc *ClientConn) Read(req *http.Request) (resp *http.Response, err error) {
|
||||
// Retrieve the pipeline ID of this request/response pair
|
||||
cc.mu.Lock()
|
||||
id, ok := cc.pipereq[req]
|
||||
delete(cc.pipereq, req)
|
||||
if !ok {
|
||||
cc.mu.Unlock()
|
||||
return nil, ErrPipeline
|
||||
}
|
||||
cc.mu.Unlock()
|
||||
|
||||
// Ensure pipeline order
|
||||
cc.pipe.StartResponse(id)
|
||||
defer cc.pipe.EndResponse(id)
|
||||
|
||||
cc.mu.Lock()
|
||||
if cc.re != nil {
|
||||
defer cc.mu.Unlock()
|
||||
return nil, cc.re
|
||||
}
|
||||
if cc.r == nil { // connection closed by user in the meantime
|
||||
defer cc.mu.Unlock()
|
||||
return nil, errClosed
|
||||
}
|
||||
r := cc.r
|
||||
lastbody := cc.lastbody
|
||||
cc.lastbody = nil
|
||||
cc.mu.Unlock()
|
||||
|
||||
// Make sure body is fully consumed, even if user does not call body.Close
|
||||
if lastbody != nil {
|
||||
// body.Close is assumed to be idempotent and multiple calls to
|
||||
// it should return the error that its first invocation
|
||||
// returned.
|
||||
err = lastbody.Close()
|
||||
if err != nil {
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
cc.re = err
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
resp, err = http.ReadResponse(r, req)
|
||||
cc.mu.Lock()
|
||||
defer cc.mu.Unlock()
|
||||
if err != nil {
|
||||
cc.re = err
|
||||
return resp, err
|
||||
}
|
||||
cc.lastbody = resp.Body
|
||||
|
||||
cc.nread++
|
||||
|
||||
if resp.Close {
|
||||
cc.re = ErrPersistEOF // don't send any more requests
|
||||
return resp, cc.re
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// Do is convenience method that writes a request and reads a response.
|
||||
func (cc *ClientConn) Do(req *http.Request) (*http.Response, error) {
|
||||
err := cc.Write(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return cc.Read(req)
|
||||
}
|
||||
@@ -0,0 +1,836 @@
|
||||
// TINYGO: The following is copied from Go 1.21.5 official implementation.
|
||||
|
||||
// Copyright 2011 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.
|
||||
|
||||
// HTTP reverse proxy handler
|
||||
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"mime"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptrace"
|
||||
"net/http/internal/ascii"
|
||||
"net/textproto"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/http/httpguts"
|
||||
)
|
||||
|
||||
// A ProxyRequest contains a request to be rewritten by a ReverseProxy.
|
||||
type ProxyRequest struct {
|
||||
// In is the request received by the proxy.
|
||||
// The Rewrite function must not modify In.
|
||||
In *http.Request
|
||||
|
||||
// Out is the request which will be sent by the proxy.
|
||||
// The Rewrite function may modify or replace this request.
|
||||
// Hop-by-hop headers are removed from this request
|
||||
// before Rewrite is called.
|
||||
Out *http.Request
|
||||
}
|
||||
|
||||
// SetURL routes the outbound request to the scheme, host, and base path
|
||||
// provided in target. If the target's path is "/base" and the incoming
|
||||
// request was for "/dir", the target request will be for "/base/dir".
|
||||
//
|
||||
// SetURL rewrites the outbound Host header to match the target's host.
|
||||
// To preserve the inbound request's Host header (the default behavior
|
||||
// of NewSingleHostReverseProxy):
|
||||
//
|
||||
// rewriteFunc := func(r *httputil.ProxyRequest) {
|
||||
// r.SetURL(url)
|
||||
// r.Out.Host = r.In.Host
|
||||
// }
|
||||
func (r *ProxyRequest) SetURL(target *url.URL) {
|
||||
rewriteRequestURL(r.Out, target)
|
||||
r.Out.Host = ""
|
||||
}
|
||||
|
||||
// SetXForwarded sets the X-Forwarded-For, X-Forwarded-Host, and
|
||||
// X-Forwarded-Proto headers of the outbound request.
|
||||
//
|
||||
// - The X-Forwarded-For header is set to the client IP address.
|
||||
// - The X-Forwarded-Host header is set to the host name requested
|
||||
// by the client.
|
||||
// - The X-Forwarded-Proto header is set to "http" or "https", depending
|
||||
// on whether the inbound request was made on a TLS-enabled connection.
|
||||
//
|
||||
// If the outbound request contains an existing X-Forwarded-For header,
|
||||
// SetXForwarded appends the client IP address to it. To append to the
|
||||
// inbound request's X-Forwarded-For header (the default behavior of
|
||||
// ReverseProxy when using a Director function), copy the header
|
||||
// from the inbound request before calling SetXForwarded:
|
||||
//
|
||||
// rewriteFunc := func(r *httputil.ProxyRequest) {
|
||||
// r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
|
||||
// r.SetXForwarded()
|
||||
// }
|
||||
func (r *ProxyRequest) SetXForwarded() {
|
||||
clientIP, _, err := net.SplitHostPort(r.In.RemoteAddr)
|
||||
if err == nil {
|
||||
prior := r.Out.Header["X-Forwarded-For"]
|
||||
if len(prior) > 0 {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
r.Out.Header.Set("X-Forwarded-For", clientIP)
|
||||
} else {
|
||||
r.Out.Header.Del("X-Forwarded-For")
|
||||
}
|
||||
r.Out.Header.Set("X-Forwarded-Host", r.In.Host)
|
||||
if r.In.TLS == nil {
|
||||
r.Out.Header.Set("X-Forwarded-Proto", "http")
|
||||
} else {
|
||||
r.Out.Header.Set("X-Forwarded-Proto", "https")
|
||||
}
|
||||
}
|
||||
|
||||
// ReverseProxy is an HTTP Handler that takes an incoming request and
|
||||
// sends it to another server, proxying the response back to the
|
||||
// client.
|
||||
//
|
||||
// 1xx responses are forwarded to the client if the underlying
|
||||
// transport supports ClientTrace.Got1xxResponse.
|
||||
type ReverseProxy struct {
|
||||
// Rewrite must be a function which modifies
|
||||
// the request into a new request to be sent
|
||||
// using Transport. Its response is then copied
|
||||
// back to the original client unmodified.
|
||||
// Rewrite must not access the provided ProxyRequest
|
||||
// or its contents after returning.
|
||||
//
|
||||
// The Forwarded, X-Forwarded, X-Forwarded-Host,
|
||||
// and X-Forwarded-Proto headers are removed from the
|
||||
// outbound request before Rewrite is called. See also
|
||||
// the ProxyRequest.SetXForwarded method.
|
||||
//
|
||||
// Unparsable query parameters are removed from the
|
||||
// outbound request before Rewrite is called.
|
||||
// The Rewrite function may copy the inbound URL's
|
||||
// RawQuery to the outbound URL to preserve the original
|
||||
// parameter string. Note that this can lead to security
|
||||
// issues if the proxy's interpretation of query parameters
|
||||
// does not match that of the downstream server.
|
||||
//
|
||||
// At most one of Rewrite or Director may be set.
|
||||
Rewrite func(*ProxyRequest)
|
||||
|
||||
// Director is a function which modifies
|
||||
// the request into a new request to be sent
|
||||
// using Transport. Its response is then copied
|
||||
// back to the original client unmodified.
|
||||
// Director must not access the provided Request
|
||||
// after returning.
|
||||
//
|
||||
// By default, the X-Forwarded-For header is set to the
|
||||
// value of the client IP address. If an X-Forwarded-For
|
||||
// header already exists, the client IP is appended to the
|
||||
// existing values. As a special case, if the header
|
||||
// exists in the Request.Header map but has a nil value
|
||||
// (such as when set by the Director func), the X-Forwarded-For
|
||||
// header is not modified.
|
||||
//
|
||||
// To prevent IP spoofing, be sure to delete any pre-existing
|
||||
// X-Forwarded-For header coming from the client or
|
||||
// an untrusted proxy.
|
||||
//
|
||||
// Hop-by-hop headers are removed from the request after
|
||||
// Director returns, which can remove headers added by
|
||||
// Director. Use a Rewrite function instead to ensure
|
||||
// modifications to the request are preserved.
|
||||
//
|
||||
// Unparsable query parameters are removed from the outbound
|
||||
// request if Request.Form is set after Director returns.
|
||||
//
|
||||
// At most one of Rewrite or Director may be set.
|
||||
Director func(*http.Request)
|
||||
|
||||
// The transport used to perform proxy requests.
|
||||
// If nil, http.DefaultTransport is used.
|
||||
Transport http.RoundTripper
|
||||
|
||||
// FlushInterval specifies the flush interval
|
||||
// to flush to the client while copying the
|
||||
// response body.
|
||||
// If zero, no periodic flushing is done.
|
||||
// A negative value means to flush immediately
|
||||
// after each write to the client.
|
||||
// The FlushInterval is ignored when ReverseProxy
|
||||
// recognizes a response as a streaming response, or
|
||||
// if its ContentLength is -1; for such responses, writes
|
||||
// are flushed to the client immediately.
|
||||
FlushInterval time.Duration
|
||||
|
||||
// ErrorLog specifies an optional logger for errors
|
||||
// that occur when attempting to proxy the request.
|
||||
// If nil, logging is done via the log package's standard logger.
|
||||
ErrorLog *log.Logger
|
||||
|
||||
// BufferPool optionally specifies a buffer pool to
|
||||
// get byte slices for use by io.CopyBuffer when
|
||||
// copying HTTP response bodies.
|
||||
BufferPool BufferPool
|
||||
|
||||
// ModifyResponse is an optional function that modifies the
|
||||
// Response from the backend. It is called if the backend
|
||||
// returns a response at all, with any HTTP status code.
|
||||
// If the backend is unreachable, the optional ErrorHandler is
|
||||
// called without any call to ModifyResponse.
|
||||
//
|
||||
// If ModifyResponse returns an error, ErrorHandler is called
|
||||
// with its error value. If ErrorHandler is nil, its default
|
||||
// implementation is used.
|
||||
ModifyResponse func(*http.Response) error
|
||||
|
||||
// ErrorHandler is an optional function that handles errors
|
||||
// reaching the backend or errors from ModifyResponse.
|
||||
//
|
||||
// If nil, the default is to log the provided error and return
|
||||
// a 502 Status Bad Gateway response.
|
||||
ErrorHandler func(http.ResponseWriter, *http.Request, error)
|
||||
}
|
||||
|
||||
// A BufferPool is an interface for getting and returning temporary
|
||||
// byte slices for use by io.CopyBuffer.
|
||||
type BufferPool interface {
|
||||
Get() []byte
|
||||
Put([]byte)
|
||||
}
|
||||
|
||||
func singleJoiningSlash(a, b string) string {
|
||||
aslash := strings.HasSuffix(a, "/")
|
||||
bslash := strings.HasPrefix(b, "/")
|
||||
switch {
|
||||
case aslash && bslash:
|
||||
return a + b[1:]
|
||||
case !aslash && !bslash:
|
||||
return a + "/" + b
|
||||
}
|
||||
return a + b
|
||||
}
|
||||
|
||||
func joinURLPath(a, b *url.URL) (path, rawpath string) {
|
||||
if a.RawPath == "" && b.RawPath == "" {
|
||||
return singleJoiningSlash(a.Path, b.Path), ""
|
||||
}
|
||||
// Same as singleJoiningSlash, but uses EscapedPath to determine
|
||||
// whether a slash should be added
|
||||
apath := a.EscapedPath()
|
||||
bpath := b.EscapedPath()
|
||||
|
||||
aslash := strings.HasSuffix(apath, "/")
|
||||
bslash := strings.HasPrefix(bpath, "/")
|
||||
|
||||
switch {
|
||||
case aslash && bslash:
|
||||
return a.Path + b.Path[1:], apath + bpath[1:]
|
||||
case !aslash && !bslash:
|
||||
return a.Path + "/" + b.Path, apath + "/" + bpath
|
||||
}
|
||||
return a.Path + b.Path, apath + bpath
|
||||
}
|
||||
|
||||
// NewSingleHostReverseProxy returns a new ReverseProxy that routes
|
||||
// URLs to the scheme, host, and base path provided in target. If the
|
||||
// target's path is "/base" and the incoming request was for "/dir",
|
||||
// the target request will be for /base/dir.
|
||||
//
|
||||
// NewSingleHostReverseProxy does not rewrite the Host header.
|
||||
//
|
||||
// To customize the ReverseProxy behavior beyond what
|
||||
// NewSingleHostReverseProxy provides, use ReverseProxy directly
|
||||
// with a Rewrite function. The ProxyRequest SetURL method
|
||||
// may be used to route the outbound request. (Note that SetURL,
|
||||
// unlike NewSingleHostReverseProxy, rewrites the Host header
|
||||
// of the outbound request by default.)
|
||||
//
|
||||
// proxy := &ReverseProxy{
|
||||
// Rewrite: func(r *ProxyRequest) {
|
||||
// r.SetURL(target)
|
||||
// r.Out.Host = r.In.Host // if desired
|
||||
// },
|
||||
// }
|
||||
func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
|
||||
director := func(req *http.Request) {
|
||||
rewriteRequestURL(req, target)
|
||||
}
|
||||
return &ReverseProxy{Director: director}
|
||||
}
|
||||
|
||||
func rewriteRequestURL(req *http.Request, target *url.URL) {
|
||||
targetQuery := target.RawQuery
|
||||
req.URL.Scheme = target.Scheme
|
||||
req.URL.Host = target.Host
|
||||
req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
|
||||
if targetQuery == "" || req.URL.RawQuery == "" {
|
||||
req.URL.RawQuery = targetQuery + req.URL.RawQuery
|
||||
} else {
|
||||
req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
|
||||
}
|
||||
}
|
||||
|
||||
func copyHeader(dst, src http.Header) {
|
||||
for k, vv := range src {
|
||||
for _, v := range vv {
|
||||
dst.Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hop-by-hop headers. These are removed when sent to the backend.
|
||||
// As of RFC 7230, hop-by-hop headers are required to appear in the
|
||||
// Connection header field. These are the headers defined by the
|
||||
// obsoleted RFC 2616 (section 13.5.1) and are used for backward
|
||||
// compatibility.
|
||||
var hopHeaders = []string{
|
||||
"Connection",
|
||||
"Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google
|
||||
"Keep-Alive",
|
||||
"Proxy-Authenticate",
|
||||
"Proxy-Authorization",
|
||||
"Te", // canonicalized version of "TE"
|
||||
"Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522
|
||||
"Transfer-Encoding",
|
||||
"Upgrade",
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) defaultErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
|
||||
p.logf("http: proxy error: %v", err)
|
||||
rw.WriteHeader(http.StatusBadGateway)
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) getErrorHandler() func(http.ResponseWriter, *http.Request, error) {
|
||||
if p.ErrorHandler != nil {
|
||||
return p.ErrorHandler
|
||||
}
|
||||
return p.defaultErrorHandler
|
||||
}
|
||||
|
||||
// modifyResponse conditionally runs the optional ModifyResponse hook
|
||||
// and reports whether the request should proceed.
|
||||
func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) bool {
|
||||
if p.ModifyResponse == nil {
|
||||
return true
|
||||
}
|
||||
if err := p.ModifyResponse(res); err != nil {
|
||||
res.Body.Close()
|
||||
p.getErrorHandler()(rw, req, err)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
transport := p.Transport
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
ctx := req.Context()
|
||||
if ctx.Done() != nil {
|
||||
// CloseNotifier predates context.Context, and has been
|
||||
// entirely superseded by it. If the request contains
|
||||
// a Context that carries a cancellation signal, don't
|
||||
// bother spinning up a goroutine to watch the CloseNotify
|
||||
// channel (if any).
|
||||
//
|
||||
// If the request Context has a nil Done channel (which
|
||||
// means it is either context.Background, or a custom
|
||||
// Context implementation with no cancellation signal),
|
||||
// then consult the CloseNotifier if available.
|
||||
} else if cn, ok := rw.(http.CloseNotifier); ok {
|
||||
var cancel context.CancelFunc
|
||||
ctx, cancel = context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
notifyChan := cn.CloseNotify()
|
||||
go func() {
|
||||
select {
|
||||
case <-notifyChan:
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
outreq := req.Clone(ctx)
|
||||
if req.ContentLength == 0 {
|
||||
outreq.Body = nil // Issue 16036: nil Body for http.Transport retries
|
||||
}
|
||||
if outreq.Body != nil {
|
||||
// Reading from the request body after returning from a handler is not
|
||||
// allowed, and the RoundTrip goroutine that reads the Body can outlive
|
||||
// this handler. This can lead to a crash if the handler panics (see
|
||||
// Issue 46866). Although calling Close doesn't guarantee there isn't
|
||||
// any Read in flight after the handle returns, in practice it's safe to
|
||||
// read after closing it.
|
||||
defer outreq.Body.Close()
|
||||
}
|
||||
if outreq.Header == nil {
|
||||
outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate
|
||||
}
|
||||
|
||||
if (p.Director != nil) == (p.Rewrite != nil) {
|
||||
p.getErrorHandler()(rw, req, errors.New("ReverseProxy must have exactly one of Director or Rewrite set"))
|
||||
return
|
||||
}
|
||||
|
||||
if p.Director != nil {
|
||||
p.Director(outreq)
|
||||
if outreq.Form != nil {
|
||||
outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery)
|
||||
}
|
||||
}
|
||||
outreq.Close = false
|
||||
|
||||
reqUpType := upgradeType(outreq.Header)
|
||||
if !ascii.IsPrint(reqUpType) {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType))
|
||||
return
|
||||
}
|
||||
removeHopByHopHeaders(outreq.Header)
|
||||
|
||||
// Issue 21096: tell backend applications that care about trailer support
|
||||
// that we support trailers. (We do, but we don't go out of our way to
|
||||
// advertise that unless the incoming client request thought it was worth
|
||||
// mentioning.) Note that we look at req.Header, not outreq.Header, since
|
||||
// the latter has passed through removeHopByHopHeaders.
|
||||
if httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") {
|
||||
outreq.Header.Set("Te", "trailers")
|
||||
}
|
||||
|
||||
// After stripping all the hop-by-hop connection headers above, add back any
|
||||
// necessary for protocol upgrades, such as for websockets.
|
||||
if reqUpType != "" {
|
||||
outreq.Header.Set("Connection", "Upgrade")
|
||||
outreq.Header.Set("Upgrade", reqUpType)
|
||||
}
|
||||
|
||||
if p.Rewrite != nil {
|
||||
// Strip client-provided forwarding headers.
|
||||
// The Rewrite func may use SetXForwarded to set new values
|
||||
// for these or copy the previous values from the inbound request.
|
||||
outreq.Header.Del("Forwarded")
|
||||
outreq.Header.Del("X-Forwarded-For")
|
||||
outreq.Header.Del("X-Forwarded-Host")
|
||||
outreq.Header.Del("X-Forwarded-Proto")
|
||||
|
||||
// Remove unparsable query parameters from the outbound request.
|
||||
outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery)
|
||||
|
||||
pr := &ProxyRequest{
|
||||
In: req,
|
||||
Out: outreq,
|
||||
}
|
||||
p.Rewrite(pr)
|
||||
outreq = pr.Out
|
||||
} else {
|
||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||
// If we aren't the first proxy retain prior
|
||||
// X-Forwarded-For information as a comma+space
|
||||
// separated list and fold multiple headers into one.
|
||||
prior, ok := outreq.Header["X-Forwarded-For"]
|
||||
omit := ok && prior == nil // Issue 38079: nil now means don't populate the header
|
||||
if len(prior) > 0 {
|
||||
clientIP = strings.Join(prior, ", ") + ", " + clientIP
|
||||
}
|
||||
if !omit {
|
||||
outreq.Header.Set("X-Forwarded-For", clientIP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := outreq.Header["User-Agent"]; !ok {
|
||||
// If the outbound request doesn't have a User-Agent header set,
|
||||
// don't send the default Go HTTP client User-Agent.
|
||||
outreq.Header.Set("User-Agent", "")
|
||||
}
|
||||
|
||||
trace := &httptrace.ClientTrace{
|
||||
Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
|
||||
h := rw.Header()
|
||||
copyHeader(h, http.Header(header))
|
||||
rw.WriteHeader(code)
|
||||
|
||||
// Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses
|
||||
for k := range h {
|
||||
delete(h, k)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
outreq = outreq.WithContext(httptrace.WithClientTrace(outreq.Context(), trace))
|
||||
|
||||
res, err := transport.RoundTrip(outreq)
|
||||
if err != nil {
|
||||
p.getErrorHandler()(rw, outreq, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
|
||||
if res.StatusCode == http.StatusSwitchingProtocols {
|
||||
if !p.modifyResponse(rw, res, outreq) {
|
||||
return
|
||||
}
|
||||
p.handleUpgradeResponse(rw, outreq, res)
|
||||
return
|
||||
}
|
||||
|
||||
removeHopByHopHeaders(res.Header)
|
||||
|
||||
if !p.modifyResponse(rw, res, outreq) {
|
||||
return
|
||||
}
|
||||
|
||||
copyHeader(rw.Header(), res.Header)
|
||||
|
||||
// The "Trailer" header isn't included in the Transport's response,
|
||||
// at least for *http.Transport. Build it up from Trailer.
|
||||
announcedTrailers := len(res.Trailer)
|
||||
if announcedTrailers > 0 {
|
||||
trailerKeys := make([]string, 0, len(res.Trailer))
|
||||
for k := range res.Trailer {
|
||||
trailerKeys = append(trailerKeys, k)
|
||||
}
|
||||
rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
|
||||
}
|
||||
|
||||
rw.WriteHeader(res.StatusCode)
|
||||
|
||||
err = p.copyResponse(rw, res.Body, p.flushInterval(res))
|
||||
if err != nil {
|
||||
defer res.Body.Close()
|
||||
// Since we're streaming the response, if we run into an error all we can do
|
||||
// is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
|
||||
// on read error while copying body.
|
||||
if !shouldPanicOnCopyError(req) {
|
||||
p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
|
||||
return
|
||||
}
|
||||
panic(http.ErrAbortHandler)
|
||||
}
|
||||
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
||||
|
||||
if len(res.Trailer) > 0 {
|
||||
// Force chunking if we saw a response trailer.
|
||||
// This prevents net/http from calculating the length for short
|
||||
// bodies and adding a Content-Length.
|
||||
http.NewResponseController(rw).Flush()
|
||||
}
|
||||
|
||||
if len(res.Trailer) == announcedTrailers {
|
||||
copyHeader(rw.Header(), res.Trailer)
|
||||
return
|
||||
}
|
||||
|
||||
for k, vv := range res.Trailer {
|
||||
k = http.TrailerPrefix + k
|
||||
for _, v := range vv {
|
||||
rw.Header().Add(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var inOurTests bool // whether we're in our own tests
|
||||
|
||||
// shouldPanicOnCopyError reports whether the reverse proxy should
|
||||
// panic with http.ErrAbortHandler. This is the right thing to do by
|
||||
// default, but Go 1.10 and earlier did not, so existing unit tests
|
||||
// weren't expecting panics. Only panic in our own tests, or when
|
||||
// running under the HTTP server.
|
||||
func shouldPanicOnCopyError(req *http.Request) bool {
|
||||
if inOurTests {
|
||||
// Our tests know to handle this panic.
|
||||
return true
|
||||
}
|
||||
if req.Context().Value(http.ServerContextKey) != nil {
|
||||
// We seem to be running under an HTTP server, so
|
||||
// it'll recover the panic.
|
||||
return true
|
||||
}
|
||||
// Otherwise act like Go 1.10 and earlier to not break
|
||||
// existing tests.
|
||||
return false
|
||||
}
|
||||
|
||||
// removeHopByHopHeaders removes hop-by-hop headers.
|
||||
func removeHopByHopHeaders(h http.Header) {
|
||||
// RFC 7230, section 6.1: Remove headers listed in the "Connection" header.
|
||||
for _, f := range h["Connection"] {
|
||||
for _, sf := range strings.Split(f, ",") {
|
||||
if sf = textproto.TrimString(sf); sf != "" {
|
||||
h.Del(sf)
|
||||
}
|
||||
}
|
||||
}
|
||||
// RFC 2616, section 13.5.1: Remove a set of known hop-by-hop headers.
|
||||
// This behavior is superseded by the RFC 7230 Connection header, but
|
||||
// preserve it for backwards compatibility.
|
||||
for _, f := range hopHeaders {
|
||||
h.Del(f)
|
||||
}
|
||||
}
|
||||
|
||||
// flushInterval returns the p.FlushInterval value, conditionally
|
||||
// overriding its value for a specific request/response.
|
||||
func (p *ReverseProxy) flushInterval(res *http.Response) time.Duration {
|
||||
resCT := res.Header.Get("Content-Type")
|
||||
|
||||
// For Server-Sent Events responses, flush immediately.
|
||||
// The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream
|
||||
if baseCT, _, _ := mime.ParseMediaType(resCT); baseCT == "text/event-stream" {
|
||||
return -1 // negative means immediately
|
||||
}
|
||||
|
||||
// We might have the case of streaming for which Content-Length might be unset.
|
||||
if res.ContentLength == -1 {
|
||||
return -1
|
||||
}
|
||||
|
||||
return p.FlushInterval
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) copyResponse(dst http.ResponseWriter, src io.Reader, flushInterval time.Duration) error {
|
||||
var w io.Writer = dst
|
||||
|
||||
if flushInterval != 0 {
|
||||
mlw := &maxLatencyWriter{
|
||||
dst: dst,
|
||||
flush: http.NewResponseController(dst).Flush,
|
||||
latency: flushInterval,
|
||||
}
|
||||
defer mlw.stop()
|
||||
|
||||
// set up initial timer so headers get flushed even if body writes are delayed
|
||||
mlw.flushPending = true
|
||||
mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
|
||||
|
||||
w = mlw
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
if p.BufferPool != nil {
|
||||
buf = p.BufferPool.Get()
|
||||
defer p.BufferPool.Put(buf)
|
||||
}
|
||||
_, err := p.copyBuffer(w, src, buf)
|
||||
return err
|
||||
}
|
||||
|
||||
// copyBuffer returns any write errors or non-EOF read errors, and the amount
|
||||
// of bytes written.
|
||||
func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
|
||||
if len(buf) == 0 {
|
||||
buf = make([]byte, 32*1024)
|
||||
}
|
||||
var written int64
|
||||
for {
|
||||
nr, rerr := src.Read(buf)
|
||||
if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
|
||||
p.logf("httputil: ReverseProxy read error during body copy: %v", rerr)
|
||||
}
|
||||
if nr > 0 {
|
||||
nw, werr := dst.Write(buf[:nr])
|
||||
if nw > 0 {
|
||||
written += int64(nw)
|
||||
}
|
||||
if werr != nil {
|
||||
return written, werr
|
||||
}
|
||||
if nr != nw {
|
||||
return written, io.ErrShortWrite
|
||||
}
|
||||
}
|
||||
if rerr != nil {
|
||||
if rerr == io.EOF {
|
||||
rerr = nil
|
||||
}
|
||||
return written, rerr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) logf(format string, args ...any) {
|
||||
if p.ErrorLog != nil {
|
||||
p.ErrorLog.Printf(format, args...)
|
||||
} else {
|
||||
log.Printf(format, args...)
|
||||
}
|
||||
}
|
||||
|
||||
type maxLatencyWriter struct {
|
||||
dst io.Writer
|
||||
flush func() error
|
||||
latency time.Duration // non-zero; negative means to flush immediately
|
||||
|
||||
mu sync.Mutex // protects t, flushPending, and dst.Flush
|
||||
t *time.Timer
|
||||
flushPending bool
|
||||
}
|
||||
|
||||
func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
n, err = m.dst.Write(p)
|
||||
if m.latency < 0 {
|
||||
m.flush()
|
||||
return
|
||||
}
|
||||
if m.flushPending {
|
||||
return
|
||||
}
|
||||
if m.t == nil {
|
||||
m.t = time.AfterFunc(m.latency, m.delayedFlush)
|
||||
} else {
|
||||
m.t.Reset(m.latency)
|
||||
}
|
||||
m.flushPending = true
|
||||
return
|
||||
}
|
||||
|
||||
func (m *maxLatencyWriter) delayedFlush() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if !m.flushPending { // if stop was called but AfterFunc already started this goroutine
|
||||
return
|
||||
}
|
||||
m.flush()
|
||||
m.flushPending = false
|
||||
}
|
||||
|
||||
func (m *maxLatencyWriter) stop() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.flushPending = false
|
||||
if m.t != nil {
|
||||
m.t.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func upgradeType(h http.Header) string {
|
||||
if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
|
||||
return ""
|
||||
}
|
||||
return h.Get("Upgrade")
|
||||
}
|
||||
|
||||
func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) {
|
||||
reqUpType := upgradeType(req.Header)
|
||||
resUpType := upgradeType(res.Header)
|
||||
if !ascii.IsPrint(resUpType) { // We know reqUpType is ASCII, it's checked by the caller.
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType))
|
||||
}
|
||||
if !ascii.EqualFold(reqUpType, resUpType) {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
|
||||
return
|
||||
}
|
||||
|
||||
backConn, ok := res.Body.(io.ReadWriteCloser)
|
||||
if !ok {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
|
||||
return
|
||||
}
|
||||
|
||||
rc := http.NewResponseController(rw)
|
||||
conn, brw, hijackErr := rc.Hijack()
|
||||
if errors.Is(hijackErr, http.ErrNotSupported) {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
|
||||
return
|
||||
}
|
||||
|
||||
backConnCloseCh := make(chan bool)
|
||||
go func() {
|
||||
// Ensure that the cancellation of a request closes the backend.
|
||||
// See issue https://golang.org/issue/35559.
|
||||
select {
|
||||
case <-req.Context().Done():
|
||||
case <-backConnCloseCh:
|
||||
}
|
||||
backConn.Close()
|
||||
}()
|
||||
defer close(backConnCloseCh)
|
||||
|
||||
if hijackErr != nil {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", hijackErr))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
copyHeader(rw.Header(), res.Header)
|
||||
|
||||
res.Header = rw.Header()
|
||||
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
|
||||
if err := res.Write(brw); err != nil {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err))
|
||||
return
|
||||
}
|
||||
if err := brw.Flush(); err != nil {
|
||||
p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err))
|
||||
return
|
||||
}
|
||||
errc := make(chan error, 1)
|
||||
spc := switchProtocolCopier{user: conn, backend: backConn}
|
||||
go spc.copyToBackend(errc)
|
||||
go spc.copyFromBackend(errc)
|
||||
<-errc
|
||||
}
|
||||
|
||||
// switchProtocolCopier exists so goroutines proxying data back and
|
||||
// forth have nice names in stacks.
|
||||
type switchProtocolCopier struct {
|
||||
user, backend io.ReadWriter
|
||||
}
|
||||
|
||||
func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
|
||||
_, err := io.Copy(c.user, c.backend)
|
||||
errc <- err
|
||||
}
|
||||
|
||||
func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
|
||||
_, err := io.Copy(c.backend, c.user)
|
||||
errc <- err
|
||||
}
|
||||
|
||||
func cleanQueryParams(s string) string {
|
||||
reencode := func(s string) string {
|
||||
v, _ := url.ParseQuery(s)
|
||||
return v.Encode()
|
||||
}
|
||||
for i := 0; i < len(s); {
|
||||
switch s[i] {
|
||||
case ';':
|
||||
return reencode(s)
|
||||
case '%':
|
||||
if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
|
||||
return reencode(s)
|
||||
}
|
||||
i += 3
|
||||
default:
|
||||
i++
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func ishex(c byte) bool {
|
||||
switch {
|
||||
case '0' <= c && c <= '9':
|
||||
return true
|
||||
case 'a' <= c && c <= 'f':
|
||||
return true
|
||||
case 'A' <= c && c <= 'F':
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user