Изящно завершение работы сервера Gorilla
Я собираю сервер на ходу, используя мультиплексорную библиотеку горилл, найденную по https://github.com/gorilla/mux. Проблема в том, что я хочу, чтобы он корректно завершал работу, когда я использую Ctrl+C, или когда есть определенный вызов API, например, "/shutdown".
Я уже знаю, что в Go 1.8 изящное отключение уже реализовано. Но как совместить его с мультиплексором гориллы? Кроме того, как совместить его с сигналом SIGINT?
Может кто-нибудь показать мне, как это сделать?
2 ответа
Канал может быть использован для захвата запроса на отключение через вызов API (/shutdown
) или сигнал прерывания (Ctrl+C
).
- встраивать
http.Server
в пользовательскую структуру, поэтому мы можем вызвать http Server.Shutdown позже - Добавить поле канала (
shutdownReq
) для передачи запроса на отключение от вызова API/shutdown
- Зарегистрируйте обработчики http, включая
/shutdown
вgorilla/mux
маршрутизатор, затем назначьте маршрутизаторhttp.Server.Handler
- регистр
os.Interrupt/syscall.SIGINT, syscall.SIGTERM
обработчик - использование
select
захватить запрос на отключение через вызов API илиinterrupt
сигнал - Выполните чистое отключение, позвонив
Server.Shutdown
Ниже приведен пример кода:
package main
import (
"context"
"log"
"net/http"
"sync/atomic"
"syscall"
"time"
"os"
"os/signal"
"github.com/gorilla/mux"
)
type myServer struct {
http.Server
shutdownReq chan bool
reqCount uint32
}
func NewServer() *myServer {
//create server
s := &myServer{
Server: http.Server{
Addr: ":8080",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
},
shutdownReq: make(chan bool),
}
router := mux.NewRouter()
//register handlers
router.HandleFunc("/", s.RootHandler)
router.HandleFunc("/shutdown", s.ShutdownHandler)
//set http server handler
s.Handler = router
return s
}
func (s *myServer) WaitShutdown() {
irqSig := make(chan os.Signal, 1)
signal.Notify(irqSig, syscall.SIGINT, syscall.SIGTERM)
//Wait interrupt or shutdown request through /shutdown
select {
case sig := <-irqSig:
log.Printf("Shutdown request (signal: %v)", sig)
case sig := <-s.shutdownReq:
log.Printf("Shutdown request (/shutdown %v)", sig)
}
log.Printf("Stoping http server ...")
//Create shutdown context with 10 second timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
//shutdown the server
err := s.Shutdown(ctx)
if err != nil {
log.Printf("Shutdown request error: %v", err)
}
}
func (s *myServer) RootHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello Gorilla MUX!\n"))
}
func (s *myServer) ShutdownHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Shutdown server"))
//Do nothing if shutdown request already issued
//if s.reqCount == 0 then set to 1, return true otherwise false
if !atomic.CompareAndSwapUint32(&s.reqCount, 0, 1) {
log.Printf("Shutdown through API call in progress...")
return
}
go func() {
s.shutdownReq <- true
}()
}
func main() {
//Start the server
server := NewServer()
done := make(chan bool)
go func() {
err := server.ListenAndServe()
if err != nil {
log.Printf("Listen and serve: %v", err)
}
done <- true
}()
//wait shutdown
server.WaitShutdown()
<-done
log.Printf("DONE!")
}
Примечание. Пожалуйста, просмотрите эту проблему, которая связана с корректным завершением работы.
Вариант исходного вопроса — как завершить работу в модульном тесте? Наблюдение за сигналом ОС не требуется, таймауты не нужны, поэтому синхронизировать меньше. Кроме того, есть еще одна проблема: модульному тесту обычно приходится ждать, пока сервер начнет прослушивать, прежде чем выполнять тест.
Вот упрощенный ответ @putu для этой цели.
package main
import (
"context"
"fmt"
"net"
"net/http"
"sync"
"sync/atomic"
"testing"
"github.com/gorilla/mux"
)
type (
myServer struct {
ctx context.Context
srv http.Server
stopping uint32
err error
wg sync.WaitGroup
}
)
func newServer(ctx context.Context, port int) (s *myServer) {
s = &myServer{
ctx: ctx,
srv: http.Server{
Addr: fmt.Sprintf(":%d", port),
},
}
// make the routes
router := mux.NewRouter()
router.HandleFunc("/", s.getRoot)
s.srv.Handler = router
return
}
func (s *myServer) getRoot(w http.ResponseWriter, r *http.Request) {
// example route
w.WriteHeader(http.StatusOK)
}
func (s *myServer) start() (err error) {
addr := s.srv.Addr
ln, err := net.Listen("tcp", addr)
if err != nil {
return
}
s.wg.Add(1)
go func() {
fmt.Println("Server started")
err = s.srv.Serve(ln)
if err != http.ErrServerClosed {
s.err = err
}
s.wg.Done()
}()
return
}
func (s *myServer) stop() error {
if atomic.CompareAndSwapUint32(&s.stopping, 0, 1) {
s.srv.Shutdown(s.ctx)
fmt.Println("Server stopped")
}
s.wg.Wait()
return s.err
}
func TestMockServer(t *testing.T) {
s := newServer(context.Background(), 8000)
err := s.start()
if err != nil {
t.Fatal(err)
}
// ...do a test that makes use of the mock server...
_, err = http.Get("http://localhost:8000")
if err != nil {
t.Fatal(err)
}
err = s.stop()
if err != nil {
t.Fatal(err)
}
}