Add configurable timeouts and curate default timeout settings

This commit is contained in:
Marco Jantke 2017-08-18 15:34:04 +02:00 committed by Traefiker
parent d84ccbc52a
commit 14a0d66410
10 changed files with 460 additions and 76 deletions

View file

@ -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")