Skip to content

echo.Engine's should have a .Close() method #655

Closed
@gordallott

Description

@gordallott

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions