2019-03-14 09:30:04 +01:00
package server
import (
"bufio"
"context"
2020-01-06 16:56:05 +01:00
"errors"
"io"
2019-03-14 09:30:04 +01:00
"net"
"net/http"
2020-01-06 16:56:05 +01:00
"strings"
2019-03-14 09:30:04 +01:00
"testing"
"time"
2019-08-03 03:58:23 +02:00
"github.com/containous/traefik/v2/pkg/config/static"
"github.com/containous/traefik/v2/pkg/tcp"
"github.com/containous/traefik/v2/pkg/types"
2019-03-14 09:30:04 +01:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
2020-01-06 16:56:05 +01:00
func TestShutdownHijacked ( t * testing . T ) {
router := & tcp . Router { }
router . HTTPHandler ( http . HandlerFunc ( func ( rw http . ResponseWriter , req * http . Request ) {
conn , _ , err := rw . ( http . Hijacker ) . Hijack ( )
require . NoError ( t , err )
2019-03-14 09:30:04 +01:00
2020-01-06 16:56:05 +01:00
resp := http . Response { StatusCode : http . StatusOK }
err = resp . Write ( conn )
require . NoError ( t , err )
} ) )
testShutdown ( t , router )
}
2019-03-14 09:30:04 +01:00
2020-01-06 16:56:05 +01:00
func TestShutdownHTTP ( t * testing . T ) {
2019-03-14 09:30:04 +01:00
router := & tcp . Router { }
router . HTTPHandler ( http . HandlerFunc ( func ( rw http . ResponseWriter , req * http . Request ) {
rw . WriteHeader ( http . StatusOK )
2020-01-06 16:56:05 +01:00
time . Sleep ( time . Second )
2019-03-14 09:30:04 +01:00
} ) )
2020-01-06 16:56:05 +01:00
testShutdown ( t , router )
}
2019-03-14 09:30:04 +01:00
2020-01-06 16:56:05 +01:00
func TestShutdownTCP ( t * testing . T ) {
router := & tcp . Router { }
router . AddCatchAllNoTLS ( tcp . HandlerFunc ( func ( conn tcp . WriteCloser ) {
for {
_ , err := http . ReadRequest ( bufio . NewReader ( conn ) )
if err == io . EOF || ( err != nil && strings . HasSuffix ( err . Error ( ) , "use of closed network connection" ) ) {
return
}
require . NoError ( t , err )
resp := http . Response { StatusCode : http . StatusOK }
err = resp . Write ( conn )
require . NoError ( t , err )
}
} ) )
testShutdown ( t , router )
}
func testShutdown ( t * testing . T , router * tcp . Router ) {
epConfig := & static . EntryPointsTransport { }
epConfig . SetDefaults ( )
epConfig . LifeCycle . RequestAcceptGraceTimeout = 0
epConfig . LifeCycle . GraceTimeOut = types . Duration ( 5 * time . Second )
entryPoint , err := NewTCPEntryPoint ( context . Background ( ) , & static . EntryPoint {
// We explicitly use an IPV4 address because on Alpine, with an IPV6 address
// there seems to be shenanigans related to properly cleaning up file descriptors
Address : "127.0.0.1:0" ,
Transport : epConfig ,
ForwardedHeaders : & static . ForwardedHeaders { } ,
} )
2019-03-14 09:30:04 +01:00
require . NoError ( t , err )
2020-01-06 16:56:05 +01:00
conn , err := startEntrypoint ( entryPoint , router )
require . NoError ( t , err )
2019-03-14 09:30:04 +01:00
2020-01-06 16:56:05 +01:00
epAddr := entryPoint . listener . Addr ( ) . String ( )
request , err := http . NewRequest ( http . MethodHead , "http://127.0.0.1:8082" , nil )
2019-03-14 09:30:04 +01:00
require . NoError ( t , err )
2020-01-06 16:56:05 +01:00
time . Sleep ( time . Millisecond * 100 )
// We need to do a write on the conn before the shutdown to make it "exist".
// Because the connection indeed exists as far as TCP is concerned,
// but since we only pass it along to the HTTP server after at least one byte is peaked,
// the HTTP server (and hence its shutdown) does not know about the connection until that first byte peaking.
2019-03-14 09:30:04 +01:00
err = request . Write ( conn )
require . NoError ( t , err )
2020-01-06 16:56:05 +01:00
go entryPoint . Shutdown ( context . Background ( ) )
// Make sure that new connections are not permitted anymore.
// Note that this should be true not only after Shutdown has returned,
// but technically also as early as the Shutdown has closed the listener,
// i.e. during the shutdown and before the gracetime is over.
var testOk bool
for i := 0 ; i < 10 ; i ++ {
loopConn , err := net . Dial ( "tcp" , epAddr )
if err == nil {
loopConn . Close ( )
time . Sleep ( time . Millisecond * 100 )
continue
}
if ! strings . HasSuffix ( err . Error ( ) , "connection refused" ) && ! strings . HasSuffix ( err . Error ( ) , "reset by peer" ) {
t . Fatalf ( ` unexpected error: got %v, wanted "connection refused" or "reset by peer" ` , err )
}
testOk = true
break
}
if ! testOk {
t . Fatal ( "entry point never closed" )
}
// And make sure that the connection we had opened before shutting things down is still operational
2019-03-14 09:30:04 +01:00
resp , err := http . ReadResponse ( bufio . NewReader ( conn ) , request )
require . NoError ( t , err )
2020-01-06 16:56:05 +01:00
assert . Equal ( t , http . StatusOK , resp . StatusCode )
2019-03-14 09:30:04 +01:00
}
2020-01-06 16:56:05 +01:00
func startEntrypoint ( entryPoint * TCPEntryPoint , router * tcp . Router ) ( net . Conn , error ) {
go entryPoint . StartTCP ( context . Background ( ) )
entryPoint . SwitchRouter ( router )
var conn net . Conn
var err error
var epStarted bool
for i := 0 ; i < 10 ; i ++ {
conn , err = net . Dial ( "tcp" , entryPoint . listener . Addr ( ) . String ( ) )
if err != nil {
time . Sleep ( time . Millisecond * 100 )
continue
}
epStarted = true
break
}
if ! epStarted {
return nil , errors . New ( "entry point never started" )
}
return conn , err
}
func TestReadTimeoutWithoutFirstByte ( t * testing . T ) {
epConfig := & static . EntryPointsTransport { }
epConfig . SetDefaults ( )
epConfig . RespondingTimeouts . ReadTimeout = types . Duration ( time . Second * 2 )
2019-03-14 09:30:04 +01:00
entryPoint , err := NewTCPEntryPoint ( context . Background ( ) , & static . EntryPoint {
2020-01-06 16:56:05 +01:00
Address : ":0" ,
Transport : epConfig ,
2019-03-14 09:30:04 +01:00
ForwardedHeaders : & static . ForwardedHeaders { } ,
} )
require . NoError ( t , err )
router := & tcp . Router { }
router . HTTPHandler ( http . HandlerFunc ( func ( rw http . ResponseWriter , req * http . Request ) {
2020-01-06 16:56:05 +01:00
rw . WriteHeader ( http . StatusOK )
2019-03-14 09:30:04 +01:00
} ) )
2019-09-26 11:00:06 +02:00
2020-01-06 16:56:05 +01:00
conn , err := startEntrypoint ( entryPoint , router )
2019-03-14 09:30:04 +01:00
require . NoError ( t , err )
2020-01-06 16:56:05 +01:00
errChan := make ( chan error )
2019-03-14 09:30:04 +01:00
2020-01-06 16:56:05 +01:00
go func ( ) {
b := make ( [ ] byte , 2048 )
_ , err := conn . Read ( b )
errChan <- err
} ( )
2019-03-14 09:30:04 +01:00
2020-01-06 16:56:05 +01:00
select {
case err := <- errChan :
require . Equal ( t , io . EOF , err )
case <- time . Tick ( time . Second * 5 ) :
t . Error ( "Timeout while read" )
}
2019-03-14 09:30:04 +01:00
}
2020-01-06 16:56:05 +01:00
func TestReadTimeoutWithFirstByte ( t * testing . T ) {
epConfig := & static . EntryPointsTransport { }
epConfig . SetDefaults ( )
epConfig . RespondingTimeouts . ReadTimeout = types . Duration ( time . Second * 2 )
2019-03-14 09:30:04 +01:00
entryPoint , err := NewTCPEntryPoint ( context . Background ( ) , & static . EntryPoint {
2020-01-06 16:56:05 +01:00
Address : ":0" ,
Transport : epConfig ,
2019-03-14 09:30:04 +01:00
ForwardedHeaders : & static . ForwardedHeaders { } ,
} )
require . NoError ( t , err )
router := & tcp . Router { }
2020-01-06 16:56:05 +01:00
router . HTTPHandler ( http . HandlerFunc ( func ( rw http . ResponseWriter , req * http . Request ) {
rw . WriteHeader ( http . StatusOK )
2019-03-14 09:30:04 +01:00
} ) )
2020-01-06 16:56:05 +01:00
conn , err := startEntrypoint ( entryPoint , router )
2019-03-14 09:30:04 +01:00
require . NoError ( t , err )
2020-01-06 16:56:05 +01:00
_ , err = conn . Write ( [ ] byte ( "GET /some HTTP/1.1\r\n" ) )
2019-03-14 09:30:04 +01:00
require . NoError ( t , err )
2020-01-06 16:56:05 +01:00
errChan := make ( chan error )
2019-03-14 09:30:04 +01:00
2020-01-06 16:56:05 +01:00
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 ( time . Second * 5 ) :
t . Error ( "Timeout while read" )
}
2019-03-14 09:30:04 +01:00
}