mirror of
https://github.com/caddyserver/caddy.git
synced 2024-12-27 06:03:48 +03:00
reverseproxy: Implement read & write timeouts for HTTP transport (#4905)
This commit is contained in:
parent
abad9bc256
commit
a379fa4c6c
2 changed files with 72 additions and 2 deletions
|
@ -875,6 +875,26 @@ func (h *HTTPTransport) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||||
}
|
}
|
||||||
h.WriteBufferSize = int(size)
|
h.WriteBufferSize = int(size)
|
||||||
|
|
||||||
|
case "read_timeout":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
timeout, err := caddy.ParseDuration(d.Val())
|
||||||
|
if err != nil {
|
||||||
|
return d.Errf("invalid read timeout duration '%s': %v", d.Val(), err)
|
||||||
|
}
|
||||||
|
h.ReadTimeout = caddy.Duration(timeout)
|
||||||
|
|
||||||
|
case "write_timeout":
|
||||||
|
if !d.NextArg() {
|
||||||
|
return d.ArgErr()
|
||||||
|
}
|
||||||
|
timeout, err := caddy.ParseDuration(d.Val())
|
||||||
|
if err != nil {
|
||||||
|
return d.Errf("invalid write timeout duration '%s': %v", d.Val(), err)
|
||||||
|
}
|
||||||
|
h.WriteTimeout = caddy.Duration(timeout)
|
||||||
|
|
||||||
case "max_response_header":
|
case "max_response_header":
|
||||||
if !d.NextArg() {
|
if !d.NextArg() {
|
||||||
return d.ArgErr()
|
return d.ArgErr()
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
|
|
||||||
"github.com/caddyserver/caddy/v2"
|
"github.com/caddyserver/caddy/v2"
|
||||||
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
"github.com/caddyserver/caddy/v2/modules/caddytls"
|
||||||
|
"go.uber.org/zap"
|
||||||
"golang.org/x/net/http2"
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -88,6 +89,12 @@ type HTTPTransport struct {
|
||||||
// The size of the read buffer in bytes. Default: `4KiB`.
|
// The size of the read buffer in bytes. Default: `4KiB`.
|
||||||
ReadBufferSize int `json:"read_buffer_size,omitempty"`
|
ReadBufferSize int `json:"read_buffer_size,omitempty"`
|
||||||
|
|
||||||
|
// The maximum time to wait for next read from backend. Default: no timeout.
|
||||||
|
ReadTimeout caddy.Duration `json:"read_timeout,omitempty"`
|
||||||
|
|
||||||
|
// The maximum time to wait for next write to backend. Default: no timeout.
|
||||||
|
WriteTimeout caddy.Duration `json:"write_timeout,omitempty"`
|
||||||
|
|
||||||
// The versions of HTTP to support. As a special case, "h2c"
|
// The versions of HTTP to support. As a special case, "h2c"
|
||||||
// can be specified to use H2C (HTTP/2 over Cleartext) to the
|
// can be specified to use H2C (HTTP/2 over Cleartext) to the
|
||||||
// upstream (this feature is experimental and subject to
|
// upstream (this feature is experimental and subject to
|
||||||
|
@ -147,7 +154,7 @@ func (h *HTTPTransport) Provision(ctx caddy.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTransport builds a standard-lib-compatible http.Transport value from h.
|
// NewTransport builds a standard-lib-compatible http.Transport value from h.
|
||||||
func (h *HTTPTransport) NewTransport(ctx caddy.Context) (*http.Transport, error) {
|
func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, error) {
|
||||||
// Set keep-alive defaults if it wasn't otherwise configured
|
// Set keep-alive defaults if it wasn't otherwise configured
|
||||||
if h.KeepAlive == nil {
|
if h.KeepAlive == nil {
|
||||||
h.KeepAlive = &KeepAlive{
|
h.KeepAlive = &KeepAlive{
|
||||||
|
@ -194,6 +201,7 @@ func (h *HTTPTransport) NewTransport(ctx caddy.Context) (*http.Transport, error)
|
||||||
network = dialInfo.Network
|
network = dialInfo.Network
|
||||||
address = dialInfo.Address
|
address = dialInfo.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := dialer.DialContext(ctx, network, address)
|
conn, err := dialer.DialContext(ctx, network, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// identify this error as one that occurred during
|
// identify this error as one that occurred during
|
||||||
|
@ -201,6 +209,18 @@ func (h *HTTPTransport) NewTransport(ctx caddy.Context) (*http.Transport, error)
|
||||||
// decide whether to retry a request
|
// decide whether to retry a request
|
||||||
return nil, DialError{err}
|
return nil, DialError{err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if read/write timeouts are configured and this is a TCP connection, enforce the timeouts
|
||||||
|
// by wrapping the connection with our own type
|
||||||
|
if tcpConn, ok := conn.(*net.TCPConn); ok && (h.ReadTimeout > 0 || h.WriteTimeout > 0) {
|
||||||
|
conn = &tcpRWTimeoutConn{
|
||||||
|
TCPConn: tcpConn,
|
||||||
|
readTimeout: time.Duration(h.ReadTimeout),
|
||||||
|
writeTimeout: time.Duration(h.WriteTimeout),
|
||||||
|
logger: caddyCtx.Logger(h),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return conn, nil
|
return conn, nil
|
||||||
},
|
},
|
||||||
MaxConnsPerHost: h.MaxConnsPerHost,
|
MaxConnsPerHost: h.MaxConnsPerHost,
|
||||||
|
@ -214,7 +234,7 @@ func (h *HTTPTransport) NewTransport(ctx caddy.Context) (*http.Transport, error)
|
||||||
if h.TLS != nil {
|
if h.TLS != nil {
|
||||||
rt.TLSHandshakeTimeout = time.Duration(h.TLS.HandshakeTimeout)
|
rt.TLSHandshakeTimeout = time.Duration(h.TLS.HandshakeTimeout)
|
||||||
var err error
|
var err error
|
||||||
rt.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(ctx)
|
rt.TLSClientConfig, err = h.TLS.MakeTLSClientConfig(caddyCtx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("making TLS client config: %v", err)
|
return nil, fmt.Errorf("making TLS client config: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -510,6 +530,36 @@ type KeepAlive struct {
|
||||||
IdleConnTimeout caddy.Duration `json:"idle_timeout,omitempty"`
|
IdleConnTimeout caddy.Duration `json:"idle_timeout,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tcpRWTimeoutConn enforces read/write timeouts for a TCP connection.
|
||||||
|
// If it fails to set deadlines, the error is logged but does not abort
|
||||||
|
// the read/write attempt (ignoring the error is consistent with what
|
||||||
|
// the standard library does: https://github.com/golang/go/blob/c5da4fb7ac5cb7434b41fc9a1df3bee66c7f1a4d/src/net/http/server.go#L981-L986)
|
||||||
|
type tcpRWTimeoutConn struct {
|
||||||
|
*net.TCPConn
|
||||||
|
readTimeout, writeTimeout time.Duration
|
||||||
|
logger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tcpRWTimeoutConn) Read(b []byte) (int, error) {
|
||||||
|
if c.readTimeout > 0 {
|
||||||
|
err := c.TCPConn.SetReadDeadline(time.Now().Add(c.readTimeout))
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("failed to set read deadline", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.TCPConn.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *tcpRWTimeoutConn) Write(b []byte) (int, error) {
|
||||||
|
if c.writeTimeout > 0 {
|
||||||
|
err := c.TCPConn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
|
||||||
|
if err != nil {
|
||||||
|
c.logger.Error("failed to set write deadline", zap.Error(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return c.TCPConn.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
// decodeBase64DERCert base64-decodes, then DER-decodes, certStr.
|
// decodeBase64DERCert base64-decodes, then DER-decodes, certStr.
|
||||||
func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
|
func decodeBase64DERCert(certStr string) (*x509.Certificate, error) {
|
||||||
// decode base64
|
// decode base64
|
||||||
|
|
Loading…
Reference in a new issue