Używam biblioteki Mux z Gorilla Web Toolkit wraz z dołączonym serwerem Go http.
Problem w tym, że w mojej aplikacji serwer HTTP jest tylko jednym komponentem i wymaga zatrzymania i uruchomienia według własnego uznania.
Kiedy nazywam http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router)
to blokowaniem i nie mogę zatrzymać działania serwera.
Wiem, że był to problem w przeszłości, czy nadal tak jest? Czy są jakieś nowe rozwiązania?
nil
sięsrv.Shutdown
dostaćpanic: runtime error: invalid memory address or nil pointer dereference
. Przechodząccontext.Todo()
zamiast działa.Jak wspomniano w
yo.ian.g
odpowiedzi. Go 1.8 zawiera tę funkcjonalność w standardowym lib.Minimalny przykład dla
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.
Oryginalna odpowiedź - Pre Go 1.8:
Opierając się na odpowiedzi Uvelichitel .
Możesz stworzyć własną wersję,
ListenAndServe
która zwracaio.Closer
i nie blokuje.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 }
Pełny kod dostępny tutaj .
Serwer HTTP zamknie się z błędem
accept tcp [::]:8080: use of closed network connection
źródło
Wersja 1.8 będzie zawierała bezpieczne i wymuszone zamykanie, dostępne odpowiednio za pośrednictwem
Server::Shutdown(context.Context)
iServer::Close()
.go func() { httpError := srv.ListenAndServe(address, handler) if httpError != nil { log.Println("While serving HTTP: ", httpError) } }() srv.Shutdown(context)
Odpowiednie zatwierdzenie można znaleźć tutaj
źródło
go func() { X() }()
po nim następujeY()
fałszywe założenie dla czytelnika, któreX()
zostanie wykonane wcześniejY()
. Grupy oczekujących itp. Zapewniają, że takie błędy czasowe nie gryzą Cię w najmniej oczekiwanym momencie!Możesz konstruować
net.Listener
l, err := net.Listen("tcp", fmt.Sprintf(":%d", service.Port())) if err != nil { log.Fatal(err) }
które możesz
Close()
go func(){ //... l.Close() }()
i
http.Serve()
na nimźródło
http.ListenAndServe
z konkretnych powodów. W ten sposób używam biblioteki GWT MUX, nie jestem pewien, jak używać do tego net.listen ..Ponieważ żadna z poprzednich odpowiedzi nie mówi, dlaczego nie możesz tego zrobić, jeśli używasz http.ListenAndServe (), przeszedłem do kodu źródłowego http v1.8 i oto, co mówi:
func ListenAndServe(addr string, handler Handler) error { server := &Server{Addr: addr, Handler: handler} return server.ListenAndServe() }
Jak widać, funkcja http.ListenAndServe nie zwraca zmiennej serwera. Oznacza to, że nie możesz dostać się do „serwera”, aby użyć polecenia Shutdown. Dlatego zamiast używać tej funkcji, aby zaimplementować wdzięczne zamknięcie systemu, musisz utworzyć własną instancję „serwera”.
źródło
Możesz zamknąć serwer, zamykając jego kontekst.
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 }
A kiedy będziesz gotowy, aby go zamknąć, zadzwoń:
źródło
ctx
doserver.Shutdown
jest nieprawidłowe. Kontekst jest już anulowany, więc nie zostanie zamknięty w czysty sposób. Mogłeś wezwaćserver.Close
do nieczystego zamknięcia. (Aby całkowicie zamknąć system, kod ten musiałby zostać gruntownie przerobiony.Można to rozwiązać za
context.Context
pomocą plikunet.ListenConfig
. W moim przypadku, nie chcę użyćsync.WaitGroup
lubhttp.Server
„sShutdown()
połączenia, a zamiast polegać nacontext.Context
(który został zamknięty z sygnałem).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 }
źródło
To, co zrobiłem w takich przypadkach, gdy aplikacja jest tylko serwerem i nie wykonuje żadnej innej funkcji, to zainstalowanie
http.HandleFunc
wzorca takiego jak/shutdown
. Coś jakhttp.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) } })
Nie wymaga 1.8. Ale jeśli jest dostępna wersja 1.8, to
os.Exit(0)
moim zdaniem to rozwiązanie można osadzić tutaj zamiast wywołania, jeśli jest to pożądane.Kod do wykonania wszystkich prac porządkowych pozostawiamy jako ćwiczenie dla czytelnika.
Dodatkowy kredyt, jeśli możesz powiedzieć, gdzie ten kod czyszczący może być najbardziej racjonalnie umieszczony, ponieważ nie polecałbym tego robić w tym miejscu i jak to trafienie w punkt końcowy powinno spowodować wywołanie tego kodu.
Więcej dodatkowych punktów, jeśli możesz powiedzieć, gdzie to
os.exit(0)
wywołanie (lub jakiekolwiek wyjście z procesu, którego zdecydujesz się użyć), podane tutaj tylko w celach ilustracyjnych, byłoby najbardziej uzasadnione.Jeszcze większy zasługa, jeśli potrafisz wyjaśnić, dlaczego ten mechanizm sygnalizacji procesu serwera HTTP powinien być wzięty pod uwagę ponad wszystkie inne tego typu mechanizmy, które są w tym przypadku możliwe.
źródło