Estoy usando la biblioteca Mux de Gorilla Web Toolkit junto con el servidor http de Go incluido.
El problema es que en mi aplicación, el servidor HTTP es solo un componente y debe detenerse y comenzar a mi discreción.
Cuando lo llamo http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router)
bloquea y parece que no puedo detener la ejecución del servidor.
Soy consciente de que esto ha sido un problema en el pasado, ¿sigue siendo así? ¿Hay nuevas soluciones?
nil
asrv.Shutdown
conseguirpanic: runtime error: invalid memory address or nil pointer dereference
. Encontext.Todo()
cambio, pasar funciona.Como se menciona en
yo.ian.g
la respuesta de. Go 1.8 ha incluido esta funcionalidad en la biblioteca estándar.Ejemplo mínimo para para
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, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := server.Shutdown(ctx); err != nil { // handle err } // Wait for ListenAndServe goroutine to close.
Respuesta original - Pre Go 1.8:
Sobre la base de la respuesta de Uvelichitel .
Puede crear su propia versión de la
ListenAndServe
que devuelve unio.Closer
y no bloquea.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 }
Código completo disponible aquí .
El servidor HTTP se cerrará con el error
accept tcp [::]:8080: use of closed network connection
fuente
Go 1.8 incluirá un cierre elegante y contundente, disponible a través de
Server::Shutdown(context.Context)
yServer::Close()
respectivamente.go func() { httpError := srv.ListenAndServe(address, handler) if httpError != nil { log.Println("While serving HTTP: ", httpError) } }() srv.Shutdown(context)
El compromiso relevante se puede encontrar aquí
fuente
go func() { X() }()
seguido deY()
hace la suposición falsa para el lector de queX()
se ejecutará antesY()
. Los grupos de espera, etc., garantizan que los errores de sincronización como este no te muerdan cuando menos lo esperas.Puedes construir
net.Listener
l, err := net.Listen("tcp", fmt.Sprintf(":%d", service.Port())) if err != nil { log.Fatal(err) }
que puedes
Close()
go func(){ //... l.Close() }()
y
http.Serve()
en esofuente
http.ListenAndServe
por razones específicas. Así es como uso la biblioteca GWT MUX, no estoy seguro de cómo usar net.listen para eso ..Dado que ninguna de las respuestas anteriores dice por qué no puede hacerlo si usa http.ListenAndServe (), entré en el código fuente http v1.8 y esto es lo que dice:
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
Como puede ver, la función http.ListenAndServe no devuelve la variable del servidor. Esto significa que no puede acceder al 'servidor' para usar el comando de apagado. Por lo tanto, debe crear su propia instancia de 'servidor' en lugar de usar esta función para que se implemente el cierre ordenado.
fuente
Puede cerrar el servidor cerrando su contexto.
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 }
Y cuando esté listo para cerrarlo, llame a:
fuente
ctx
toserver.Shutdown
es incorrecto. El contexto ya está cancelado, por lo que no se cerrará limpiamente. Bien podría haber pedidoserver.Close
un cierre impuro. (Para un apagado limpio, este código debería ser reelaborado extensamente.Es posible resolver esto
context.Context
usando anet.ListenConfig
. En mi caso, yo no quería utilizar unasync.WaitGroup
ohttp.Server
'sShutdown()
llamada, y en lugar de confiar en unacontext.Context
(que estaba cerrada con una señal).import ( "context" "http" "net" "net/http/pprof" ) func myListen(ctx context.Context, cancel context.CancelFunc) error { lc := net.ListenConfig{} ln, err := lc.Listen(ctx, "tcp4", "127.0.0.1:6060") if err != nil { // wrap the err or log why the listen failed return err } mux := http.NewServeMux() mux.Handle("/debug/pprof/", pprof.Index) mux.Handle("/debug/pprof/cmdline", pprof.CmdLine) mux.Handle("/debug/pprof/profile", pprof.Profile) mux.Handle("/debug/pprof/symbol", pprof.Symbol) mux.Handle("/debug/pprof/trace", pprof.Trace) go func() { if err := http.Serve(l, mux); err != nil { cancel() // log why we shut down the context return err } }() // If you want something semi-synchronous, sleep here for a fraction of a second return nil }
fuente
Lo que he hecho para esos casos en los que la aplicación es solo el servidor y no realiza ninguna otra función es instalar y
http.HandleFunc
para un patrón como/shutdown
. Algo comohttp.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) { if <credentials check passes> { // - Turn on mechanism to reject incoming requests. // - Block until "in-flight" requests complete. // - Release resources, both internal and external. // - Perform all other cleanup procedures thought necessary // for this to be called a "graceful shutdown". fmt.Fprint(w, "Goodbye!\n") os.Exit(0) } })
No requiere 1.8. Pero si 1.8 está disponible, entonces esa solución puede integrarse aquí en lugar de la
os.Exit(0)
llamada si es deseable, creo.El código para realizar todo ese trabajo de limpieza se deja como ejercicio para el lector.
Crédito adicional si puede decir dónde podría colocarse el código de limpieza de manera más razonable, ya que no recomendaría hacerlo aquí, y cómo este golpe de punto final debería causar la invocación de ese código.
Más crédito adicional si puede decir dónde se
os.exit(0)
ubicaría de manera más razonable esa llamada (o cualquier salida de proceso que elija usar), que se proporciona aquí solo con fines ilustrativos.Aún más crédito adicional si puede explicar por qué este mecanismo de señalización del proceso del servidor HTTP debe considerarse por encima de todos los demás mecanismos que se consideran viables en este caso.
fuente