我正在使用 Gorilla Web Toolkit 中的 Mux 库以及捆绑的 Go http 服务器。
问题是,在我的应用程序中,HTTP 服务器只是一个组件,需要我自行决定停止和启动。
当我调用http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router)它时,我似乎无法阻止服务器运行。
http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router)
我知道这在过去一直是个问题,现在仍然如此吗?有没有新的解决方案?
关闭(在 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") }
如在yo.ian.g回答中提到的。Go 1.8 在标准库中包含了这个功能。
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>
kill -2 <pid>
原始答案 - Pre Go 1.8 :
基于U回答。
您可以创建自己的版本,ListenAndServe该版本返回一个io.Closer并且不会阻止。
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
accept tcp [::]:8080: use of closed network connection