优雅地关闭大猩猩服务器

问题描述 投票:0回答:2

我正在使用 https://github.com/gorilla/mux 中的大猩猩多路复用器库构建一个服务器。 问题是,我希望它在我使用 Ctrl+C 时正常关闭,或者当有特定的 API 调用时,例如“/shutdown”。

我已经知道在 Go 1.8 中,已经实现了优雅关机。但是如何将它与大猩猩多路复用器结合起来呢?另外,如何将它与 SIGINT 信号结合起来?

谁能告诉我怎么做?

go webserver channel sigint gorilla
2个回答
9
投票

Channel可用于通过API调用(

/shutdown
)或中断信号(
Ctrl+C
)捕获关闭请求。

  1. http.Server
    嵌入到自定义结构中,这样我们就可以稍后调用http Server.Shutdown
  2. 添加频道字段(
    shutdownReq
    )用于传递来自API调用的关闭请求
    /shutdown
  3. /shutdown
    的路由器中注册包括
    gorilla/mux
    在内的http处理程序,然后将路由器分配给
    http.Server.Handler
  4. 注册
    os.Interrupt/syscall.SIGINT, syscall.SIGTERM
    处理程序
  5. 使用
    select
    通过API调用或
    interrupt
    信号捕获关机请求
  6. 通过调用
    Server.Shutdown
  7. 执行干净关机

下面是示例代码:

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!")
}

注意:请关注本期 与正常关机有关


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),
        },
    }

    // 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)
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.