我正在使用Gorilla Web Toolkit中的Mux库以及捆绑的Go http服务器。
问题在于,在我的应用程序中,HTTP服务器只是一个组件,需要自行决定停止和启动。
当我调用http.ListenAndServe(fmt.Sprintf(":%d", service.Port()), service.router)
它阻塞时,似乎无法停止服务器运行。
我知道过去这一直是个问题,还是这样吗?有没有新的解决方案?
Answers:
关于正常关机(在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")
}
nil
时srv.Shutdown
我得到了panic: runtime error: invalid memory address or nil pointer dereference
。context.Todo()
而是通过传递。
如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, 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.
原始答案-Pre Go 1.8:
建立在Uvelichitel的答案上。
您可以创建自己的版本,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
}
完整的代码在这里。
HTTP服务器将关闭并显示错误
accept tcp [::]:8080: use of closed network connection
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)
相关的提交可以在这里找到
go func() { X() }()
随后Y()
对X()
之前执行的读者做出错误的假设Y()
。等待组等确保计时错误不会在最不期望的时候咬你!
你可以构造 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)
http.ListenAndServe
出于特定原因询问。那就是我使用GWT MUX库的方式,我不确定如何使用net.listen。.–
由于先前的答案都没有说明为什么使用http.ListenAndServe()无法执行此操作,因此我进入了v1.8 http源代码,它的含义如下:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
如您所见,http.ListenAndServe函数不会返回服务器变量。这意味着您无法进入“服务器”以使用“关机”命令。因此,您需要创建自己的“服务器”实例,而不是使用此功能来实现正常关机。
您可以通过关闭服务器上下文来关闭服务器。
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()
ctx
to的使用server.Shutdown
不正确。该上下文已被取消,因此不会被彻底关闭。您可能已经要求server.Close
进行不干净的关机。(干净关闭该代码将需要广泛的再加工。
这有可能使用来解决context.Context
使用net.ListenConfig
。在我的情况,我不想使用sync.WaitGroup
或http.Server
的Shutdown()
电话,而是依靠context.Context
(其用信号关闭)。
import (
"context"
"http"
"net"
"net/http/pprof"
)
func myListen(ctx context.Context, cancel context.CancelFunc) error {
lc := net.ListenConfig{}
ln, err := lc.Listen(ctx, "tcp4", "127.0.0.1:6060")
if err != nil {
// wrap the err or log why the listen failed
return err
}
mux := http.NewServeMux()
mux.Handle("/debug/pprof/", pprof.Index)
mux.Handle("/debug/pprof/cmdline", pprof.CmdLine)
mux.Handle("/debug/pprof/profile", pprof.Profile)
mux.Handle("/debug/pprof/symbol", pprof.Symbol)
mux.Handle("/debug/pprof/trace", pprof.Trace)
go func() {
if err := http.Serve(l, mux); err != nil {
cancel()
// log why we shut down the context
return err
}
}()
// If you want something semi-synchronous, sleep here for a fraction of a second
return nil
}
对于这种情况,在这种情况下,应用程序仅是服务器,而没有执行其他功能,我http.HandleFunc
为此安装了像这样的模式/shutdown
。就像是
http.HandleFunc("/shutdown", func(w http.ResponseWriter, r *http.Request) {
if <credentials check passes> {
// - Turn on mechanism to reject incoming requests.
// - Block until "in-flight" requests complete.
// - Release resources, both internal and external.
// - Perform all other cleanup procedures thought necessary
// for this to be called a "graceful shutdown".
fmt.Fprint(w, "Goodbye!\n")
os.Exit(0)
}
})
它不需要1.8。但是os.Exit(0)
,我认为,如果有1.8版本可用,那么可以在此处嵌入该解决方案,而不是调用该解决方案。
执行所有这些清理工作的代码留给读者作为练习。
如果您能说出清理代码可能最合理的放置位置,那么这是额外的功劳,因为我不建议您在此处进行清理,以及此端点命中应如何导致该代码的调用。
如果您能说出此os.exit(0)
调用(或选择使用的任何进程出口)在何处(仅出于说明目的),则将获得更多的额外信用,这是最合理的放置方式。
如果您能解释为什么在这种情况下认为可行的所有其他此类机制之上,应该首先考虑这种HTTP服务器进程信令机制,则还有更多的功劳。