web-dev-qa-db-fra.com

Comment arrêter http.ListenAndServe ()

J'utilise la bibliothèque Mux de Gorilla Web Toolkit avec le serveur Go http fourni.

Le problème est que dans mon application, le serveur HTTP n’est qu’un composant et qu’il est nécessaire de s’arrêter et de démarrer à ma discrétion.

Lorsque j'appelle http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router), il se bloque et je n'arrive pas à empêcher le serveur de fonctionner.

Je sais que cela a été un problème dans le passé, est-ce toujours le cas? Y at-il de nouvelles solutions s'il vous plaît? Merci.

52
jim

En ce qui concerne l'arrêt en douceur (introduit dans Go 1.8), un exemple un peu plus concret:

package main

import (
    "context"
    "log"
    "io"
    "time"
    "net/http"
)

func startHttpServer() *http.Server {
    srv := &http.Server{Addr: ":8080"}

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "hello world\n")
    })

    go func() {
        // returns ErrServerClosed on graceful close
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            // NOTE: there is a chance that next line won't have time to run,
            // as main() doesn't wait for this goroutine to stop. don't use
            // code with race conditions like these for production. see post
            // comments below on more discussion on how to handle this.
            log.Fatalf("ListenAndServe(): %s", err)
        }
    }()

    // returning reference so caller can call Shutdown()
    return srv
}

func main() {
    log.Printf("main: starting HTTP server")

    srv := startHttpServer()

    log.Printf("main: serving for 10 seconds")

    time.Sleep(10 * time.Second)

    log.Printf("main: stopping HTTP server")

    // now close the server gracefully ("shutdown")
    // timeout could be given with a proper context (in real world you shouldn't use TODO() ).
    if err := srv.Shutdown(context.TODO()); err != nil {
        panic(err) // failure/timeout shutting down the server gracefully
    }

    log.Printf("main: done. exiting")
}
62
joonas.fi

Comme mentionné dans la réponse de yo.ian.g. Go 1.8 a inclus cette fonctionnalité dans la bibliothèque standard.

Exemple minimal pour pour Go 1.8+:

    server := &http.Server{Addr: ":8080", Handler: handler}

    go func() {
        if err := server.ListenAndServe(); err != nil {
            // handle err
        }
    }

    // Setting up signal capturing
    stop := make(chan os.Signal, 1)
    signal.Notify(stop, os.Interrupt)

    // Waiting for SIGINT (pkill -2)
    <-stop

    ctx, _ := context.WithTimeout(context.Background(), 5*time.Second)
    if err := server.Shutdown(ctx); err != nil {
        // handle err
    }

Réponse originale - Pre Go 1.8:

S'appuyant sur la réponse de Uvelichitel

Vous pouvez créer votre propre version de ListenAndServe qui renvoie un io.Closer et ne bloque pas.

func ListenAndServeWithClose(addr string, handler http.Handler) (io.Closer,error) {

    var (
        listener  net.Listener
        srvCloser io.Closer
        err       error
    )

    srv := &http.Server{Addr: addr, Handler: handler}

    if addr == "" {
        addr = ":http"
    }

    listener, err = net.Listen("tcp", addr)
    if err != nil {
        return nil, err
    }

    go func() {
        err := srv.Serve(tcpKeepAliveListener{listener.(*net.TCPListener)})
        if err != nil {
            log.Println("HTTP Server Error - ", err)
        }
    }()

    srvCloser = listener
    return srvCloser, nil
}

Code complet disponible ici .

Le serveur HTTP se fermera avec l'erreur accept tcp [::]:8080: use of closed network connection

22
John S Perayil

Go 1.8 comprendra un arrêt progressif et gracieux, disponible via Server::Shutdown(context.Context) et Server::Close() respectivement.

go func() {
    httpError := srv.ListenAndServe(address, handler)
    if httpError != nil {
        log.Println("While serving HTTP: ", httpError)
    }
}()

srv.Shutdown(context)

Le commit correspondant peut être trouvé ici

20
yo.ian.g

Vous pouvez construire net.Listener

l, err := net.Listen("tcp", fmt.Sprintf(":%d", service.Port()))
if err != nil {
    log.Fatal(err)
}

que vous pouvez Close()

go func(){
    //...
    l.Close()
}()

et http.Serve() dessus

http.Serve(l, service.router)
19
Uvelichitel

Comme aucune des réponses précédentes ne dit pourquoi vous ne pouvez pas le faire si vous utilisez http.ListenAndServe (), je suis entré dans le code source http v1.8 et voici ce qu'il dit:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

Comme vous pouvez le constater, la fonction http.ListenAndServe ne renvoie pas la variable serveur. Cela signifie que vous ne pouvez pas accéder au «serveur» pour utiliser la commande d'arrêt. Par conséquent, vous devez créer votre propre instance de "serveur" au lieu d'utiliser cette fonction pour la mise en œuvre de l'arrêt progressif.

6
juz

Vous pouvez fermer le serveur en fermant son contexte.

type ServeReqs func(ctx context.Context, cfg Config, deps ReqHandlersDependencies) error

var ServeReqsImpl = func(ctx context.Context, cfg Config, deps ReqHandlersDependencies) error {
    http.Handle(pingRoute, decorateHttpRes(pingHandlerImpl(deps.pingRouteResponseMessage), addJsonHeader()))

    server := &http.Server{Addr: fmt.Sprintf(":%d", cfg.port), Handler: nil}

    go func() {
        <-ctx.Done()
        fmt.Println("Shutting down the HTTP server...")
        server.Shutdown(ctx)
    }()

    err := server.ListenAndServeTLS(
        cfg.certificatePemFilePath,
        cfg.certificatePemPrivKeyFilePath,
    )

    // Shutting down the server is not something bad ffs Go...
    if err == http.ErrServerClosed {
        return nil
    }

    return err
}

Et chaque fois que vous êtes prêt à le fermer, appelez le:

ctx, closeServer := context.WithCancel(context.Background())
err := ServeReqs(ctx, etc)
closeServer()

Je pensais à la même question alors j'ai décidé de tout écrire dans un tutoriel Github . Checkout le code source complet , test d'intégration et mise en oeuvre d'une couche SSL pour la protection!

Si quelqu'un souhaite y contribuer, le rendre encore meilleur, écrire plus de tests, n'hésitez pas à soumettre un PR! 

Les contributions et le partage des connaissances sont plus que bienvenus!

2
BlocksByLukas

les gars que diriez-vous de cela

 package gracefull_shutdown_server

    import (
        "net/http"
        "log"
        "os"
        "os/signal"
        "time"
        "context"
        "fmt"
    )



    func startHttpServer() *http.Server{
        mux:=http.NewServeMux()
        mux.HandleFunc("/",defaultRoute)
        srv:=&http.Server{
            Addr:":8080",
            Handler:mux,
        }
        go func() {
            if err:=srv.ListenAndServe();err != http.ErrServerClosed{
                log.Fatalf("ListenAndServe(): %s",err)
            }

        }()
        return srv
    }

    func defaultRoute(w http.ResponseWriter, r *http.Request){
        time.Sleep(time.Second*30)
        w.Write([]byte("it's working"))
    }
    func MainStartHttpServer()  {
        srv:=startHttpServer()
        stop:= make(chan os.Signal)
        signal.Notify(stop,os.Interrupt)
        select {
        case <-stop:
            fmt.Println("server going to shut down")
            ctx,cancel:=context.WithTimeout(context.Background(),time.Second*5)
            defer cancel()
            err:=srv.Shutdown(ctx)
            if err!=nil{
                fmt.Println(err)
            }
        }
    }
0
Damitha Dayananda