Add configurable timeouts and curate default timeout settings
This commit is contained in:
parent
d84ccbc52a
commit
14a0d66410
10 changed files with 460 additions and 76 deletions
|
@ -1,12 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
fmtlog "log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
@ -29,7 +26,6 @@ import (
|
|||
"github.com/coreos/go-systemd/daemon"
|
||||
"github.com/docker/libkv/store"
|
||||
"github.com/satori/go.uuid"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -178,28 +174,6 @@ func run(traefikConfiguration *server.TraefikConfiguration) {
|
|||
// load global configuration
|
||||
globalConfiguration := traefikConfiguration.GlobalConfiguration
|
||||
|
||||
http.DefaultTransport.(*http.Transport).MaxIdleConnsPerHost = globalConfiguration.MaxIdleConnsPerHost
|
||||
if globalConfiguration.InsecureSkipVerify {
|
||||
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
|
||||
if len(globalConfiguration.RootCAs) > 0 {
|
||||
roots := x509.NewCertPool()
|
||||
for _, cert := range globalConfiguration.RootCAs {
|
||||
certContent, err := cert.Read()
|
||||
if err != nil {
|
||||
log.Error("Error while read RootCAs", err)
|
||||
continue
|
||||
}
|
||||
roots.AppendCertsFromPEM(certContent)
|
||||
}
|
||||
|
||||
tr := http.DefaultTransport.(*http.Transport)
|
||||
tr.TLSClientConfig = &tls.Config{RootCAs: roots}
|
||||
|
||||
http2.ConfigureTransport(tr)
|
||||
}
|
||||
|
||||
if globalConfiguration.File != nil && len(globalConfiguration.File.Filename) == 0 {
|
||||
// no filename, setting to global config file
|
||||
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||
|
|
77
docs/toml.md
77
docs/toml.md
|
@ -68,7 +68,12 @@
|
|||
#
|
||||
# ProvidersThrottleDuration = "2s"
|
||||
|
||||
# IdleTimeout: maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
|
||||
# IdleTimeout
|
||||
#
|
||||
# Deprecated - see [respondingTimeouts] section. In the case both settings are configured, the deprecated option will
|
||||
# be overwritten.
|
||||
#
|
||||
# IdleTimeout is the maximum amount of time an idle (keep-alive) connection will remain idle before closing itself.
|
||||
# This is set to enforce closing of stale client connections.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming seconds.
|
||||
|
@ -330,6 +335,76 @@ To write JSON format logs, specify `json` as the format:
|
|||
# interval = "30s"
|
||||
```
|
||||
|
||||
## Responding timeouts
|
||||
```
|
||||
# respondingTimeouts are timeouts for incoming requests to the Traefik instance.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
[respondingTimeouts]
|
||||
|
||||
# readTimeout is the maximum duration for reading the entire request, including the body.
|
||||
# If zero, no timeout exists.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
#
|
||||
# readTimeout = "5s"
|
||||
|
||||
# writeTimeout is the maximum duration before timing out writes of the response. It covers the time from the end of
|
||||
# the request header read to the end of the response write.
|
||||
# If zero, no timeout exists.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
#
|
||||
# writeTimeout = "5s"
|
||||
|
||||
# idleTimeout is the maximum duration an idle (keep-alive) connection will remain idle before closing itself.
|
||||
# If zero, no timeout exists.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "180s"
|
||||
#
|
||||
# idleTimeout = "360s"
|
||||
|
||||
```
|
||||
|
||||
## Forwarding timeouts
|
||||
```
|
||||
# forwardingTimeouts are timeouts for requests forwarded to the backend servers.
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
[forwardingTimeouts]
|
||||
|
||||
# dialTimeout is the amount of time to wait until a connection to a backend server can be established.
|
||||
# If zero, no timeout exists.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "30s"
|
||||
#
|
||||
# dialTimeout = "30s"
|
||||
|
||||
# responseHeaderTimeout is the amount of time to wait for a server's response headers after fully writing the request (including its body, if any).
|
||||
# If zero, no timeout exists.
|
||||
# Can be provided in a format supported by [time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) or as raw
|
||||
# values (digits). If no units are provided, the value is parsed assuming seconds.
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
#
|
||||
# responseHeaderTimeout = "0s"
|
||||
```
|
||||
|
||||
## ACME (Let's Encrypt) configuration
|
||||
|
||||
```toml
|
||||
|
|
38
integration/fixtures/timeout/forwarding_timeouts.toml
Normal file
38
integration/fixtures/timeout/forwarding_timeouts.toml
Normal file
|
@ -0,0 +1,38 @@
|
|||
logLevel = "DEBUG"
|
||||
defaultEntryPoints = ["http"]
|
||||
|
||||
[entryPoints]
|
||||
[entryPoints.http]
|
||||
address = ":8000"
|
||||
|
||||
[accessLog]
|
||||
format = "json"
|
||||
|
||||
[web]
|
||||
address = ":8080"
|
||||
|
||||
[forwardingTimeouts]
|
||||
dialTimeout = "300ms"
|
||||
responseHeaderTimeout = "300ms"
|
||||
|
||||
[file]
|
||||
|
||||
[backends]
|
||||
[backends.backend1]
|
||||
[backends.backend1.servers.server1]
|
||||
# Non-routable IP address that should always deliver a dial timeout.
|
||||
# See: https://stackoverflow.com/questions/100841/artificially-create-a-connection-timeout-error#answer-904609
|
||||
url = "http://10.255.255.1"
|
||||
[backends.backend2]
|
||||
[backends.backend2.servers.server2]
|
||||
url = "http://{{.TimeoutEndpoint}}:9000"
|
||||
|
||||
[frontends]
|
||||
[frontends.frontend1]
|
||||
backend = "backend1"
|
||||
[frontends.frontend1.routes.test_1]
|
||||
rule = "Path:/dialTimeout"
|
||||
[frontends.frontend2]
|
||||
backend = "backend2"
|
||||
[frontends.frontend2.routes.test_2]
|
||||
rule = "Path:/responseHeaderTimeout"
|
|
@ -25,6 +25,7 @@ func init() {
|
|||
check.Suite(&SimpleSuite{})
|
||||
check.Suite(&AccessLogSuite{})
|
||||
check.Suite(&HTTPSSuite{})
|
||||
check.Suite(&TimeoutSuite{})
|
||||
check.Suite(&FileSuite{})
|
||||
check.Suite(&HealthCheckSuite{})
|
||||
check.Suite(&DockerSuite{})
|
||||
|
|
7
integration/resources/compose/timeout.yml
Normal file
7
integration/resources/compose/timeout.yml
Normal file
|
@ -0,0 +1,7 @@
|
|||
timeoutEndpoint:
|
||||
image: yaman/timeout
|
||||
environment:
|
||||
- PROTO=http
|
||||
- PORT=9000
|
||||
ports:
|
||||
- "9000:9000"
|
44
integration/timeout_test.go
Normal file
44
integration/timeout_test.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/containous/traefik/integration/try"
|
||||
"github.com/go-check/check"
|
||||
checker "github.com/vdemeester/shakers"
|
||||
)
|
||||
|
||||
type TimeoutSuite struct{ BaseSuite }
|
||||
|
||||
func (s *TimeoutSuite) SetUpSuite(c *check.C) {
|
||||
s.createComposeProject(c, "timeout")
|
||||
s.composeProject.Start(c)
|
||||
}
|
||||
|
||||
func (s *TimeoutSuite) TestForwardingTimeouts(c *check.C) {
|
||||
httpTimeoutEndpoint := s.composeProject.Container(c, "timeoutEndpoint").NetworkSettings.IPAddress
|
||||
file := s.adaptFile(c, "fixtures/timeout/forwarding_timeouts.toml", struct {
|
||||
TimeoutEndpoint string
|
||||
}{httpTimeoutEndpoint})
|
||||
defer os.Remove(file)
|
||||
|
||||
cmd, _ := s.cmdTraefik(withConfigFile(file))
|
||||
err := cmd.Start()
|
||||
c.Assert(err, checker.IsNil)
|
||||
defer cmd.Process.Kill()
|
||||
|
||||
err = try.GetRequest("http://127.0.0.1:8080/api/providers", 60*time.Second, try.BodyContains("Path:/dialTimeout"))
|
||||
c.Assert(err, checker.IsNil)
|
||||
|
||||
// This simulates a DialTimeout when connecting to the backend server.
|
||||
response, err := http.Get("http://127.0.0.1:8000/dialTimeout")
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, http.StatusGatewayTimeout)
|
||||
|
||||
// This simulates a ResponseHeaderTimeout.
|
||||
response, err = http.Get("http://127.0.0.1:8000/responseHeaderTimeout?sleep=1000")
|
||||
c.Assert(err, checker.IsNil)
|
||||
c.Assert(response.StatusCode, checker.Equals, http.StatusGatewayTimeout)
|
||||
}
|
|
@ -28,8 +28,15 @@ import (
|
|||
"github.com/containous/traefik/types"
|
||||
)
|
||||
|
||||
// DefaultHealthCheckInterval is the default health check interval.
|
||||
const DefaultHealthCheckInterval = 30 * time.Second
|
||||
const (
|
||||
// DefaultHealthCheckInterval is the default health check interval.
|
||||
DefaultHealthCheckInterval = 30 * time.Second
|
||||
|
||||
// DefaultDialTimeout when connecting to a backend server.
|
||||
DefaultDialTimeout = 30 * time.Second
|
||||
// DefaultIdleTimeout before closing an idle connection.
|
||||
DefaultIdleTimeout = 180 * time.Second
|
||||
)
|
||||
|
||||
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||
type TraefikConfiguration struct {
|
||||
|
@ -54,11 +61,13 @@ type GlobalConfiguration struct {
|
|||
DefaultEntryPoints DefaultEntryPoints `description:"Entrypoints to be used by frontends that do not specify any entrypoint"`
|
||||
ProvidersThrottleDuration flaeg.Duration `description:"Backends throttle duration: minimum duration between 2 events from providers before applying a new configuration. It avoids unnecessary reloads if multiples events are sent in a short amount of time."`
|
||||
MaxIdleConnsPerHost int `description:"If non-zero, controls the maximum idle (keep-alive) to keep per-host. If zero, DefaultMaxIdleConnsPerHost is used"`
|
||||
IdleTimeout flaeg.Duration `description:"maximum amount of time an idle (keep-alive) connection will remain idle before closing itself."`
|
||||
IdleTimeout flaeg.Duration `description:"(Deprecated) maximum amount of time an idle (keep-alive) connection will remain idle before closing itself."` // Deprecated
|
||||
InsecureSkipVerify bool `description:"Disable SSL certificate verification"`
|
||||
RootCAs RootCAs `description:"Add cert file for self-signed certicate"`
|
||||
Retry *Retry `description:"Enable retry sending request if network error"`
|
||||
HealthCheck *HealthCheckConfig `description:"Health check parameters"`
|
||||
RespondingTimeouts *RespondingTimeouts `description:"Timeouts for incoming requests to the Traefik instance"`
|
||||
ForwardingTimeouts *ForwardingTimeouts `description:"Timeouts for requests forwarded to the backend servers"`
|
||||
Docker *docker.Provider `description:"Enable Docker backend with default settings"`
|
||||
File *file.Provider `description:"Enable File backend with default settings"`
|
||||
Web *WebProvider `description:"Enable Web backend with default settings"`
|
||||
|
@ -411,6 +420,19 @@ type HealthCheckConfig struct {
|
|||
Interval flaeg.Duration `description:"Default periodicity of enabled health checks"`
|
||||
}
|
||||
|
||||
// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
|
||||
type RespondingTimeouts struct {
|
||||
ReadTimeout flaeg.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set"`
|
||||
WriteTimeout flaeg.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set"`
|
||||
IdleTimeout flaeg.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. Defaults to 180 seconds. If zero, no timeout is set"`
|
||||
}
|
||||
|
||||
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
|
||||
type ForwardingTimeouts struct {
|
||||
DialTimeout flaeg.Duration `description:"The amount of time to wait until a connection to a backend server can be established. Defaults to 30 seconds. If zero, no timeout exists"`
|
||||
ResponseHeaderTimeout flaeg.Duration `description:"The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists"`
|
||||
}
|
||||
|
||||
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
|
||||
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||
//default Docker
|
||||
|
@ -573,10 +595,16 @@ func NewTraefikConfiguration() *TraefikConfiguration {
|
|||
DefaultEntryPoints: []string{},
|
||||
ProvidersThrottleDuration: flaeg.Duration(2 * time.Second),
|
||||
MaxIdleConnsPerHost: 200,
|
||||
IdleTimeout: flaeg.Duration(180 * time.Second),
|
||||
IdleTimeout: flaeg.Duration(0),
|
||||
HealthCheck: &HealthCheckConfig{
|
||||
Interval: flaeg.Duration(DefaultHealthCheckInterval),
|
||||
},
|
||||
RespondingTimeouts: &RespondingTimeouts{
|
||||
IdleTimeout: flaeg.Duration(DefaultIdleTimeout),
|
||||
},
|
||||
ForwardingTimeouts: &ForwardingTimeouts{
|
||||
DialTimeout: flaeg.Duration(DefaultDialTimeout),
|
||||
},
|
||||
CheckNewVersion: true,
|
||||
},
|
||||
ConfigFile: "",
|
||||
|
|
175
server/server.go
175
server/server.go
|
@ -7,6 +7,7 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
|
@ -33,23 +34,25 @@ import (
|
|||
"github.com/vulcand/oxy/forward"
|
||||
"github.com/vulcand/oxy/roundrobin"
|
||||
"github.com/vulcand/oxy/utils"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
var oxyLogger = &OxyLogger{}
|
||||
|
||||
// Server is the reverse-proxy/load-balancer engine
|
||||
type Server struct {
|
||||
serverEntryPoints serverEntryPoints
|
||||
configurationChan chan types.ConfigMessage
|
||||
configurationValidatedChan chan types.ConfigMessage
|
||||
signals chan os.Signal
|
||||
stopChan chan bool
|
||||
providers []provider.Provider
|
||||
currentConfigurations safe.Safe
|
||||
globalConfiguration GlobalConfiguration
|
||||
accessLoggerMiddleware *accesslog.LogHandler
|
||||
routinesPool *safe.Pool
|
||||
leadership *cluster.Leadership
|
||||
serverEntryPoints serverEntryPoints
|
||||
configurationChan chan types.ConfigMessage
|
||||
configurationValidatedChan chan types.ConfigMessage
|
||||
signals chan os.Signal
|
||||
stopChan chan bool
|
||||
providers []provider.Provider
|
||||
currentConfigurations safe.Safe
|
||||
globalConfiguration GlobalConfiguration
|
||||
accessLoggerMiddleware *accesslog.LogHandler
|
||||
routinesPool *safe.Pool
|
||||
leadership *cluster.Leadership
|
||||
defaultForwardingRoundTripper http.RoundTripper
|
||||
}
|
||||
|
||||
type serverEntryPoints map[string]*serverEntryPoint
|
||||
|
@ -82,6 +85,8 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
|||
server.currentConfigurations.Set(currentConfigurations)
|
||||
server.globalConfiguration = globalConfiguration
|
||||
server.routinesPool = safe.NewPool(context.Background())
|
||||
server.defaultForwardingRoundTripper = createHTTPTransport(globalConfiguration)
|
||||
|
||||
if globalConfiguration.Cluster != nil {
|
||||
// leadership creation if cluster mode
|
||||
server.leadership = cluster.NewLeadership(server.routinesPool.Ctx(), globalConfiguration.Cluster)
|
||||
|
@ -101,6 +106,60 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
|||
return server
|
||||
}
|
||||
|
||||
// createHTTPTransport creates an http.Transport configured with the GlobalConfiguration settings.
|
||||
// For the settings that can't be configured in Traefik it uses the default http.Transport settings.
|
||||
// An exception to this is the MaxIdleConns setting as we only provide the option MaxIdleConnsPerHost
|
||||
// in Traefik at this point in time. Setting this value to the default of 100 could lead to confusing
|
||||
// behaviour and backwards compatibility issues.
|
||||
func createHTTPTransport(globalConfiguration GlobalConfiguration) *http.Transport {
|
||||
dialer := &net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}
|
||||
if globalConfiguration.ForwardingTimeouts != nil {
|
||||
dialer.Timeout = time.Duration(globalConfiguration.ForwardingTimeouts.DialTimeout)
|
||||
}
|
||||
|
||||
transport := &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
DialContext: dialer.DialContext,
|
||||
MaxIdleConnsPerHost: globalConfiguration.MaxIdleConnsPerHost,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
}
|
||||
if globalConfiguration.ForwardingTimeouts != nil {
|
||||
transport.ResponseHeaderTimeout = time.Duration(globalConfiguration.ForwardingTimeouts.ResponseHeaderTimeout)
|
||||
}
|
||||
if globalConfiguration.InsecureSkipVerify {
|
||||
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
if len(globalConfiguration.RootCAs) > 0 {
|
||||
transport.TLSClientConfig = &tls.Config{
|
||||
RootCAs: createRootCACertPool(globalConfiguration.RootCAs),
|
||||
}
|
||||
http2.ConfigureTransport(transport)
|
||||
}
|
||||
|
||||
return transport
|
||||
}
|
||||
|
||||
func createRootCACertPool(rootCAs RootCAs) *x509.CertPool {
|
||||
roots := x509.NewCertPool()
|
||||
|
||||
for _, cert := range rootCAs {
|
||||
certContent, err := cert.Read()
|
||||
if err != nil {
|
||||
log.Error("Error while read RootCAs", err)
|
||||
continue
|
||||
}
|
||||
roots.AppendCertsFromPEM(certContent)
|
||||
}
|
||||
|
||||
return roots
|
||||
}
|
||||
|
||||
// Start starts the server.
|
||||
func (server *Server) Start() {
|
||||
server.startHTTPServers()
|
||||
|
@ -224,7 +283,7 @@ func (server *Server) setupServerEntryPoint(newServerEntryPointName string, newS
|
|||
}
|
||||
serverMiddlewares = append(serverMiddlewares, ipWhitelistMiddleware)
|
||||
}
|
||||
newsrv, err := server.prepareServer(newServerEntryPointName, newServerEntryPoint.httpRouter, server.globalConfiguration.EntryPoints[newServerEntryPointName], serverMiddlewares...)
|
||||
newsrv, err := server.prepareServer(newServerEntryPointName, server.globalConfiguration.EntryPoints[newServerEntryPointName], newServerEntryPoint.httpRouter, serverMiddlewares...)
|
||||
if err != nil {
|
||||
log.Fatal("Error preparing server: ", err)
|
||||
}
|
||||
|
@ -551,14 +610,17 @@ func (server *Server) startServer(srv *http.Server, globalConfiguration GlobalCo
|
|||
}
|
||||
}
|
||||
|
||||
func (server *Server) prepareServer(entryPointName string, router *middlewares.HandlerSwitcher, entryPoint *EntryPoint, middlewares ...negroni.Handler) (*http.Server, error) {
|
||||
log.Infof("Preparing server %s %+v", entryPointName, entryPoint)
|
||||
func (server *Server) prepareServer(entryPointName string, entryPoint *EntryPoint, router *middlewares.HandlerSwitcher, middlewares ...negroni.Handler) (*http.Server, error) {
|
||||
readTimeout, writeTimeout, idleTimeout := buildServerTimeouts(server.globalConfiguration)
|
||||
log.Infof("Preparing server %s %+v with readTimeout=%s writeTimeout=%s idleTimeout=%s", entryPointName, entryPoint, readTimeout, writeTimeout, idleTimeout)
|
||||
|
||||
// middlewares
|
||||
var negroni = negroni.New()
|
||||
n := negroni.New()
|
||||
for _, middleware := range middlewares {
|
||||
negroni.Use(middleware)
|
||||
n.Use(middleware)
|
||||
}
|
||||
negroni.UseHandler(router)
|
||||
n.UseHandler(router)
|
||||
|
||||
tlsConfig, err := server.createTLSConfig(entryPointName, entryPoint.TLS, router)
|
||||
if err != nil {
|
||||
log.Errorf("Error creating TLS config: %s", err)
|
||||
|
@ -566,13 +628,37 @@ func (server *Server) prepareServer(entryPointName string, router *middlewares.H
|
|||
}
|
||||
|
||||
return &http.Server{
|
||||
Addr: entryPoint.Address,
|
||||
Handler: negroni,
|
||||
TLSConfig: tlsConfig,
|
||||
IdleTimeout: time.Duration(server.globalConfiguration.IdleTimeout),
|
||||
Addr: entryPoint.Address,
|
||||
Handler: n,
|
||||
TLSConfig: tlsConfig,
|
||||
ReadTimeout: readTimeout,
|
||||
WriteTimeout: writeTimeout,
|
||||
IdleTimeout: idleTimeout,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func buildServerTimeouts(globalConfig GlobalConfiguration) (readTimeout, writeTimeout, idleTimeout time.Duration) {
|
||||
readTimeout = time.Duration(0)
|
||||
writeTimeout = time.Duration(0)
|
||||
if globalConfig.RespondingTimeouts != nil {
|
||||
readTimeout = time.Duration(globalConfig.RespondingTimeouts.ReadTimeout)
|
||||
writeTimeout = time.Duration(globalConfig.RespondingTimeouts.WriteTimeout)
|
||||
}
|
||||
|
||||
// When RespondingTimeouts.IdleTimout is configured, always use that setting
|
||||
if globalConfig.RespondingTimeouts != nil {
|
||||
idleTimeout = time.Duration(globalConfig.RespondingTimeouts.IdleTimeout)
|
||||
} else if globalConfig.IdleTimeout != 0 {
|
||||
// Backwards compatibility for deprecated IdleTimeout
|
||||
idleTimeout = time.Duration(globalConfig.IdleTimeout)
|
||||
} else {
|
||||
// Default value if neither the deprecated IdleTimeout nor the new RespondingTimeouts.IdleTimout are configured
|
||||
idleTimeout = time.Duration(DefaultIdleTimeout)
|
||||
}
|
||||
|
||||
return readTimeout, writeTimeout, idleTimeout
|
||||
}
|
||||
|
||||
func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) map[string]*serverEntryPoint {
|
||||
serverEntryPoints := make(map[string]*serverEntryPoint)
|
||||
for entryPointName := range globalConfiguration.EntryPoints {
|
||||
|
@ -584,15 +670,22 @@ func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration)
|
|||
return serverEntryPoints
|
||||
}
|
||||
|
||||
// clientTLSRoundTripper is used for forwarding client authentication to
|
||||
// backend server
|
||||
func clientTLSRoundTripper(config *tls.Config) http.RoundTripper {
|
||||
if config == nil {
|
||||
return http.DefaultTransport
|
||||
}
|
||||
return &http.Transport{
|
||||
TLSClientConfig: config,
|
||||
// getRoundTripper will either use server.defaultForwardingRoundTripper or create a new one
|
||||
// given a custom TLS configuration is passed and the passTLSCert option is set to true.
|
||||
func (server *Server) getRoundTripper(globalConfiguration GlobalConfiguration, passTLSCert bool, tls *TLS) (http.RoundTripper, error) {
|
||||
if passTLSCert {
|
||||
tlsConfig, err := createClientTLSConfig(tls)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create TLSClientConfig: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transport := createHTTPTransport(globalConfiguration)
|
||||
transport.TLSClientConfig = tlsConfig
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
return server.defaultForwardingRoundTripper, nil
|
||||
}
|
||||
|
||||
// LoadConfig returns a new gorilla.mux Route from the specified global configuration and the dynamic
|
||||
|
@ -660,29 +753,20 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
|||
if backends[entryPointName+frontend.Backend] == nil {
|
||||
log.Debugf("Creating backend %s", frontend.Backend)
|
||||
|
||||
var (
|
||||
tlsConfig *tls.Config
|
||||
err error
|
||||
lb http.Handler
|
||||
)
|
||||
|
||||
if frontend.PassTLSCert {
|
||||
tlsConfig, err = createClientTLSConfig(entryPoint.TLS)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create TLS config for frontend %s: %v", frontendName, err)
|
||||
continue frontend
|
||||
}
|
||||
roundTripper, err := server.getRoundTripper(globalConfiguration, frontend.PassTLSCert, entryPoint.TLS)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to create RoundTripper for frontend %s: %v", frontendName, err)
|
||||
log.Errorf("Skipping frontend %s...", frontendName)
|
||||
continue frontend
|
||||
}
|
||||
|
||||
// passing nil will use the roundtripper http.DefaultTransport
|
||||
rt := clientTLSRoundTripper(tlsConfig)
|
||||
|
||||
fwd, err := forward.New(
|
||||
forward.Logger(oxyLogger),
|
||||
forward.PassHostHeader(frontend.PassHostHeader),
|
||||
forward.RoundTripper(rt),
|
||||
forward.RoundTripper(roundTripper),
|
||||
forward.ErrorHandler(errorHandler),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Errorf("Error creating forwarder for frontend %s: %v", frontendName, err)
|
||||
log.Errorf("Skipping frontend %s...", frontendName)
|
||||
|
@ -720,6 +804,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
|||
sticky = roundrobin.NewStickySession(cookiename)
|
||||
}
|
||||
|
||||
var lb http.Handler
|
||||
switch lbMethod {
|
||||
case types.Drr:
|
||||
log.Debugf("Creating load-balancer drr")
|
||||
|
|
|
@ -36,6 +36,86 @@ func (lb *testLoadBalancer) Servers() []*url.URL {
|
|||
return []*url.URL{}
|
||||
}
|
||||
|
||||
func TestPrepareServerTimeouts(t *testing.T) {
|
||||
tests := []struct {
|
||||
desc string
|
||||
globalConfig GlobalConfiguration
|
||||
wantIdleTimeout time.Duration
|
||||
wantReadTimeout time.Duration
|
||||
wantWriteTimeout time.Duration
|
||||
}{
|
||||
{
|
||||
desc: "full configuration",
|
||||
globalConfig: GlobalConfiguration{
|
||||
RespondingTimeouts: &RespondingTimeouts{
|
||||
IdleTimeout: flaeg.Duration(10 * time.Second),
|
||||
ReadTimeout: flaeg.Duration(12 * time.Second),
|
||||
WriteTimeout: flaeg.Duration(14 * time.Second),
|
||||
},
|
||||
},
|
||||
wantIdleTimeout: time.Duration(10 * time.Second),
|
||||
wantReadTimeout: time.Duration(12 * time.Second),
|
||||
wantWriteTimeout: time.Duration(14 * time.Second),
|
||||
},
|
||||
{
|
||||
desc: "using defaults",
|
||||
globalConfig: GlobalConfiguration{},
|
||||
wantIdleTimeout: time.Duration(180 * time.Second),
|
||||
wantReadTimeout: time.Duration(0 * time.Second),
|
||||
wantWriteTimeout: time.Duration(0 * time.Second),
|
||||
},
|
||||
{
|
||||
desc: "deprecated IdleTimeout configured",
|
||||
globalConfig: GlobalConfiguration{
|
||||
IdleTimeout: flaeg.Duration(45 * time.Second),
|
||||
},
|
||||
wantIdleTimeout: time.Duration(45 * time.Second),
|
||||
wantReadTimeout: time.Duration(0 * time.Second),
|
||||
wantWriteTimeout: time.Duration(0 * time.Second),
|
||||
},
|
||||
{
|
||||
desc: "deprecated and new IdleTimeout configured",
|
||||
globalConfig: GlobalConfiguration{
|
||||
IdleTimeout: flaeg.Duration(45 * time.Second),
|
||||
RespondingTimeouts: &RespondingTimeouts{
|
||||
IdleTimeout: flaeg.Duration(80 * time.Second),
|
||||
},
|
||||
},
|
||||
wantIdleTimeout: time.Duration(80 * time.Second),
|
||||
wantReadTimeout: time.Duration(0 * time.Second),
|
||||
wantWriteTimeout: time.Duration(0 * time.Second),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
entryPointName := "http"
|
||||
entryPoint := &EntryPoint{Address: "localhost:8080"}
|
||||
router := middlewares.NewHandlerSwitcher(mux.NewRouter())
|
||||
|
||||
srv := NewServer(test.globalConfig)
|
||||
httpServer, err := srv.prepareServer(entryPointName, entryPoint, router)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected error when preparing srv: %s", err)
|
||||
}
|
||||
|
||||
if httpServer.IdleTimeout != test.wantIdleTimeout {
|
||||
t.Errorf("Got %s as IdleTimeout, want %s", httpServer.IdleTimeout, test.wantIdleTimeout)
|
||||
}
|
||||
if httpServer.ReadTimeout != test.wantReadTimeout {
|
||||
t.Errorf("Got %s as ReadTimeout, want %s", httpServer.ReadTimeout, test.wantReadTimeout)
|
||||
}
|
||||
if httpServer.WriteTimeout != test.wantWriteTimeout {
|
||||
t.Errorf("Got %s as WriteTimeout, want %s", httpServer.WriteTimeout, test.wantWriteTimeout)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerMultipleFrontendRules(t *testing.T) {
|
||||
cases := []struct {
|
||||
expression string
|
||||
|
|
|
@ -366,6 +366,58 @@
|
|||
#
|
||||
# interval = "30s"
|
||||
|
||||
# Timeout settings for the http servers Traefik starts
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [respondingTimeouts]
|
||||
|
||||
# ReadTimeout is the maximum duration for reading the entire request, including the body.
|
||||
# If zero, no timeout exists.
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
#
|
||||
# readTimeout = "5s"
|
||||
|
||||
# WriteTimeout is the maximum duration before timing out writes of the response.
|
||||
# If zero, no timeout exists.
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
#
|
||||
# writeTimeout = "5s"
|
||||
|
||||
# IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself.
|
||||
# Defaults to 180 seconds.
|
||||
# If zero, no timeout exists.
|
||||
#
|
||||
# Optional
|
||||
# Default: "180s"
|
||||
#
|
||||
# idleTimeout = "360s"
|
||||
|
||||
# Timeout settings for requests forwarded to the Backend Servers
|
||||
#
|
||||
# Optional
|
||||
#
|
||||
# [forwardingTimeouts]
|
||||
|
||||
# The amount of time to wait until a connection to a Backend Server can be established.
|
||||
# If zero, no timeout exists.
|
||||
#
|
||||
# Optional
|
||||
# Default: "30s"
|
||||
#
|
||||
# dialTimeout = "30s"
|
||||
|
||||
# The amount of time to wait for a server's response headers after fully writing the request (including its body, if any). If zero, no timeout exists
|
||||
#
|
||||
# Optional
|
||||
# Default: "0s"
|
||||
#
|
||||
# responseHeaderTimeout = "0s"
|
||||
|
||||
################################################################
|
||||
# Web configuration backend
|
||||
################################################################
|
||||
|
|
Loading…
Reference in a new issue