英文:
Gracefully Shutdown Gorilla Server
问题
我正在使用gorilla multiplexer库(https://github.com/gorilla/mux)在Go语言中构建一个服务器。
问题是,我希望在使用Ctrl+C时或特定的API调用(例如"/shutdown")时,服务器能够优雅地关闭。
我已经知道在Go 1.8中已经实现了优雅关闭。但是如何将其与gorilla multiplexer结合起来?还有,如何将其与SIGINT信号结合起来?
有人可以向我展示如何做吗?
英文:
I'm building a server in go using gorilla multiplexer library found in https://github.com/gorilla/mux.
The problem is, I want it to gracefully shutdown when I'm using Ctrl+C, or when there is a specific API call, for example "/shutdown".
I already know that in Go 1.8, graceful shutdown is already implemented. But how to combine it with gorilla multiplexer? Also, how to combine it with SIGINT signal?
Can anyone show me how to do it?
答案1
得分: 9
通道可以用于通过API调用(/shutdown
)或中断信号(Ctrl+C
)捕获关闭请求。
- 将
http.Server
嵌入自定义结构中,以便稍后调用http Server.Shutdown。 - 添加通道字段(
shutdownReq
),用于从API调用/shutdown
传递关闭请求。 - 在
gorilla/mux
的路由器中注册包括/shutdown
在内的HTTP处理程序,然后将路由器分配给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 {
// 创建服务器
s := &myServer{
Server: http.Server{
Addr: ":8080",
ReadTimeout: 10 * time.Second,
WriteTimeout: 10 * time.Second,
},
shutdownReq: make(chan bool),
}
router := mux.NewRouter()
// 注册处理程序
router.HandleFunc("/", s.RootHandler)
router.HandleFunc("/shutdown", s.ShutdownHandler)
// 设置HTTP服务器处理程序
s.Handler = router
return s
}
func (s *myServer) WaitShutdown() {
irqSig := make(chan os.Signal, 1)
signal.Notify(irqSig, syscall.SIGINT, syscall.SIGTERM)
// 等待中断或通过/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("Stopping http server ...")
// 创建带有10秒超时的关闭上下文
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// 关闭服务器
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"))
// 如果已经发出关闭请求,则不执行任何操作
// 如果s.reqCount为0,则设置为1,返回true;否则返回false
if !atomic.CompareAndSwapUint32(&s.reqCount, 0, 1) {
log.Printf("Shutdown through API call in progress...")
return
}
go func() {
s.shutdownReq <- true
}()
}
func main() {
// 启动服务器
server := NewServer()
done := make(chan bool)
go func() {
err := server.ListenAndServe()
if err != nil {
log.Printf("Listen and serve: %v", err)
}
done <- true
}()
// 等待关闭
server.WaitShutdown()
<-done
log.Printf("DONE!")
}
注意:请关注与优雅关闭相关的此问题。
英文:
Channel can be used to capture shutdown request through API call (/shutdown
) or interrupt signal (Ctrl+C
).
- Embed
http.Server
into a custom struct, so we can call http Server.Shutdown later - Add channel field (
shutdownReq
) for passing shutdown request from API call/shutdown
- Register http handlers including
/shutdown
ingorilla/mux
's router, then assign the router tohttp.Server.Handler
- Register
os.Interrupt/syscall.SIGINT, syscall.SIGTERM
handler - Use
select
to capture shutdown request through API call orinterrupt
signal - Perform clean shutdown by calling
Server.Shutdown
Below is the example code:
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!")
}
Note: Please watch this issue which is related to gracefull shutdown.
答案2
得分: 0
一种变体的问题 - 如何在单元测试中关闭服务器?不需要监听操作系统信号,也不需要超时,因此同步的内容更少。另外,还有一个问题 - 单元测试通常需要等待服务器开始监听后才能进行测试。
这是@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),
},
}
// 创建路由
router := mux.NewRouter()
router.HandleFunc("/", s.getRoot)
s.srv.Handler = router
return
}
func (s *myServer) getRoot(w http.ResponseWriter, r *http.Request) {
// 示例路由
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("服务器已启动")
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("服务器已停止")
}
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)
}
// ...进行使用模拟服务器的测试...
_, err = http.Get("http://localhost:8000")
if err != nil {
t.Fatal(err)
}
err = s.stop()
if err != nil {
t.Fatal(err)
}
}
希望对你有帮助!
英文:
A variation of the original question - how to shut down in a unit test? Watching for an OS signal is not needed, timeouts aren't needed, and so there's less to synchronize. Also, there's another issue - a unit test usually has to wait for the server to start listening before doing its test.
Here's @putu's answer simplified for this purpose.
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)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论