Skip to content

Commit

Permalink
Make HTTP Keep-Alive timeout configurable for backend connections
Browse files Browse the repository at this point in the history
  • Loading branch information
mszabo-wikia authored and traefiker committed Jun 27, 2019
1 parent 84d7c65 commit f643666
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/content/reference/static-configuration/cli.txt
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,10 @@
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.

--serverstransport.forwardingtimeouts.idleconntimeout (Default: "90s")
The maximum period for which an idle HTTP keep-alive connection to a backend
server will remain open before closing itself.

--serverstransport.insecureskipverify (Default: "false")
Disable SSL certificate verification.

Expand Down
4 changes: 4 additions & 0 deletions docs/content/reference/static-configuration/env.md
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,10 @@ The amount of time to wait until a connection to a backend server can be establi
`TRAEFIK_SERVERSTRANSPORT_FORWARDINGTIMEOUTS_RESPONSEHEADERTIMEOUT`:
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. (Default: ```0```)

`TRAEFIK_SERVERSTRANSPORT_FORWARDINGTIMEOUTS_IDLECONNTIMEOUT`:
The maximum period for which an idle HTTP keep-alive connection to a backend
server will remain open before closing itself. (Default: ```90s```)

`TRAEFIK_SERVERSTRANSPORT_INSECURESKIPVERIFY`:
Disable SSL certificate verification. (Default: ```false```)

Expand Down
1 change: 1 addition & 0 deletions docs/content/reference/static-configuration/file.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
[ServersTransport.ForwardingTimeouts]
DialTimeout = 42
ResponseHeaderTimeout = 42
IdleConnTimeout = 5

[EntryPoints]

Expand Down
31 changes: 31 additions & 0 deletions integration/fixtures/timeout/keepalive.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
[global]
checkNewVersion = false
sendAnonymousUsage = false

[log]
level = "DEBUG"

[serversTransport.forwardingTimeouts]
idleConnTimeout = "{{ .IdleConnTimeout }}"

[entryPoints]
[entryPoints.web]
address = ":8000"

[api]

[providers]
[providers.file]

[http.routers]
[http.routers.router1]
Service = "keepalive"
Rule = "PathPrefix(`/keepalive`)"

[http.services]
[http.services.keepalive]
[http.services.keepalive.LoadBalancer]
passHostHeader = true
[[http.services.keepalive.LoadBalancer.Servers]]
URL = "{{ .KeepAliveServer }}"
Weight = 1
1 change: 1 addition & 0 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func init() {
check.Suite(&HeadersSuite{})
check.Suite(&HostResolverSuite{})
check.Suite(&HTTPSSuite{})
check.Suite(&KeepAliveSuite{})
check.Suite(&LogRotationSuite{})
check.Suite(&MarathonSuite{})
check.Suite(&MarathonSuite15{})
Expand Down
105 changes: 105 additions & 0 deletions integration/keepalive_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package integration

import (
"math"
"net"
"net/http"
"net/http/httptest"
"os"
"time"

"github.com/containous/traefik/integration/try"
"github.com/go-check/check"
checker "github.com/vdemeester/shakers"
)

type KeepAliveSuite struct {
BaseSuite
}

type KeepAliveConfig struct {
KeepAliveServer string
IdleConnTimeout string
}

type connStateChangeEvent struct {
key string
state http.ConnState
}

func (s *KeepAliveSuite) TestShouldRespectConfiguredBackendHttpKeepAliveTime(c *check.C) {
idleTimeout := time.Duration(75) * time.Millisecond

connStateChanges := make(chan connStateChangeEvent)
noMoreRequests := make(chan bool, 1)
completed := make(chan bool, 1)

// keep track of HTTP connections and their status changes and measure their idle period
go func() {
connCount := 0
idlePeriodStartMap := make(map[string]time.Time)
idlePeriodLengthMap := make(map[string]time.Duration)

maxWaitDuration := 5 * time.Second
maxWaitTimeExceeded := time.After(maxWaitDuration)
moreRequestsExpected := true

// Ensure that all idle HTTP connections are closed before verification phase
for moreRequestsExpected || len(idlePeriodLengthMap) < connCount {
select {
case event := <-connStateChanges:
switch event.state {
case http.StateNew:
connCount++
case http.StateIdle:
idlePeriodStartMap[event.key] = time.Now()
case http.StateClosed:
idlePeriodLengthMap[event.key] = time.Since(idlePeriodStartMap[event.key])
}
case <-noMoreRequests:
moreRequestsExpected = false
case <-maxWaitTimeExceeded:
c.Logf("timeout waiting for all connections to close, waited for %v, configured idle timeout was %v", maxWaitDuration, idleTimeout)
c.Fail()
close(completed)
return
}
}

c.Check(connCount, checker.Equals, 1)

for _, idlePeriod := range idlePeriodLengthMap {
// Our method of measuring the actual idle period is not precise, so allow some sub-ms deviation
c.Check(math.Round(idlePeriod.Seconds()), checker.LessOrEqualThan, idleTimeout.Seconds())
}

close(completed)
}()

server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}))

server.Config.ConnState = func(conn net.Conn, state http.ConnState) {
connStateChanges <- connStateChangeEvent{key: conn.RemoteAddr().String(), state: state}
}
server.Start()
defer server.Close()

config := KeepAliveConfig{KeepAliveServer: server.URL, IdleConnTimeout: idleTimeout.String()}
file := s.adaptFile(c, "fixtures/timeout/keepalive.toml", config)

defer os.Remove(file)
cmd, display := s.traefikCmd(withConfigFile(file))
defer display(c)

err := cmd.Start()
c.Check(err, checker.IsNil)
defer cmd.Process.Kill()

err = try.GetRequest("http://127.0.0.1:8000/keepalive", time.Duration(1)*time.Second, try.StatusCodeIs(200))
c.Check(err, checker.IsNil)

close(noMoreRequests)
<-completed
}
2 changes: 2 additions & 0 deletions pkg/config/static/static_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,11 +109,13 @@ func (a *RespondingTimeouts) SetDefaults() {
type ForwardingTimeouts struct {
DialTimeout types.Duration `description:"The amount of time to wait until a connection to a backend server can be established. If zero, no timeout exists." export:"true"`
ResponseHeaderTimeout types.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." export:"true"`
IdleConnTimeout types.Duration `description:"The maximum period for which an idle HTTP keep-alive connection will remain open before closing itself" export:"true"`
}

// SetDefaults sets the default values.
func (f *ForwardingTimeouts) SetDefaults() {
f.DialTimeout = types.Duration(30 * time.Second)
f.IdleConnTimeout = types.Duration(90 * time.Second)
}

// LifeCycle contains configurations relevant to the lifecycle (such as the shutdown phase) of Traefik.
Expand Down
1 change: 1 addition & 0 deletions pkg/server/roundtripper.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func createHTTPTransport(transportConfiguration *static.ServersTransport) (*http

if transportConfiguration.ForwardingTimeouts != nil {
transport.ResponseHeaderTimeout = time.Duration(transportConfiguration.ForwardingTimeouts.ResponseHeaderTimeout)
transport.IdleConnTimeout = time.Duration(transportConfiguration.ForwardingTimeouts.IdleConnTimeout)
}

if transportConfiguration.InsecureSkipVerify {
Expand Down

0 comments on commit f643666

Please sign in to comment.