From 90d355d3e1693435a6300da7d4e191126aced78e Mon Sep 17 00:00:00 2001 From: Carl Jackson Date: Mon, 1 Sep 2014 16:06:19 -0700 Subject: [PATCH] Use net/http.Server.SetKeepAlivesEnabled in Go 1.3 This feature can be used in place of the pile of hacks in middleware.go, and doesn't involve awkwardly shimming out a http.ResponseWriter. Sounds like a win-win! --- example/main.go | 18 ++++++++++++++++ graceful/graceful.go | 33 ----------------------------- graceful/middleware.go | 2 ++ graceful/middleware13.go | 12 +++++++++++ graceful/middleware_test.go | 2 ++ graceful/net.go | 3 +++ graceful/serve.go | 41 ++++++++++++++++++++++++++++++++++++ graceful/serve13.go | 42 +++++++++++++++++++++++++++++++++++++ 8 files changed, 120 insertions(+), 33 deletions(-) create mode 100644 graceful/middleware13.go create mode 100644 graceful/serve.go create mode 100644 graceful/serve13.go diff --git a/example/main.go b/example/main.go index d49b2af..9315af6 100644 --- a/example/main.go +++ b/example/main.go @@ -13,6 +13,7 @@ import ( "net/http" "regexp" "strconv" + "time" "github.com/goji/param" "github.com/zenazn/goji" @@ -58,6 +59,9 @@ func main() { // Use a custom 404 handler goji.NotFound(NotFound) + // Sometimes requests take a long time. + goji.Get("/waitforit", WaitForIt) + // Call Serve() at the bottom of your main() function, and it'll take // care of everything else for you, including binding to a socket (with // automatic support for systemd and Einhorn) and supporting graceful @@ -135,6 +139,20 @@ func GetGreet(c web.C, w http.ResponseWriter, r *http.Request) { greet.Write(w) } +// WaitForIt is a particularly slow handler (GET "/waitforit"). Try loading this +// endpoint and initiating a graceful shutdown (Ctrl-C) or Einhorn reload. The +// old server will stop accepting new connections and will attempt to kill +// outstanding idle (keep-alive) connections, but will patiently stick around +// for this endpoint to finish. How kind of it! +func WaitForIt(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "This is going to be legend... (wait for it)\n") + if fl, ok := w.(http.Flusher); ok { + fl.Flush() + } + time.Sleep(15 * time.Second) + io.WriteString(w, "...dary! Legendary!\n") +} + // AdminRoot is root (GET "/admin/root"). Much secret. Very administrate. Wow. func AdminRoot(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "Gritter\n======\n\nSuper secret admin page!\n") diff --git a/graceful/graceful.go b/graceful/graceful.go index 13537e7..8958126 100644 --- a/graceful/graceful.go +++ b/graceful/graceful.go @@ -22,7 +22,6 @@ import ( "crypto/tls" "net" "net/http" - "time" ) /* @@ -41,38 +40,6 @@ ListenAndServe and ListenAndServeTLS here more-or-less verbatim. "Oh well!" // implementations of its methods. type Server http.Server -func (srv *Server) Serve(l net.Listener) (err error) { - go func() { - <-kill - l.Close() - }() - l = WrapListener(l) - - // Spawn a shadow http.Server to do the actual servering. We do this - // because we need to sketch on some of the parameters you passed in, - // and it's nice to keep our sketching to ourselves. - shadow := *(*http.Server)(srv) - - if shadow.ReadTimeout == 0 { - shadow.ReadTimeout = forever - } - shadow.Handler = Middleware(shadow.Handler) - - err = shadow.Serve(l) - - // We expect an error when we close the listener, so we indiscriminately - // swallow Serve errors when we're in a shutdown state. - select { - case <-kill: - return nil - default: - return err - } -} - -// About 200 years, also known as "forever" -const forever time.Duration = 200 * 365 * 24 * time.Hour - func (srv *Server) ListenAndServe() error { addr := srv.Addr if addr == "" { diff --git a/graceful/middleware.go b/graceful/middleware.go index 3e17620..f169891 100644 --- a/graceful/middleware.go +++ b/graceful/middleware.go @@ -1,3 +1,5 @@ +// +build !go1.3 + package graceful import ( diff --git a/graceful/middleware13.go b/graceful/middleware13.go new file mode 100644 index 0000000..b1a7e2d --- /dev/null +++ b/graceful/middleware13.go @@ -0,0 +1,12 @@ +// +build go1.3 + +package graceful + +import "net/http" + +// Middleware is a stub that does nothing. When used with versions of Go before +// Go 1.3, it provides functionality similar to net/http.Server's +// SetKeepAlivesEnabled. +func Middleware(h http.Handler) http.Handler { + return h +} diff --git a/graceful/middleware_test.go b/graceful/middleware_test.go index ecec606..15b4fcc 100644 --- a/graceful/middleware_test.go +++ b/graceful/middleware_test.go @@ -1,3 +1,5 @@ +// +build !go1.3 + package graceful import ( diff --git a/graceful/net.go b/graceful/net.go index 5573796..b3f124a 100644 --- a/graceful/net.go +++ b/graceful/net.go @@ -162,6 +162,9 @@ func (c *conn) Read(b []byte) (n int, err error) { if c.state == csWaiting { c.state = csWorking + } else if c.state == csDead { + n = 0 + err = io.EOF } }() diff --git a/graceful/serve.go b/graceful/serve.go new file mode 100644 index 0000000..8746075 --- /dev/null +++ b/graceful/serve.go @@ -0,0 +1,41 @@ +// +build !go1.3 + +package graceful + +import ( + "net" + "net/http" + "time" +) + +// About 200 years, also known as "forever" +const forever time.Duration = 200 * 365 * 24 * time.Hour + +func (srv *Server) Serve(l net.Listener) error { + go func() { + <-kill + l.Close() + }() + l = WrapListener(l) + + // Spawn a shadow http.Server to do the actual servering. We do this + // because we need to sketch on some of the parameters you passed in, + // and it's nice to keep our sketching to ourselves. + shadow := *(*http.Server)(srv) + + if shadow.ReadTimeout == 0 { + shadow.ReadTimeout = forever + } + shadow.Handler = Middleware(shadow.Handler) + + err := shadow.Serve(l) + + // We expect an error when we close the listener, so we indiscriminately + // swallow Serve errors when we're in a shutdown state. + select { + case <-kill: + return nil + default: + return err + } +} diff --git a/graceful/serve13.go b/graceful/serve13.go new file mode 100644 index 0000000..41e9f52 --- /dev/null +++ b/graceful/serve13.go @@ -0,0 +1,42 @@ +// +build go1.3 + +package graceful + +import ( + "net" + "net/http" + "time" +) + +// About 200 years, also known as "forever" +const forever time.Duration = 200 * 365 * 24 * time.Hour + +func (srv *Server) Serve(l net.Listener) error { + l = WrapListener(l) + + // Spawn a shadow http.Server to do the actual servering. We do this + // because we need to sketch on some of the parameters you passed in, + // and it's nice to keep our sketching to ourselves. + shadow := *(*http.Server)(srv) + + if shadow.ReadTimeout == 0 { + shadow.ReadTimeout = forever + } + + go func() { + <-kill + shadow.SetKeepAlivesEnabled(false) + l.Close() + }() + + err := shadow.Serve(l) + + // We expect an error when we close the listener, so we indiscriminately + // swallow Serve errors when we're in a shutdown state. + select { + case <-kill: + return nil + default: + return err + } +}