How to stop http.ListenAndServe()

2019-01-08 07:46发布

I am using the Mux library from Gorilla Web Toolkit along with the bundled Go http server.

The problem is that in my application the HTTP server is only one component and it is required to stop and start at my discretion.

When I call http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router) it blocks and I cannot seem to stop the server from running.

I am aware this has been a problem in the past, is that still the case? Are there any new solutions please? Thanks.

标签: go
8条回答
趁早两清
2楼-- · 2019-01-08 08:20

You can close the server by closing its context.

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
}

And whenever you are ready to close it, call:

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

I was figuring the same question so I decided to write it all down to a Github tutorial. Checkout the full source code, integration test and how to implement an SSL layer for protection!

If anyone would like to contribute to it, make it even better, write more tests, feel free to submit a PR!

Contributions and knowledge sharing is more than welcome!

查看更多
啃猪蹄的小仙女
3楼-- · 2019-01-08 08:23

Go 1.8 will include graceful and forceful shutdown, available via Server::Shutdown(context.Context) and Server::Close() respectively.

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

srv.Shutdown(context)

The relevant commit can be found here

查看更多
啃猪蹄的小仙女
4楼-- · 2019-01-08 08:23

You can construct net.Listener

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

which you can Close()

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

and http.Serve() on it

http.Serve(l, service.router)
查看更多
在下西门庆
5楼-- · 2019-01-08 08:27

As mentioned in yo.ian.g's answer. Go 1.8 has included this functionality in the standard lib.

Minimal example for for 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
    }

Original Answer - Pre Go 1.8 :

Building on Uvelichitel's answer.

You can create your own version of ListenAndServe which returns an io.Closer and does not block.

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
}

Full code available here.

The HTTP Server will close with the error accept tcp [::]:8080: use of closed network connection

查看更多
唯我独甜
6楼-- · 2019-01-08 08:33

Since none of the previous answers say why you can't do it if you use http.ListenAndServe(), I went into the v1.8 http source code and here is what it says:

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

As you can see the http.ListenAndServe function does not return the server variable. This means you cannot get to 'server' to use the Shutdown command. Therefore, you need to create your own 'server' instance instead of using this function for the graceful shutdown to be implemented.

查看更多
一夜七次
7楼-- · 2019-01-08 08:34

Regarding graceful shutdown (introduced in Go 1.8), a bit more concrete example:

package main

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

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 instead of nil as a https://golang.org/pkg/context/
    if err := srv.Shutdown(nil); err != nil {
        panic(err) // failure/timeout shutting down the server gracefully
    }

    log.Printf("main: done. exiting")
}
查看更多
登录 后发表回答