小编典典

如何停止 http.ListenAndServe()

go

我正在使用 Gorilla Web Toolkit 中的 Mux 库以及捆绑的 Go http 服务器。

问题是,在我的应用程序中,HTTP 服务器只是一个组件,需要我自行决定停止和启动。

当我调用http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router)它时,我似乎无法阻止服务器运行。

我知道这在过去一直是个问题,现在仍然如此吗?有没有新的解决方案?


阅读 611

收藏
2021-11-28

共2个答案

小编典典

关闭(在 Go 1.8 中引入),一个更具体的例子:

package main

import (
    "context"
    "io"
    "log"
    "net/http"
    "sync"
    "time"
)

func startHttpServer(wg *sync.WaitGroup) *http.Server {
    srv := &http.Server{Addr: ":8080"}

    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        io.WriteString(w, "hello world\n")
    })

    go func() {
        defer wg.Done() // let main know we are done cleaning up

        // always returns error. ErrServerClosed on graceful close
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            // unexpected error. port in use?
            log.Fatalf("ListenAndServe(): %v", err)
        }
    }()

    // returning reference so caller can call Shutdown()
    return srv
}

func main() {
    log.Printf("main: starting HTTP server")

    httpServerExitDone := &sync.WaitGroup{}

    httpServerExitDone.Add(1)
    srv := startHttpServer(httpServerExitDone)

    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 with a proper context
    // (in real world you shouldn't use TODO()).
    if err := srv.Shutdown(context.TODO()); err != nil {
        panic(err) // failure/timeout shutting down the server gracefully
    }

    // wait for goroutine started in startHttpServer() to stop
    httpServerExitDone.Wait()

    log.Printf("main: done. exiting")
}
2021-11-28
小编典典

如在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 (kill -2)
    <-stop

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    if err := server.Shutdown(ctx); err != nil {
        // handle err
    }

    // Wait for ListenAndServe goroutine to close.

您可以使用优雅地杀死服务器 kill -2 <pid>


原始答案 - Pre Go 1.8 :

基于U回答。

您可以创建自己的版本,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
}

完整代码。

package main

import (
    "io"
    "log"
    "net"
    "net/http"
    "time"
)

func main() {

    redirHandler := http.RedirectHandler("http://www.example.com", http.StatusTemporaryRedirect)

    srvCLoser, err := ListenAndServeWithClose(":8080", redirHandler)
    if err != nil {
        log.Fatalln("ListenAndServeWithClose Error - ", err)
    }

    // Do Stuff

    // Close HTTP Server
    err = srvCLoser.Close()
    if err != nil {
        log.Fatalln("Server Close Error - ", err)
    }

    log.Println("Server Closed")
}

func ListenAndServeWithClose(addr string, handler http.Handler) (sc io.Closer, err error) {

    var listener net.Listener

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

    return listener, nil
}

type tcpKeepAliveListener struct {
    *net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
    tc, err := ln.AcceptTCP()
    if err != nil {
        return
    }
    tc.SetKeepAlive(true)
    tc.SetKeepAlivePeriod(3 * time.Minute)
    return tc, nil
}

HTTP 服务器将关闭并显示错误 accept tcp [::]:8080: use of closed network connection

2021-11-28