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.
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")
}
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
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
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)
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.
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!
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)
}
}
}