优雅地关闭Gorilla服务器

huangapple go评论151阅读模式
英文:

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)捕获关闭请求。

  1. http.Server嵌入自定义结构中,以便稍后调用http Server.Shutdown
  2. 添加通道字段(shutdownReq),用于从API调用/shutdown传递关闭请求。
  3. gorilla/mux的路由器中注册包括/shutdown在内的HTTP处理程序,然后将路由器分配给http.Server.Handler
  4. 注册os.Interrupt/syscall.SIGINT, syscall.SIGTERM处理程序。
  5. 使用select捕获通过API调用或interrupt信号的关闭请求。
  6. 通过调用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).

  1. Embed http.Server into a custom struct, so we can call http Server.Shutdown later
  2. Add channel field (shutdownReq) for passing shutdown request from API call /shutdown
  3. Register http handlers including /shutdown in gorilla/mux's router, then assign the router to http.Server.Handler
  4. Register os.Interrupt/syscall.SIGINT, syscall.SIGTERM handler
  5. Use select to capture shutdown request through API call or interrupt signal
  6. Perform clean shutdown by calling Server.Shutdown

Below is the example code:

package main
import (
&quot;context&quot;
&quot;log&quot;
&quot;net/http&quot;
&quot;sync/atomic&quot;
&quot;syscall&quot;
&quot;time&quot;
&quot;os&quot;
&quot;os/signal&quot;
&quot;github.com/gorilla/mux&quot;
)
type myServer struct {
http.Server
shutdownReq chan bool
reqCount    uint32
}
func NewServer() *myServer {
//create server
s := &amp;myServer{
Server: http.Server{
Addr:         &quot;:8080&quot;,
ReadTimeout:  10 * time.Second,
WriteTimeout: 10 * time.Second,
},
shutdownReq: make(chan bool),
}
router := mux.NewRouter()
//register handlers
router.HandleFunc(&quot;/&quot;, s.RootHandler)
router.HandleFunc(&quot;/shutdown&quot;, 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 := &lt;-irqSig:
log.Printf(&quot;Shutdown request (signal: %v)&quot;, sig)
case sig := &lt;-s.shutdownReq:
log.Printf(&quot;Shutdown request (/shutdown %v)&quot;, sig)
}
log.Printf(&quot;Stoping http server ...&quot;)
//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(&quot;Shutdown request error: %v&quot;, err)
}
}
func (s *myServer) RootHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(&quot;Hello Gorilla MUX!\n&quot;))
}
func (s *myServer) ShutdownHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(&quot;Shutdown server&quot;))
//Do nothing if shutdown request already issued
//if s.reqCount == 0 then set to 1, return true otherwise false
if !atomic.CompareAndSwapUint32(&amp;s.reqCount, 0, 1) {
log.Printf(&quot;Shutdown through API call in progress...&quot;)
return
}
go func() {
s.shutdownReq &lt;- true
}()
}
func main() {
//Start the server
server := NewServer()
done := make(chan bool)
go func() {
err := server.ListenAndServe()
if err != nil {
log.Printf(&quot;Listen and serve: %v&quot;, err)
}
done &lt;- true
}()
//wait shutdown
server.WaitShutdown()
&lt;-done
log.Printf(&quot;DONE!&quot;)
}

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 (
&quot;context&quot;
&quot;fmt&quot;
&quot;net&quot;
&quot;net/http&quot;
&quot;sync&quot;
&quot;sync/atomic&quot;
&quot;testing&quot;
&quot;github.com/gorilla/mux&quot;
)
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 = &amp;myServer{
ctx: ctx,
srv: http.Server{
Addr: fmt.Sprintf(&quot;:%d&quot;, port),
},
}
// make the routes
router := mux.NewRouter()
router.HandleFunc(&quot;/&quot;, 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(&quot;tcp&quot;, addr)
if err != nil {
return
}
s.wg.Add(1)
go func() {
fmt.Println(&quot;Server started&quot;)
err = s.srv.Serve(ln)
if err != http.ErrServerClosed {
s.err = err
}
s.wg.Done()
}()
return
}
func (s *myServer) stop() error {
if atomic.CompareAndSwapUint32(&amp;s.stopping, 0, 1) {
s.srv.Shutdown(s.ctx)
fmt.Println(&quot;Server stopped&quot;)
}
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(&quot;http://localhost:8000&quot;)
if err != nil {
t.Fatal(err)
}
err = s.stop()
if err != nil {
t.Fatal(err)
}
}

huangapple
  • 本文由 发表于 2017年4月26日 18:39:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/43631854.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定