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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
fmtlog "log"
|
fmtlog "log"
|
||||||
"net/http"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -29,7 +26,6 @@ import (
|
||||||
"github.com/coreos/go-systemd/daemon"
|
"github.com/coreos/go-systemd/daemon"
|
||||||
"github.com/docker/libkv/store"
|
"github.com/docker/libkv/store"
|
||||||
"github.com/satori/go.uuid"
|
"github.com/satori/go.uuid"
|
||||||
"golang.org/x/net/http2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -178,28 +174,6 @@ func run(traefikConfiguration *server.TraefikConfiguration) {
|
||||||
// load global configuration
|
// load global configuration
|
||||||
globalConfiguration := traefikConfiguration.GlobalConfiguration
|
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 {
|
if globalConfiguration.File != nil && len(globalConfiguration.File.Filename) == 0 {
|
||||||
// no filename, setting to global config file
|
// no filename, setting to global config file
|
||||||
if len(traefikConfiguration.ConfigFile) != 0 {
|
if len(traefikConfiguration.ConfigFile) != 0 {
|
||||||
|
|
77
docs/toml.md
77
docs/toml.md
|
@ -68,7 +68,12 @@
|
||||||
#
|
#
|
||||||
# ProvidersThrottleDuration = "2s"
|
# 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.
|
# 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
|
# 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.
|
# 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"
|
# 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
|
## ACME (Let's Encrypt) configuration
|
||||||
|
|
||||||
```toml
|
```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(&SimpleSuite{})
|
||||||
check.Suite(&AccessLogSuite{})
|
check.Suite(&AccessLogSuite{})
|
||||||
check.Suite(&HTTPSSuite{})
|
check.Suite(&HTTPSSuite{})
|
||||||
|
check.Suite(&TimeoutSuite{})
|
||||||
check.Suite(&FileSuite{})
|
check.Suite(&FileSuite{})
|
||||||
check.Suite(&HealthCheckSuite{})
|
check.Suite(&HealthCheckSuite{})
|
||||||
check.Suite(&DockerSuite{})
|
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"
|
"github.com/containous/traefik/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultHealthCheckInterval is the default health check interval.
|
const (
|
||||||
const DefaultHealthCheckInterval = 30 * time.Second
|
// 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
|
// TraefikConfiguration holds GlobalConfiguration and other stuff
|
||||||
type TraefikConfiguration struct {
|
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"`
|
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."`
|
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"`
|
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"`
|
InsecureSkipVerify bool `description:"Disable SSL certificate verification"`
|
||||||
RootCAs RootCAs `description:"Add cert file for self-signed certicate"`
|
RootCAs RootCAs `description:"Add cert file for self-signed certicate"`
|
||||||
Retry *Retry `description:"Enable retry sending request if network error"`
|
Retry *Retry `description:"Enable retry sending request if network error"`
|
||||||
HealthCheck *HealthCheckConfig `description:"Health check parameters"`
|
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"`
|
Docker *docker.Provider `description:"Enable Docker backend with default settings"`
|
||||||
File *file.Provider `description:"Enable File backend with default settings"`
|
File *file.Provider `description:"Enable File backend with default settings"`
|
||||||
Web *WebProvider `description:"Enable Web 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"`
|
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
|
// NewTraefikDefaultPointersConfiguration creates a TraefikConfiguration with pointers default values
|
||||||
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
func NewTraefikDefaultPointersConfiguration() *TraefikConfiguration {
|
||||||
//default Docker
|
//default Docker
|
||||||
|
@ -573,10 +595,16 @@ func NewTraefikConfiguration() *TraefikConfiguration {
|
||||||
DefaultEntryPoints: []string{},
|
DefaultEntryPoints: []string{},
|
||||||
ProvidersThrottleDuration: flaeg.Duration(2 * time.Second),
|
ProvidersThrottleDuration: flaeg.Duration(2 * time.Second),
|
||||||
MaxIdleConnsPerHost: 200,
|
MaxIdleConnsPerHost: 200,
|
||||||
IdleTimeout: flaeg.Duration(180 * time.Second),
|
IdleTimeout: flaeg.Duration(0),
|
||||||
HealthCheck: &HealthCheckConfig{
|
HealthCheck: &HealthCheckConfig{
|
||||||
Interval: flaeg.Duration(DefaultHealthCheckInterval),
|
Interval: flaeg.Duration(DefaultHealthCheckInterval),
|
||||||
},
|
},
|
||||||
|
RespondingTimeouts: &RespondingTimeouts{
|
||||||
|
IdleTimeout: flaeg.Duration(DefaultIdleTimeout),
|
||||||
|
},
|
||||||
|
ForwardingTimeouts: &ForwardingTimeouts{
|
||||||
|
DialTimeout: flaeg.Duration(DefaultDialTimeout),
|
||||||
|
},
|
||||||
CheckNewVersion: true,
|
CheckNewVersion: true,
|
||||||
},
|
},
|
||||||
ConfigFile: "",
|
ConfigFile: "",
|
||||||
|
|
143
server/server.go
143
server/server.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -33,6 +34,7 @@ import (
|
||||||
"github.com/vulcand/oxy/forward"
|
"github.com/vulcand/oxy/forward"
|
||||||
"github.com/vulcand/oxy/roundrobin"
|
"github.com/vulcand/oxy/roundrobin"
|
||||||
"github.com/vulcand/oxy/utils"
|
"github.com/vulcand/oxy/utils"
|
||||||
|
"golang.org/x/net/http2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var oxyLogger = &OxyLogger{}
|
var oxyLogger = &OxyLogger{}
|
||||||
|
@ -50,6 +52,7 @@ type Server struct {
|
||||||
accessLoggerMiddleware *accesslog.LogHandler
|
accessLoggerMiddleware *accesslog.LogHandler
|
||||||
routinesPool *safe.Pool
|
routinesPool *safe.Pool
|
||||||
leadership *cluster.Leadership
|
leadership *cluster.Leadership
|
||||||
|
defaultForwardingRoundTripper http.RoundTripper
|
||||||
}
|
}
|
||||||
|
|
||||||
type serverEntryPoints map[string]*serverEntryPoint
|
type serverEntryPoints map[string]*serverEntryPoint
|
||||||
|
@ -82,6 +85,8 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
||||||
server.currentConfigurations.Set(currentConfigurations)
|
server.currentConfigurations.Set(currentConfigurations)
|
||||||
server.globalConfiguration = globalConfiguration
|
server.globalConfiguration = globalConfiguration
|
||||||
server.routinesPool = safe.NewPool(context.Background())
|
server.routinesPool = safe.NewPool(context.Background())
|
||||||
|
server.defaultForwardingRoundTripper = createHTTPTransport(globalConfiguration)
|
||||||
|
|
||||||
if globalConfiguration.Cluster != nil {
|
if globalConfiguration.Cluster != nil {
|
||||||
// leadership creation if cluster mode
|
// leadership creation if cluster mode
|
||||||
server.leadership = cluster.NewLeadership(server.routinesPool.Ctx(), globalConfiguration.Cluster)
|
server.leadership = cluster.NewLeadership(server.routinesPool.Ctx(), globalConfiguration.Cluster)
|
||||||
|
@ -101,6 +106,60 @@ func NewServer(globalConfiguration GlobalConfiguration) *Server {
|
||||||
return 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.
|
// Start starts the server.
|
||||||
func (server *Server) Start() {
|
func (server *Server) Start() {
|
||||||
server.startHTTPServers()
|
server.startHTTPServers()
|
||||||
|
@ -224,7 +283,7 @@ func (server *Server) setupServerEntryPoint(newServerEntryPointName string, newS
|
||||||
}
|
}
|
||||||
serverMiddlewares = append(serverMiddlewares, ipWhitelistMiddleware)
|
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 {
|
if err != nil {
|
||||||
log.Fatal("Error preparing server: ", err)
|
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) {
|
func (server *Server) prepareServer(entryPointName string, entryPoint *EntryPoint, router *middlewares.HandlerSwitcher, middlewares ...negroni.Handler) (*http.Server, error) {
|
||||||
log.Infof("Preparing server %s %+v", entryPointName, entryPoint)
|
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
|
// middlewares
|
||||||
var negroni = negroni.New()
|
n := negroni.New()
|
||||||
for _, middleware := range middlewares {
|
for _, middleware := range middlewares {
|
||||||
negroni.Use(middleware)
|
n.Use(middleware)
|
||||||
}
|
}
|
||||||
negroni.UseHandler(router)
|
n.UseHandler(router)
|
||||||
|
|
||||||
tlsConfig, err := server.createTLSConfig(entryPointName, entryPoint.TLS, router)
|
tlsConfig, err := server.createTLSConfig(entryPointName, entryPoint.TLS, router)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error creating TLS config: %s", err)
|
log.Errorf("Error creating TLS config: %s", err)
|
||||||
|
@ -567,12 +629,36 @@ func (server *Server) prepareServer(entryPointName string, router *middlewares.H
|
||||||
|
|
||||||
return &http.Server{
|
return &http.Server{
|
||||||
Addr: entryPoint.Address,
|
Addr: entryPoint.Address,
|
||||||
Handler: negroni,
|
Handler: n,
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
IdleTimeout: time.Duration(server.globalConfiguration.IdleTimeout),
|
ReadTimeout: readTimeout,
|
||||||
|
WriteTimeout: writeTimeout,
|
||||||
|
IdleTimeout: idleTimeout,
|
||||||
}, nil
|
}, 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 {
|
func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration) map[string]*serverEntryPoint {
|
||||||
serverEntryPoints := make(map[string]*serverEntryPoint)
|
serverEntryPoints := make(map[string]*serverEntryPoint)
|
||||||
for entryPointName := range globalConfiguration.EntryPoints {
|
for entryPointName := range globalConfiguration.EntryPoints {
|
||||||
|
@ -584,15 +670,22 @@ func (server *Server) buildEntryPoints(globalConfiguration GlobalConfiguration)
|
||||||
return serverEntryPoints
|
return serverEntryPoints
|
||||||
}
|
}
|
||||||
|
|
||||||
// clientTLSRoundTripper is used for forwarding client authentication to
|
// getRoundTripper will either use server.defaultForwardingRoundTripper or create a new one
|
||||||
// backend server
|
// given a custom TLS configuration is passed and the passTLSCert option is set to true.
|
||||||
func clientTLSRoundTripper(config *tls.Config) http.RoundTripper {
|
func (server *Server) getRoundTripper(globalConfiguration GlobalConfiguration, passTLSCert bool, tls *TLS) (http.RoundTripper, error) {
|
||||||
if config == nil {
|
if passTLSCert {
|
||||||
return http.DefaultTransport
|
tlsConfig, err := createClientTLSConfig(tls)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Failed to create TLSClientConfig: %s", err)
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return &http.Transport{
|
|
||||||
TLSClientConfig: config,
|
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
|
// 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 {
|
if backends[entryPointName+frontend.Backend] == nil {
|
||||||
log.Debugf("Creating backend %s", frontend.Backend)
|
log.Debugf("Creating backend %s", frontend.Backend)
|
||||||
|
|
||||||
var (
|
roundTripper, err := server.getRoundTripper(globalConfiguration, frontend.PassTLSCert, entryPoint.TLS)
|
||||||
tlsConfig *tls.Config
|
|
||||||
err error
|
|
||||||
lb http.Handler
|
|
||||||
)
|
|
||||||
|
|
||||||
if frontend.PassTLSCert {
|
|
||||||
tlsConfig, err = createClientTLSConfig(entryPoint.TLS)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Failed to create TLS config for frontend %s: %v", frontendName, err)
|
log.Errorf("Failed to create RoundTripper for frontend %s: %v", frontendName, err)
|
||||||
|
log.Errorf("Skipping frontend %s...", frontendName)
|
||||||
continue frontend
|
continue frontend
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// passing nil will use the roundtripper http.DefaultTransport
|
|
||||||
rt := clientTLSRoundTripper(tlsConfig)
|
|
||||||
|
|
||||||
fwd, err := forward.New(
|
fwd, err := forward.New(
|
||||||
forward.Logger(oxyLogger),
|
forward.Logger(oxyLogger),
|
||||||
forward.PassHostHeader(frontend.PassHostHeader),
|
forward.PassHostHeader(frontend.PassHostHeader),
|
||||||
forward.RoundTripper(rt),
|
forward.RoundTripper(roundTripper),
|
||||||
forward.ErrorHandler(errorHandler),
|
forward.ErrorHandler(errorHandler),
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error creating forwarder for frontend %s: %v", frontendName, err)
|
log.Errorf("Error creating forwarder for frontend %s: %v", frontendName, err)
|
||||||
log.Errorf("Skipping frontend %s...", frontendName)
|
log.Errorf("Skipping frontend %s...", frontendName)
|
||||||
|
@ -720,6 +804,7 @@ func (server *Server) loadConfig(configurations configs, globalConfiguration Glo
|
||||||
sticky = roundrobin.NewStickySession(cookiename)
|
sticky = roundrobin.NewStickySession(cookiename)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var lb http.Handler
|
||||||
switch lbMethod {
|
switch lbMethod {
|
||||||
case types.Drr:
|
case types.Drr:
|
||||||
log.Debugf("Creating load-balancer drr")
|
log.Debugf("Creating load-balancer drr")
|
||||||
|
|
|
@ -36,6 +36,86 @@ func (lb *testLoadBalancer) Servers() []*url.URL {
|
||||||
return []*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) {
|
func TestServerMultipleFrontendRules(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
expression string
|
expression string
|
||||||
|
|
|
@ -366,6 +366,58 @@
|
||||||
#
|
#
|
||||||
# interval = "30s"
|
# 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
|
# Web configuration backend
|
||||||
################################################################
|
################################################################
|
||||||
|
|
Loading…
Reference in a new issue