Как остановить http.ListenAndServe()
Я использую библиотеку Mux из Gorilla Web Toolkit вместе со встроенным http-сервером Go.
Проблема в том, что в моем приложении HTTP-сервер является только одним компонентом, и он должен останавливаться и запускаться по моему усмотрению.
Когда я звоню http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router)
это блокирует, и я не могу остановить работу сервера.
Я знаю, что это было проблемой в прошлом, это все еще так? Есть ли новые решения, пожалуйста? Благодарю.
11 ответов
Что касается постепенного выключения (введено в Go 1.8), приведу более конкретный пример:
package main
import (
"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() {
if err := srv.ListenAndServe(); err != nil {
// cannot panic, because this probably is an intentional close
log.Printf("Httpserver: ListenAndServe() error: %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 instead of nil as a https://golang.org/pkg/context/
if err := srv.Shutdown(nil); err != nil {
panic(err) // failure/timeout shutting down the server gracefully
}
log.Printf("main: done. exiting")
}
Как уже упоминалось в yo.ian.g
ответ. Go 1.8 включил эту функциональность в стандартную библиотеку.
Минимальный пример для для 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
}
Оригинальный ответ - Pre Go 1.8:
Опираясь на ответ Uvelichitel.
Вы можете создать свою собственную версию ListenAndServe
который возвращает io.Closer
и не блокирует.
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
}
Полный код доступен здесь.
HTTP-сервер закроется с ошибкой accept tcp [::]:8080: use of closed network connection
Go 1.8 будет включать в себя изящное и принудительное отключение, доступное через Server::Shutdown(context.Context)
а также Server::Close()
соответственно.
go func() {
httpError := srv.ListenAndServe(address, handler)
if httpError != nil {
log.Println("While serving HTTP: ", httpError)
}
}()
srv.Shutdown(context)
Соответствующий коммит можно найти здесь
Вы можете построить net.Listener
l, err := net.Listen("tcp", fmt.Sprintf(":%d", service.Port()))
if err != nil {
log.Fatal(err)
}
что вы можете Close()
go func(){
//...
l.Close()
}()
а также http.Serve()
в теме
http.Serve(l, service.router)
Поскольку ни один из предыдущих ответов не говорит, почему вы не можете сделать это, если используете http.ListenAndServe(), я вошел в исходный код http v1.8 и вот что он говорит:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
Как видите, функция http.ListenAndServe не возвращает серверную переменную. Это означает, что вы не можете попасть на "сервер", чтобы использовать команду "Завершение работы". Следовательно, вам нужно создать свой собственный "серверный" экземпляр, а не использовать эту функцию для постепенного завершения работы.
Вы можете закрыть сервер, закрыв его контекст.
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
}
И всякий раз, когда вы готовы закрыть его, звоните:
ctx, closeServer := context.WithCancel(context.Background())
err := ServeReqs(ctx, etc)
closeServer()
Я придумал тот же вопрос, поэтому решил записать все это в учебник по Github. Ознакомьтесь с полным исходным кодом, интеграционным тестом и способами реализации уровня SSL для защиты!
Если кто-то хотел бы внести свой вклад в это, сделать его еще лучше, написать больше тестов, не стесняйтесь представить PR!
Вклад и обмен знаниями приветствуются!
Воспроизводимый пример, когда вы не хотите, чтобы ваш главный сервер запускался в отдельной горутине:
main.go:
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"sync"
"time"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
// wait for 10 seconds before sending OK
time.Sleep(10 * time.Second)
_, _ = w.Write([]byte("OK\n"))
})
server := &http.Server{Addr: ":3333", Handler: nil}
// Creating a waiting group that waits until the graceful shutdown procedure is done
var wg sync.WaitGroup
wg.Add(1)
// This goroutine is running in parallels to the main one
go func() {
// creating a channel to listen for signals, like SIGINT
stop := make(chan os.Signal, 1)
// subscribing to interruption signals
signal.Notify(stop, os.Interrupt)
// this blocks until the signal is received
<-stop
// initiating the shutdown
err := server.Shutdown(context.Background())
// can't do much here except for logging any errors
if err != nil {
log.Printf("error during shutdown: %v\n", err)
}
// notifying the main goroutine that we are done
wg.Done()
}()
log.Println("listening on port 3333...")
err := server.ListenAndServe()
if err == http.ErrServerClosed { // graceful shutdown
log.Println("commencing server shutdown...")
wg.Wait()
log.Println("server was gracefully shut down.")
} else if err != nil {
log.Printf("server error: %v\n", err)
}
}
Откройте два терминала. При первом запуске приложение, при втором запуске
curl localhost:3333
, затем быстро переключитесь на первый и попробуйте остановить приложение с помощью
CTRL+C
Результат должен быть:
2021/03/12 13:39:49 listening on port 3333...
2021/03/12 13:39:50 user initiated a request
2021/03/12 13:39:54 commencing server shutdown...
2021/03/12 13:40:00 user request is fulfilled
2021/03/12 13:40:01 server was gracefully shut down.
Это можно решить, используя context.Context
используя net.ListenConfig
. В моем случае я не хотел использоватьsync.WaitGroup
или http.Server
с Shutdown()
звоните, а вместо этого полагайтесь на context.Context
(который был закрыт по сигналу).
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
}
ребята, как насчет этого
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)
}
}
}
Существует модуль, который реализует (изящную) остановку HTTP-серверов Go:https://github.com/pseidemann/finish .
Это устраняет необходимость в шаблоне, представленном в других ответах.
То, что я сделал для таких случаев, когда приложение является просто сервером и не выполняет никакой другой функции, это установил http.HandleFunc
для картины как /shutdown
, Что-то вроде
http.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
if <credentials check passes> {
fmt.Fprint(w, "Goodbye!\n")
os.Exit(0)
}
})
Не требует 1.8. Но если доступен 1.8, тогда это решение может быть встроено вместо os.Exit(0)
позвоните, если желательно, я считаю.