Description
Description
It's a little ridiculous that the only way of stopping an engine is by creating a listener manually and hoping that the engine lets you pass that in, so that you can manually stop the listener.
This may have a bunch of unintended consequences, for example, i had a short look over the code for the standard server and if you set a custom listener you are required to setup your TLS config manually, fasthttp may act differently but that just compounds the problem. $random other engines may not allow you to set a listener
In addition it is not clear what would actually happen if a Listener was closed, at a basic level the error returned will bubble up to echo.Run where the potentially benign error will cause echo to panic instead of simply returning an error. the http go libraries do not panic like this so it is rather odd that echo does.
with tcp listeners at least - other listeners may be different - the existing tcp connections won't be closed so as far as I am aware, echo would continue to serve requests, which may or may not be what you want but is uncontrollable from echo api (personally I would have contexts enter a done state so handler functions can at least make a choice, killing any long poll handlers for example)
All of this would be made much simpler with the addition of Close()
into the engine interface and echo.Close()
for sake of completeness - and make echo.Run
return an error instead of panicing which is just a pain to deal with
Checklist
- Dependencies installed
- No typos
- Searched existing issues and docs
Expected behaviour
func main() {
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Routes
e.GET("/", hello)
// Start server
echoToDoneChan := func(echos *echo.Echo) <-chan error {
done := make(chan error, 1)
func r() {
done <- e.Run(standard.New(":1323"))
close(done)
}
return done
}
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, os.Kill)
echoc := echoToDoneChan(e)
Main:
for {
select {
case err := <-echoc:
println(err)
break Main
case <-sigc:
println("Got close signal, shutting down echo")
echo.Close() // done's contexts, closes listen server
}
}
}
Actual behaviour
// Code taken from net/http - we lose this because echo calls .Serve
// and not .ListenAndServe() for custom listeners
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
type tcpKeepAliveListener struct {
*net.TCPListener
}
func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
tc, err := ln.AcceptTCP()
if err != nil {
return
}
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(3 * time.Minute)
return tc, nil
}
func main() {
// Echo instance
e := echo.New()
// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Routes
e.GET("/", hello)
// make listener for echo
ln, err := net.Listen("tcp", ":https");
keepAliveListener := tcpKeepAliveListener{ln.(*net.TCPListener)
if err != nil {
println(err)
os.Exit(1)
}
// very very basic TLS setup
tlsconfig := tls.Config{}
tlsconfig.NextProtos = append(tlsconfig.NextProtos, "h2") // untested, but this *should* get http/2 support
tlsconfig.NextProtos = append(tlsconfig.NextProtos, "http/1.1")
tlsconfig.Certificates = make([]tls.Certificates, 1)
tlsconfig.Certificates[0], err = tls.LoadX509KeyPair(keys.certFile, keys.keyFile)
if err != nil {
println(err)
os.Exit(1)
}
tlsListener := tls.NewListener(keepAliveListener, tlsconfig)
config := engine.Config{
listener: tlsListener
}
engine := standard.WithConfig(config)
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, os.Kill)
recoverFromEcho = func() {
defer func() {
recover()
}()
echo.Run(engine)
}
go recoverFromEcho()
<-sigc
println("Got close signal, shutting down listen server")
tlsListener.Close()
// extra code to wait for connections to end goes here i guess
}
Version/commit
latest