如何停止http.ListenAndServe()

时间:2016-09-04 18:12:02

标签: go

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

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

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

我知道这在过去一直是个问题,是不是仍然如此?请问有什么新的解决方案吗?感谢。

10 个答案:

答案 0 :(得分:66)

关于正常关闭(在Go 1.8中引入),更具体的例子:

package main

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

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

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

    go func() {
        // returns ErrServerClosed on graceful close
        if err := srv.ListenAndServe(); err != http.ErrServerClosed {
            // NOTE: there is a chance that next line won't have time to run,
            // as main() doesn't wait for this goroutine to stop. don't use
            // code with race conditions like these for production. see post
            // comments below on more discussion on how to handle this.
            log.Fatalf("ListenAndServe(): %s", err)
        }
    }()

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

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

    srv := startHttpServer()

    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
    }

    log.Printf("main: done. exiting")
}

答案 1 :(得分:26)

正如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 (pkill -2)
    <-stop

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

原始答案 - Pre Go 1.8:

Uvelichitel's答案为基础。

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

完整代码here

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

答案 2 :(得分:20)

Go 1.8将包括优雅且强制关机,分别通过Server::Shutdown(context.Context)Server::Close()提供。

go func() {
    httpError := srv.ListenAndServe(address, handler)
    if httpError != nil {
        log.Println("While serving HTTP: ", httpError)
    }
}()

srv.Shutdown(context)

可以找到相关提交here

答案 3 :(得分:19)

您可以构建net.Listener

l, err := net.Listen("tcp", fmt.Sprintf(":%d", service.Port()))
if err != nil {
    log.Fatal(err)
}

你可以Close()

go func(){
    //...
    l.Close()
}()

http.Serve()就可以了

http.Serve(l, service.router)

答案 4 :(得分:9)

由于以前的答案都没有说明为什么你不能这样做,如果你使用http.ListenAndServe(),我进入了v1.8 http源代码,这就是它所说的:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

如您所见,http.ListenAndServe函数不返回服务器变量。这意味着您无法使用“服务器”来使用“关闭”命令。因此,您需要创建自己的“服务器”实例,而不是使用此函数来实现正常关闭。

答案 5 :(得分:2)

您可以通过关闭服务器上下文来关闭服务器。

type ServeReqs func(ctx context.Context, cfg Config, deps ReqHandlersDependencies) error

var ServeReqsImpl = func(ctx context.Context, cfg Config, deps ReqHandlersDependencies) error {
    http.Handle(pingRoute, decorateHttpRes(pingHandlerImpl(deps.pingRouteResponseMessage), addJsonHeader()))

    server := &http.Server{Addr: fmt.Sprintf(":%d", cfg.port), Handler: nil}

    go func() {
        <-ctx.Done()
        fmt.Println("Shutting down the HTTP server...")
        server.Shutdown(ctx)
    }()

    err := server.ListenAndServeTLS(
        cfg.certificatePemFilePath,
        cfg.certificatePemPrivKeyFilePath,
    )

    // Shutting down the server is not something bad ffs Go...
    if err == http.ErrServerClosed {
        return nil
    }

    return err
}

每当您准备关闭它时,请致电:

ctx, closeServer := context.WithCancel(context.Background())
err := ServeReqs(ctx, etc)
closeServer()

我在想同样的问题,所以我决定将其全部记为Github tutorial。结帐the full source code,集成测试以及如何实施SSL层进行保护!

如果有人想为此做出贡献,甚至做得更好,编写更多测试,请随时提交PR!

贡献和知识共享非常受欢迎!

答案 6 :(得分:0)

请问这件事

ws

答案 7 :(得分:0)

可以使用net.ListenConfig使用context.Context解决此问题。就我而言,我不想使用sync.WaitGrouphttp.Server的{​​{1}}调用,而是依靠Shutdown()(已通过信号关闭)

context.Context

答案 8 :(得分:0)

当您不希望主服务器在单独的 goroutine 中运行时的可重现示例:

main.go:

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "sync"
    "time"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
        // wait for 10 seconds before sending OK
        time.Sleep(10 * time.Second)
        _, _ = w.Write([]byte("OK\n"))
    })
    server := &http.Server{Addr: ":3333", Handler: nil}

    // Creating a waiting group that waits until the graceful shutdown procedure is done
    var wg sync.WaitGroup
    wg.Add(1)

    // This goroutine is running in parallels to the main one
    go func() {
        // creating a channel to listen for signals, like SIGINT
        stop := make(chan os.Signal, 1)
        // subscribing to interruption signals
        signal.Notify(stop, os.Interrupt)
        // this blocks until the signal is received
        <-stop
        // initiating the shutdown
        err := server.Shutdown(context.Background())
        // can't do much here except for logging any errors
        if err != nil {
            log.Printf("error during shutdown: %v\n", err)
        }
        // notifying the main goroutine that we are done
        wg.Done()
    }()

    log.Println("listening on port 3333...")
    err := server.ListenAndServe()
    if err == http.ErrServerClosed { // graceful shutdown
        log.Println("commencing server shutdown...")
        wg.Wait()
        log.Println("server was gracefully shut down.")
    } else if err != nil {
        log.Printf("server error: %v\n", err)
    }
}

打开两个终端。第一次运行应用程序,第二次运行curl localhost:3333,然后快速切换到第一个并尝试使用CTRL+C

停止应用程序

输出应该是:

2021/03/12 13:39:49 listening on port 3333...
2021/03/12 13:39:50 user initiated a request
2021/03/12 13:39:54 commencing server shutdown...
2021/03/12 13:40:00 user request is fulfilled
2021/03/12 13:40:01 server was gracefully shut down.

答案 9 :(得分:-3)

我为应用程序只是服务器而不执行其他功能的情况所做的是为/path/to/android/sdk这样的模式安装http.HandleFunc。像

这样的东西
/shutdown

它不需要1.8。但是,如果1.8可用,那么可以在此处嵌入该解决方案而不是http.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) { if <credentials check passes> { fmt.Fprint(w, "Goodbye!\n") os.Exit(0) } }) 调用,如果可取的话,我相信。