Merge branch v2.11 into v3.0

This commit is contained in:
kevinpollet 2024-04-10 15:27:56 +02:00
commit c1d9b9ee1f
No known key found for this signature in database
GPG key ID: 0C9A5DDD1B292453
33 changed files with 602 additions and 159 deletions

View file

@ -13,6 +13,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
ptypes "github.com/traefik/paerser/types"
"github.com/traefik/traefik/v3/pkg/config/runtime"
"github.com/traefik/traefik/v3/pkg/config/static"
)
@ -55,9 +56,11 @@ func TestHandler_EntryPoints(t *testing.T) {
GraceTimeOut: 2,
},
RespondingTimeouts: &static.RespondingTimeouts{
ReadTimeout: 3,
WriteTimeout: 4,
IdleTimeout: 5,
HTTP: &static.HTTPRespondingTimeouts{
ReadTimeout: paerserDurationPtr(3),
WriteTimeout: paerserDurationPtr(4),
IdleTimeout: paerserDurationPtr(5),
},
},
},
ProxyProtocol: &static.ProxyProtocol{
@ -77,9 +80,11 @@ func TestHandler_EntryPoints(t *testing.T) {
GraceTimeOut: 20,
},
RespondingTimeouts: &static.RespondingTimeouts{
ReadTimeout: 30,
WriteTimeout: 40,
IdleTimeout: 50,
HTTP: &static.HTTPRespondingTimeouts{
ReadTimeout: paerserDurationPtr(3),
WriteTimeout: paerserDurationPtr(4),
IdleTimeout: paerserDurationPtr(5),
},
},
},
ProxyProtocol: &static.ProxyProtocol{
@ -263,3 +268,8 @@ func generateEntryPoints(nb int) map[string]*static.EntryPoint {
return eps
}
func paerserDurationPtr(duration int) *ptypes.Duration {
d := ptypes.Duration(duration)
return &d
}

View file

@ -23,9 +23,11 @@
"requestAcceptGraceTimeout": "1ns"
},
"respondingTimeouts": {
"idleTimeout": "5ns",
"readTimeout": "3ns",
"writeTimeout": "4ns"
"http": {
"idleTimeout": "5ns",
"readTimeout": "3ns",
"writeTimeout": "4ns"
}
}
}
},
@ -53,9 +55,11 @@
"requestAcceptGraceTimeout": "10ns"
},
"respondingTimeouts": {
"idleTimeout": "50ns",
"readTimeout": "30ns",
"writeTimeout": "40ns"
"http": {
"idleTimeout": "5ns",
"readTimeout": "3ns",
"writeTimeout": "4ns"
}
}
}
}

View file

@ -423,11 +423,11 @@ func (s *IPStrategy) Get() (ip.Strategy, error) {
// +k8s:deepcopy-gen=true
// IPWhiteList holds the IP whitelist middleware configuration.
// This middleware accepts / refuses requests based on the client IP.
// This middleware limits allowed requests based on the client IP.
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipwhitelist/
// Deprecated: please use IPAllowList instead.
type IPWhiteList struct {
// SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation).
// SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation). Required.
SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"`
IPStrategy *IPStrategy `json:"ipStrategy,omitempty" toml:"ipStrategy,omitempty" yaml:"ipStrategy,omitempty" label:"allowEmpty" file:"allowEmpty" kv:"allowEmpty" export:"true"`
}
@ -435,7 +435,7 @@ type IPWhiteList struct {
// +k8s:deepcopy-gen=true
// IPAllowList holds the IP allowlist middleware configuration.
// This middleware accepts / refuses requests based on the client IP.
// This middleware limits allowed requests based on the client IP.
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/http/ipallowlist/
type IPAllowList struct {
// SourceRange defines the set of allowed IPs (or ranges of allowed IPs by using CIDR notation).

View file

@ -34,6 +34,8 @@ type TCPIPWhiteList struct {
// +k8s:deepcopy-gen=true
// TCPIPAllowList holds the TCP IPAllowList middleware configuration.
// This middleware limits allowed requests based on the client IP.
// More info: https://doc.traefik.io/traefik/v3.0/middlewares/tcp/ipallowlist/
type TCPIPAllowList struct {
// SourceRange defines the allowed IPs (or ranges of allowed IPs by using CIDR notation).
SourceRange []string `json:"sourceRange,omitempty" toml:"sourceRange,omitempty" yaml:"sourceRange,omitempty"`

View file

@ -48,6 +48,9 @@ const (
// DefaultUDPTimeout defines how long to wait by default on an idle session,
// before releasing all resources related to that session.
DefaultUDPTimeout = 3 * time.Second
// defaultLingeringTimeout defines the default maximum duration between each read operation on the connection.
defaultLingeringTimeout = 2 * time.Second
)
// Configuration is the static configuration.
@ -155,16 +158,44 @@ func (a *API) SetDefaults() {
a.Dashboard = true
}
// RespondingTimeouts contains timeout configurations for incoming requests to the Traefik instance.
// RespondingTimeouts contains timeout configurations.
type RespondingTimeouts struct {
ReadTimeout ptypes.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"`
WriteTimeout ptypes.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"`
IdleTimeout ptypes.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"`
// Deprecated: please use `respondingTimeouts.http.readTimeout` instead.
ReadTimeout *ptypes.Duration `description:"(Deprecated) ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"`
// Deprecated: please use `respondingTimeouts.http.writeTimeout` instead.
WriteTimeout *ptypes.Duration `description:"(Deprecated) WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"`
// Deprecated: please use `respondingTimeouts.http.idleTimeout` instead.
IdleTimeout *ptypes.Duration `description:"(Deprecated) IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"`
HTTP *HTTPRespondingTimeouts `description:"Defines the HTTP responding timeouts." json:"http,omitempty" toml:"http,omitempty" yaml:"http,omitempty" export:"true"`
TCP *TCPRespondingTimeouts `description:"Defines the TCP responding timeouts." json:"tcp,omitempty" toml:"tcp,omitempty" yaml:"tcp,omitempty" export:"true"`
}
// HTTPRespondingTimeouts contains HTTP timeout configurations for incoming requests to the Traefik instance.
type HTTPRespondingTimeouts struct {
ReadTimeout *ptypes.Duration `description:"ReadTimeout is the maximum duration for reading the entire request, including the body. If zero, no timeout is set." json:"readTimeout,omitempty" toml:"readTimeout,omitempty" yaml:"readTimeout,omitempty" export:"true"`
WriteTimeout *ptypes.Duration `description:"WriteTimeout is the maximum duration before timing out writes of the response. If zero, no timeout is set." json:"writeTimeout,omitempty" toml:"writeTimeout,omitempty" yaml:"writeTimeout,omitempty" export:"true"`
IdleTimeout *ptypes.Duration `description:"IdleTimeout is the maximum amount duration an idle (keep-alive) connection will remain idle before closing itself. If zero, no timeout is set." json:"idleTimeout,omitempty" toml:"idleTimeout,omitempty" yaml:"idleTimeout,omitempty" export:"true"`
}
// TCPRespondingTimeouts contains TCP timeout configurations for client connections to the Traefik instance.
type TCPRespondingTimeouts struct {
LingeringTimeout ptypes.Duration `description:"LingeringTimeout is the maximum duration between each TCP read operation on the connection. If zero, no timeout is set." json:"lingeringTimeout,omitempty" toml:"lingeringTimeout,omitempty" yaml:"lingeringTimeout,omitempty" export:"true"`
}
// SetDefaults sets the default values.
func (a *RespondingTimeouts) SetDefaults() {
a.IdleTimeout = ptypes.Duration(DefaultIdleTimeout)
noTimeout := ptypes.Duration(0)
defaultIdleTimeout := ptypes.Duration(DefaultIdleTimeout)
a.HTTP = &HTTPRespondingTimeouts{
ReadTimeout: &noTimeout,
WriteTimeout: &noTimeout,
IdleTimeout: &defaultIdleTimeout,
}
a.TCP = &TCPRespondingTimeouts{
LingeringTimeout: ptypes.Duration(defaultLingeringTimeout),
}
}
// ForwardingTimeouts contains timeout configurations for forwarding requests to the backend servers.
@ -247,6 +278,39 @@ func (c *Configuration) SetEffectiveConfiguration() {
c.EntryPoints["http"] = ep
}
for _, entrypoint := range c.EntryPoints {
if entrypoint.Transport == nil ||
entrypoint.Transport.RespondingTimeouts == nil {
continue
}
respondingTimeouts := entrypoint.Transport.RespondingTimeouts
if respondingTimeouts.ReadTimeout != nil &&
respondingTimeouts.HTTP != nil &&
respondingTimeouts.HTTP.ReadTimeout == nil {
log.Warn().Msg("Option `respondingTimeouts.readTimeout` is deprecated, please use `respondingTimeouts.http.readTimeout` instead.")
respondingTimeouts.HTTP.ReadTimeout = respondingTimeouts.ReadTimeout
respondingTimeouts.ReadTimeout = nil
}
if respondingTimeouts.WriteTimeout != nil &&
respondingTimeouts.HTTP != nil &&
respondingTimeouts.HTTP.WriteTimeout == nil {
log.Warn().Msg("Option `respondingTimeouts.writeTimeout` is deprecated, please use `respondingTimeouts.http.writeTimeout` instead.")
respondingTimeouts.HTTP.WriteTimeout = respondingTimeouts.WriteTimeout
respondingTimeouts.WriteTimeout = nil
}
if respondingTimeouts.IdleTimeout != nil &&
respondingTimeouts.HTTP != nil &&
respondingTimeouts.HTTP.IdleTimeout == nil {
log.Warn().Msg("Option `respondingTimeouts.idleTimeout` is deprecated, please use `respondingTimeouts.http.idleTimeout` instead.")
respondingTimeouts.HTTP.IdleTimeout = respondingTimeouts.IdleTimeout
respondingTimeouts.IdleTimeout = nil
}
}
// Creates the internal traefik entry point if needed
if (c.API != nil && c.API.Insecure) ||
(c.Ping != nil && !c.Ping.ManualRouting && c.Ping.EntryPoint == DefaultInternalEntryPointName) ||
@ -358,6 +422,31 @@ func (c *Configuration) ValidateConfiguration() error {
}
}
for epName, entrypoint := range c.EntryPoints {
if entrypoint.Transport == nil ||
entrypoint.Transport.RespondingTimeouts == nil ||
entrypoint.Transport.RespondingTimeouts.HTTP == nil {
continue
}
respondingTimeouts := entrypoint.Transport.RespondingTimeouts
if respondingTimeouts.ReadTimeout != nil &&
respondingTimeouts.HTTP.ReadTimeout != nil {
return fmt.Errorf("entrypoint %q has `readTimeout` option is defined multiple times (`respondingTimeouts.readTimeout` is deprecated)", epName)
}
if respondingTimeouts.WriteTimeout != nil &&
respondingTimeouts.HTTP.WriteTimeout != nil {
return fmt.Errorf("entrypoint %q has `writeTimeout` option is defined multiple times (`respondingTimeouts.writeTimeout` is deprecated)", epName)
}
if respondingTimeouts.IdleTimeout != nil &&
respondingTimeouts.HTTP.IdleTimeout != nil {
return fmt.Errorf("entrypoint %q has `idleTimeout` option is defined multiple times (`respondingTimeouts.idleTimeout` is deprecated)", epName)
}
}
return nil
}

View file

@ -11,14 +11,15 @@ import (
// CheckFile checks file permissions and content size.
func CheckFile(name string) (bool, error) {
f, err := os.Open(name)
if err != nil {
if os.IsNotExist(err) {
f, err = os.Create(name)
if err != nil {
return false, err
}
return false, f.Chmod(0o600)
if err != nil && os.IsNotExist(err) {
nf, err := os.Create(name)
if err != nil {
return false, err
}
defer nf.Close()
return false, nf.Chmod(0o600)
}
if err != nil {
return false, err
}
defer f.Close()

View file

@ -319,7 +319,7 @@ func (p *Provider) listInstances(ctx context.Context, client *awsClient) ([]ecsI
}
var mach *machine
if len(task.Attachments) != 0 {
if aws.StringValue(taskDef.NetworkMode) == "awsvpc" && len(task.Attachments) != 0 {
if len(container.NetworkInterfaces) == 0 {
logger.Error().Msgf("Skip container %s: no network interfaces", aws.StringValue(container.Name))
continue

View file

@ -511,6 +511,8 @@ func TestDo_staticConfiguration(t *testing.T) {
SendAnonymousUsage: true,
}
paerserDuration := ptypes.Duration(111 * time.Second)
config.EntryPoints = static.EntryPoints{
"foobar": &static.EntryPoint{
Address: "foo Address",
@ -520,9 +522,14 @@ func TestDo_staticConfiguration(t *testing.T) {
GraceTimeOut: ptypes.Duration(111 * time.Second),
},
RespondingTimeouts: &static.RespondingTimeouts{
ReadTimeout: ptypes.Duration(111 * time.Second),
WriteTimeout: ptypes.Duration(111 * time.Second),
IdleTimeout: ptypes.Duration(111 * time.Second),
HTTP: &static.HTTPRespondingTimeouts{
ReadTimeout: &paerserDuration,
WriteTimeout: &paerserDuration,
IdleTimeout: &paerserDuration,
},
TCP: &static.TCPRespondingTimeouts{
LingeringTimeout: ptypes.Duration(111 * time.Second),
},
},
},
ProxyProtocol: &static.ProxyProtocol{

View file

@ -38,9 +38,14 @@
"graceTimeOut": "1m51s"
},
"respondingTimeouts": {
"readTimeout": "1m51s",
"writeTimeout": "1m51s",
"idleTimeout": "1m51s"
"http": {
"readTimeout": "1m51s",
"writeTimeout": "1m51s",
"idleTimeout": "1m51s"
},
"tcp": {
"lingeringTimeout": "1m51s"
}
}
},
"proxyProtocol": {

View file

@ -9,7 +9,6 @@ import (
"net"
"net/http"
"slices"
"time"
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
"github.com/rs/zerolog/log"
@ -128,17 +127,6 @@ func (r *Router) ServeTCP(conn tcp.WriteCloser) {
return
}
// Remove read/write deadline and delegate this to underlying tcp server (for now only handled by HTTP Server)
err = conn.SetReadDeadline(time.Time{})
if err != nil {
log.Error().Err(err).Msg("Error while setting read deadline")
}
err = conn.SetWriteDeadline(time.Time{})
if err != nil {
log.Error().Err(err).Msg("Error while setting write deadline")
}
connData, err := tcpmuxer.NewConnData(hello.serverName, conn, hello.protos)
if err != nil {
log.Error().Err(err).Msg("Error while reading TCP connection data")

View file

@ -249,24 +249,15 @@ func (e *TCPEntryPoint) Start(ctx context.Context) {
panic(err)
}
if e.transportConfiguration != nil &&
e.transportConfiguration.RespondingTimeouts != nil &&
e.transportConfiguration.RespondingTimeouts.TCP != nil &&
e.transportConfiguration.RespondingTimeouts.TCP.LingeringTimeout > 0 {
lingeringTimeout := time.Duration(e.transportConfiguration.RespondingTimeouts.TCP.LingeringTimeout)
writeCloser = newLingeringConnection(writeCloser, lingeringTimeout)
}
safe.Go(func() {
// Enforce read/write deadlines at the connection level,
// because when we're peeking the first byte to determine whether we are doing TLS,
// the deadlines at the server level are not taken into account.
if e.transportConfiguration.RespondingTimeouts.ReadTimeout > 0 {
err := writeCloser.SetReadDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.ReadTimeout)))
if err != nil {
logger.Error().Err(err).Msg("Error while setting read deadline")
}
}
if e.transportConfiguration.RespondingTimeouts.WriteTimeout > 0 {
err = writeCloser.SetWriteDeadline(time.Now().Add(time.Duration(e.transportConfiguration.RespondingTimeouts.WriteTimeout)))
if err != nil {
logger.Error().Err(err).Msg("Error while setting write deadline")
}
}
e.switcher.ServeTCP(newTrackedConnection(writeCloser, e.tracker))
})
}
@ -398,6 +389,55 @@ func writeCloser(conn net.Conn) (tcp.WriteCloser, error) {
}
}
// lingeringConn represents a writeCloser with lingeringTimeout handling.
type lingeringConn struct {
tcp.WriteCloser
lingeringTimeout time.Duration
rdlMu sync.RWMutex
// readDeadline is the current readDeadline set by an upper caller.
// In case of HTTP, the HTTP go server manipulates deadlines on the connection.
readDeadline time.Time
}
// newLingeringConnection returns the given writeCloser augmented with lingeringTimeout handling.
func newLingeringConnection(conn tcp.WriteCloser, timeout time.Duration) tcp.WriteCloser {
return &lingeringConn{
WriteCloser: conn,
lingeringTimeout: timeout,
}
}
// Read reads data from the connection and postpones the connection readDeadline according to the lingeringTimeout config.
// It also ensures that the upper level set readDeadline is enforced.
func (l *lingeringConn) Read(b []byte) (int, error) {
if l.lingeringTimeout > 0 {
deadline := time.Now().Add(l.lingeringTimeout)
l.rdlMu.RLock()
if !l.readDeadline.IsZero() && deadline.After(l.readDeadline) {
deadline = l.readDeadline
}
l.rdlMu.RUnlock()
if err := l.WriteCloser.SetReadDeadline(deadline); err != nil {
return 0, err
}
}
return l.WriteCloser.Read(b)
}
// SetReadDeadline sets and save the read deadline.
func (l *lingeringConn) SetReadDeadline(t time.Time) error {
l.rdlMu.Lock()
l.readDeadline = t
l.rdlMu.Unlock()
return l.WriteCloser.SetReadDeadline(t)
}
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections.
type tcpKeepAliveListener struct {
@ -425,7 +465,7 @@ func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
}
func buildProxyProtocolListener(ctx context.Context, entryPoint *static.EntryPoint, listener net.Listener) (net.Listener, error) {
timeout := entryPoint.Transport.RespondingTimeouts.ReadTimeout
timeout := *entryPoint.Transport.RespondingTimeouts.HTTP.ReadTimeout
// proxyproto use 200ms if ReadHeaderTimeout is set to 0 and not no timeout
if timeout == 0 {
timeout = -1
@ -608,9 +648,9 @@ func createHTTPServer(ctx context.Context, ln net.Listener, configuration *stati
serverHTTP := &http.Server{
Handler: handler,
ErrorLog: stdlog.New(logs.NoLevel(log.Logger, zerolog.DebugLevel), "", 0),
ReadTimeout: time.Duration(configuration.Transport.RespondingTimeouts.ReadTimeout),
WriteTimeout: time.Duration(configuration.Transport.RespondingTimeouts.WriteTimeout),
IdleTimeout: time.Duration(configuration.Transport.RespondingTimeouts.IdleTimeout),
ReadTimeout: time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.ReadTimeout),
WriteTimeout: time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.WriteTimeout),
IdleTimeout: time.Duration(*configuration.Transport.RespondingTimeouts.HTTP.IdleTimeout),
}
if debugConnection || (configuration.Transport != nil && (configuration.Transport.KeepAliveMaxTime > 0 || configuration.Transport.KeepAliveMaxRequests > 0)) {
serverHTTP.ConnContext = func(ctx context.Context, c net.Conn) context.Context {

View file

@ -69,8 +69,10 @@ func testShutdown(t *testing.T, router *tcprouter.Router) {
epConfig.LifeCycle.RequestAcceptGraceTimeout = 0
epConfig.LifeCycle.GraceTimeOut = ptypes.Duration(5 * time.Second)
epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(5 * time.Second)
epConfig.RespondingTimeouts.WriteTimeout = ptypes.Duration(5 * time.Second)
readTimeout := ptypes.Duration(5 * time.Second)
epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout
writeTimeout := ptypes.Duration(5 * time.Second)
epConfig.RespondingTimeouts.HTTP.WriteTimeout = &writeTimeout
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
// We explicitly use an IPV4 address because on Alpine, with an IPV6 address
@ -157,7 +159,8 @@ func startEntrypoint(entryPoint *TCPEntryPoint, router *tcprouter.Router) (net.C
func TestReadTimeoutWithoutFirstByte(t *testing.T) {
epConfig := &static.EntryPointsTransport{}
epConfig.SetDefaults()
epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second)
readTimeout := ptypes.Duration(2 * time.Second)
epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
Address: ":0",
@ -194,7 +197,84 @@ func TestReadTimeoutWithoutFirstByte(t *testing.T) {
func TestReadTimeoutWithFirstByte(t *testing.T) {
epConfig := &static.EntryPointsTransport{}
epConfig.SetDefaults()
epConfig.RespondingTimeouts.ReadTimeout = ptypes.Duration(2 * time.Second)
readTimeout := ptypes.Duration(2 * time.Second)
epConfig.RespondingTimeouts.HTTP.ReadTimeout = &readTimeout
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
Address: ":0",
Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
}, nil, nil)
require.NoError(t, err)
router := &tcprouter.Router{}
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
conn, err := startEntrypoint(entryPoint, router)
require.NoError(t, err)
_, err = conn.Write([]byte("GET /some HTTP/1.1\r\n"))
require.NoError(t, err)
errChan := make(chan error)
go func() {
b := make([]byte, 2048)
_, err := conn.Read(b)
errChan <- err
}()
select {
case err := <-errChan:
require.Equal(t, io.EOF, err)
case <-time.Tick(5 * time.Second):
t.Error("Timeout while read")
}
}
func TestLingeringTimeoutWithoutFirstByte(t *testing.T) {
epConfig := &static.EntryPointsTransport{}
epConfig.SetDefaults()
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
Address: ":0",
Transport: epConfig,
ForwardedHeaders: &static.ForwardedHeaders{},
HTTP2: &static.HTTP2Config{},
}, nil, nil)
require.NoError(t, err)
router := &tcprouter.Router{}
router.SetHTTPHandler(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
rw.WriteHeader(http.StatusOK)
}))
conn, err := startEntrypoint(entryPoint, router)
require.NoError(t, err)
errChan := make(chan error)
go func() {
b := make([]byte, 2048)
_, err := conn.Read(b)
errChan <- err
}()
select {
case err := <-errChan:
require.Equal(t, io.EOF, err)
case <-time.Tick(5 * time.Second):
t.Error("Timeout while read")
}
}
func TestLingeringTimeoutWithFirstByte(t *testing.T) {
epConfig := &static.EntryPointsTransport{}
epConfig.SetDefaults()
epConfig.RespondingTimeouts.TCP.LingeringTimeout = ptypes.Duration(time.Second)
entryPoint, err := NewTCPEntryPoint(context.Background(), &static.EntryPoint{
Address: ":0",