├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── server.go ├── server_test.go └── state_string.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.test 2 | *.orig 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.3 5 | - tip 6 | 7 | install: 8 | - go get -v github.com/naoina/miyabi 9 | 10 | script: 11 | - go test ./... 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Naoya Inada 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Miyabi [![Build Status](https://travis-ci.org/naoina/miyabi.png?branch=master)](https://travis-ci.org/naoina/miyabi) 2 | 3 | Graceful shutdown and restart for Go's `net/http` handlers. 4 | 5 | Miyabi is pronounced **me-ya-be**. It means Graceful in Japanese. 6 | 7 | ## Usage 8 | 9 | It's very simple. Use `miyabi.ListenAndServe` instead of `http.ListenAndServe`. 10 | You don't have to change other code because `miyabi.ListenAndServe` is compatible with `http.ListenAndServe`. 11 | 12 | ```go 13 | package main 14 | 15 | import ( 16 | "io" 17 | "log" 18 | "net/http" 19 | 20 | "github.com/naoina/miyabi" 21 | ) 22 | 23 | // hello world, the web server 24 | func HelloServer(w http.ResponseWriter, req *http.Request) { 25 | io.WriteString(w, "hello, world!\n") 26 | } 27 | 28 | func main() { 29 | http.HandleFunc("/hello", HelloServer) 30 | log.Fatal(miyabi.ListenAndServe(":8080", nil)) 31 | } 32 | ``` 33 | 34 | See [Godoc](http://godoc.org/github.com/naoina/miyabi) for more information. 35 | 36 | **NOTE**: Miyabi is using features of Go 1.3, so doesn't work in Go 1.2.x and older versions. Also when using on Windows, it works but graceful shutdown/restart are disabled explicitly. 37 | 38 | ## Graceful shutdown or restart 39 | 40 | By default, send `SIGTERM` or `SIGINT` (Ctrl + c) signal to a process that is using Miyabi in order to graceful shutdown and send `SIGHUP` signal in order to graceful restart. 41 | If you want to change the these signal, please set another signal to `miyabi.ShutdownSignal` and/or `miyabi.RestartSignal`. 42 | 43 | In fact, `miyabi.ListenAndServe` and `miyabi.ListenAndServeTLS` will fork a process that is using Miyabi in order to achieve the graceful restart. 44 | This means that you should write code as no side effects until the call of `miyabi.ListenAndServe` or `miyabi.ListenAndServeTLS`. 45 | 46 | ## License 47 | 48 | Miyabi is licensed under the MIT. 49 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | // Package miyabi provides graceful version of net/http compatible HTTP server. 2 | package miyabi 3 | 4 | import ( 5 | "crypto/tls" 6 | "errors" 7 | "fmt" 8 | "net" 9 | "net/http" 10 | "os" 11 | "os/exec" 12 | "os/signal" 13 | "runtime" 14 | "strconv" 15 | "strings" 16 | "sync" 17 | "syscall" 18 | "time" 19 | ) 20 | 21 | var ( 22 | // ShutdownSignal is the signal for graceful shutdown. 23 | // syscall.SIGTERM by default. Please set another signal if you want. 24 | ShutdownSignal = syscall.SIGTERM 25 | 26 | // RestartSignal is the signal for graceful restart. 27 | // syscall.SIGHUP by default. Please set another signal if you want. 28 | RestartSignal = syscall.SIGHUP 29 | 30 | // Timeout specifies the timeout for terminate of the old process. 31 | // A zero value disables the timeout. 32 | Timeout = 3 * time.Minute 33 | 34 | // ServerState specifies the optional callback function that is called 35 | // when the server changes state. See the State type and associated 36 | // constants for details. 37 | ServerState func(state State) 38 | 39 | // FDEnvKey is the environment variable name of inherited file descriptor for graceful restart. 40 | FDEnvKey = "MIYABI_FD" 41 | 42 | errNotForked = errors.New("server isn't forked") 43 | ) 44 | 45 | // ListenAndServe acts like http.ListenAndServe but can be graceful shutdown 46 | // and restart. 47 | // If addr begin with "unix:", will listen on a Unix domain socket instead of 48 | // TCP. 49 | func ListenAndServe(addr string, handler http.Handler) error { 50 | server := &Server{Addr: addr, Handler: handler} 51 | return server.ListenAndServe() 52 | } 53 | 54 | // ListenAndServeTLS acts like http.ListenAndServeTLS but can be graceful 55 | // shutdown and restart. 56 | func ListenAndServeTLS(addr, certFile, keyFile string, handler http.Handler) error { 57 | server := &Server{Addr: addr, Handler: handler} 58 | return server.ListenAndServeTLS(certFile, keyFile) 59 | } 60 | 61 | // Server is similar to http.Server. 62 | // However, ListenAndServe, ListenAndServeTLS and Serve can be graceful 63 | // shutdown and restart. 64 | type Server http.Server 65 | 66 | // ListenAndServe acts like http.Server.ListenAndServe but can be graceful 67 | // shutdown and restart. If srv.Addr begin with "unix:", will listen on a Unix 68 | // domain socket instead of TCP. 69 | func (srv *Server) ListenAndServe() error { 70 | addr := srv.Addr 71 | if addr == "" { 72 | addr = ":http" 73 | } 74 | if runtime.GOOS == "windows" { 75 | l, err := srv.listenTCP(addr) 76 | if err != nil { 77 | return err 78 | } 79 | return srv.Serve(l) 80 | } 81 | if IsMaster() { 82 | var l listener 83 | var err error 84 | if strings.HasPrefix(addr, "unix:") { 85 | l, err = srv.listenUnix(addr[len("unix:"):]) 86 | } else { 87 | l, err = srv.listenTCP(addr) 88 | } 89 | if err != nil { 90 | return err 91 | } 92 | return srv.supervise(l) 93 | } 94 | ln, err := srv.listenerFromFDEnv() 95 | if err != nil { 96 | return err 97 | } 98 | return srv.Serve(ln) 99 | } 100 | 101 | // ListenAndServeTLS acts like http.Server.ListenAndServeTLS but can be 102 | // graceful shutdown and restart. 103 | func (srv *Server) ListenAndServeTLS(certFile, keyFile string) error { 104 | if IsMaster() { 105 | l, err := srv.listenTLS(certFile, keyFile) 106 | if err != nil { 107 | return err 108 | } 109 | return srv.supervise(l) 110 | } 111 | ln, err := srv.listenerFromFDEnv() 112 | if err != nil { 113 | return err 114 | } 115 | return srv.Serve(ln) 116 | } 117 | 118 | // Serve acts like http.Server.Serve but can be graceful shutdown. 119 | // If you want to graceful restart, use ListenAndServe or ListenAndServeTLS instead. 120 | func (srv *Server) Serve(l net.Listener) error { 121 | conns := make(map[net.Conn]struct{}) 122 | var mu sync.Mutex 123 | var wg sync.WaitGroup 124 | srv.ConnState = func(conn net.Conn, state http.ConnState) { 125 | mu.Lock() 126 | switch state { 127 | case http.StateActive: 128 | conns[conn] = struct{}{} 129 | wg.Add(1) 130 | case http.StateIdle, http.StateClosed: 131 | if _, exists := conns[conn]; exists { 132 | delete(conns, conn) 133 | wg.Done() 134 | } 135 | } 136 | mu.Unlock() 137 | } 138 | srv.startWaitSignals(l) 139 | err := (*http.Server)(srv).Serve(l) 140 | wg.Wait() 141 | if err, ok := err.(*net.OpError); ok { 142 | op := err.Op 143 | if runtime.GOOS == "windows" && op == "AcceptEx" { 144 | op = "accept" 145 | } 146 | if op == "accept" && err.Err.Error() == "use of closed network connection" { 147 | return nil 148 | } 149 | } 150 | return err 151 | } 152 | 153 | // SetKeepAlivesEnabled is same as http.Server.SetKeepAlivesEnabled. 154 | func (srv *Server) SetKeepAlivesEnabled(v bool) { 155 | (*http.Server)(srv).SetKeepAlivesEnabled(v) 156 | } 157 | 158 | type listener interface { 159 | net.Listener 160 | 161 | File() (*os.File, error) 162 | } 163 | 164 | func (srv *Server) listenUnix(addr string) (listener, error) { 165 | laddr, err := net.ResolveUnixAddr("unix", addr) 166 | if err != nil { 167 | return nil, err 168 | } 169 | return net.ListenUnix("unix", laddr) 170 | } 171 | 172 | func (srv *Server) listenTCP(addr string) (*tcpKeepAliveListener, error) { 173 | laddr, err := net.ResolveTCPAddr("tcp", addr) 174 | if err != nil { 175 | return nil, err 176 | } 177 | l, err := net.ListenTCP("tcp", laddr) 178 | if err != nil { 179 | return nil, err 180 | } 181 | return &tcpKeepAliveListener{l}, nil 182 | } 183 | 184 | func (srv *Server) listenTLS(certFile, keyFile string) (listener, error) { 185 | addr := srv.Addr 186 | if addr == "" { 187 | addr = ":https" 188 | } 189 | config := &tls.Config{} 190 | if srv.TLSConfig != nil { 191 | *config = *srv.TLSConfig 192 | } 193 | if config.NextProtos == nil { 194 | config.NextProtos = []string{"http/1.1"} 195 | } 196 | var err error 197 | config.Certificates = make([]tls.Certificate, 1) 198 | config.Certificates[0], err = tls.LoadX509KeyPair(certFile, keyFile) 199 | if err != nil { 200 | return nil, err 201 | } 202 | ln, err := srv.listenTCP(addr) 203 | if err != nil { 204 | return nil, err 205 | } 206 | tlsListener := tls.NewListener(ln, config) 207 | return tlsListener.(listener), nil 208 | } 209 | 210 | func (srv *Server) startWaitSignals(l net.Listener) { 211 | c := make(chan os.Signal) 212 | signal.Notify(c, syscall.SIGINT, ShutdownSignal) 213 | go func() { 214 | sig := <-c 215 | srv.SetKeepAlivesEnabled(false) 216 | switch sig { 217 | case syscall.SIGINT, ShutdownSignal: 218 | signal.Stop(c) 219 | l.Close() 220 | } 221 | }() 222 | } 223 | 224 | func (srv *Server) supervise(l listener) error { 225 | p, err := srv.forkExec(l) 226 | if err != nil { 227 | return err 228 | } 229 | if ServerState != nil { 230 | ServerState(StateStart) 231 | } 232 | c := make(chan os.Signal) 233 | signal.Notify(c, syscall.SIGINT, ShutdownSignal, RestartSignal) 234 | for { 235 | switch sig := <-c; sig { 236 | case RestartSignal: 237 | child, err := srv.forkExec(l) 238 | if err != nil { 239 | return err 240 | } 241 | p.Signal(ShutdownSignal) 242 | timer := time.AfterFunc(Timeout, func() { 243 | p.Kill() 244 | }) 245 | p.Wait() 246 | timer.Stop() 247 | p = child 248 | if ServerState != nil { 249 | ServerState(StateRestart) 250 | } 251 | case syscall.SIGINT, ShutdownSignal: 252 | signal.Stop(c) 253 | l.Close() 254 | p.Signal(ShutdownSignal) 255 | timer := time.AfterFunc(Timeout, func() { 256 | p.Kill() 257 | }) 258 | _, err := p.Wait() 259 | timer.Stop() 260 | if ServerState != nil { 261 | ServerState(StateShutdown) 262 | } 263 | return err 264 | } 265 | } 266 | } 267 | 268 | func (srv *Server) listenerFromFDEnv() (net.Listener, error) { 269 | fd, err := srv.getFD() 270 | if err != nil { 271 | return nil, err 272 | } 273 | file := os.NewFile(fd, "listen socket") 274 | defer file.Close() 275 | l, err := net.FileListener(file) 276 | if err != nil { 277 | return nil, err 278 | } 279 | if l, ok := l.(*net.UnixListener); ok { 280 | addr := l.Addr().String() 281 | if _, err := os.Stat(addr); err == nil { 282 | if err := os.Remove(addr); err != nil { 283 | return nil, err 284 | } 285 | } 286 | return srv.listenUnix(addr) 287 | } 288 | return tcpKeepAliveListener{l.(*net.TCPListener)}, nil 289 | } 290 | 291 | // getFD gets file descriptor of listen socket from environment variable. 292 | func (srv *Server) getFD() (uintptr, error) { 293 | fdStr := os.Getenv(FDEnvKey) 294 | if fdStr == "" { 295 | return 0, errNotForked 296 | } 297 | fd, err := strconv.Atoi(fdStr) 298 | if err != nil { 299 | return 0, err 300 | } 301 | return uintptr(fd), nil 302 | } 303 | 304 | func (srv *Server) forkExec(l listener) (*os.Process, error) { 305 | progName, err := exec.LookPath(os.Args[0]) 306 | if err != nil { 307 | return nil, err 308 | } 309 | pwd, err := os.Getwd() 310 | if err != nil { 311 | return nil, err 312 | } 313 | f, err := l.File() 314 | if err != nil { 315 | return nil, err 316 | } 317 | defer f.Close() 318 | files := []*os.File{os.Stdin, os.Stdout, os.Stderr, f} 319 | fdEnv := fmt.Sprintf("%s=%d", FDEnvKey, len(files)-1) 320 | return os.StartProcess(progName, os.Args, &os.ProcAttr{ 321 | Dir: pwd, 322 | Env: append(os.Environ(), fdEnv), 323 | Files: files, 324 | }) 325 | } 326 | 327 | // tcpKeepAliveListener is copy from net/http. 328 | type tcpKeepAliveListener struct { 329 | *net.TCPListener 330 | } 331 | 332 | // Accept is copy from net/http. 333 | func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { 334 | tc, err := ln.AcceptTCP() 335 | if err != nil { 336 | return nil, err 337 | } 338 | tc.SetKeepAlive(true) 339 | tc.SetKeepAlivePeriod(3 * time.Minute) 340 | return tc, nil 341 | } 342 | 343 | // IsMaster returns whether the current process is master. 344 | func IsMaster() bool { 345 | return os.Getenv(FDEnvKey) == "" 346 | } 347 | 348 | // A State represents the state of the server. 349 | // It's used by the optional ServerState hook. 350 | type State uint8 351 | 352 | const ( 353 | // StateStart represents a state that server has been started. 354 | StateStart State = iota 355 | 356 | // StateRestart represents a state that server has been restarted. 357 | StateRestart 358 | 359 | // StateShutdown represents a state that server has been shutdown. 360 | StateShutdown 361 | ) 362 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package miyabi_test 2 | 3 | import ( 4 | "net" 5 | "net/http" 6 | "os" 7 | "reflect" 8 | "strings" 9 | "syscall" 10 | "testing" 11 | "time" 12 | 13 | "github.com/naoina/miyabi" 14 | ) 15 | 16 | const ( 17 | addr = "127.0.0.1:0" 18 | ) 19 | 20 | func newTestListener(t *testing.T) net.Listener { 21 | l, err := net.Listen("tcp", addr) 22 | if err != nil { 23 | t.Fatal(err) 24 | } 25 | return l 26 | } 27 | 28 | func TestServer_Serve(t *testing.T) { 29 | done := make(chan struct{}, 1) 30 | server := &miyabi.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 31 | done <- struct{}{} 32 | })} 33 | l := newTestListener(t) 34 | defer l.Close() 35 | go func() { 36 | if err := server.Serve(l); err != nil { 37 | t.Errorf("server.Serve(l) => %#v; want nil", err) 38 | } 39 | done <- struct{}{} 40 | }() 41 | _, err := http.Get("http://" + l.Addr().String()) 42 | if err != nil { 43 | t.Fatal(err) 44 | } 45 | select { 46 | case <-done: 47 | case <-time.After(3 * time.Second): 48 | t.Error("timeout") 49 | } 50 | 51 | l.Close() 52 | select { 53 | case <-done: 54 | case <-time.After(3 * time.Second): 55 | t.Error("timeout") 56 | } 57 | } 58 | 59 | func TestServer_Serve_gracefulShutdownDefaultSignal(t *testing.T) { 60 | testServerServeGracefulShutdown(t) 61 | } 62 | 63 | func TestServer_Serve_gracefulShutdownAnotherSignal(t *testing.T) { 64 | for _, sig := range []syscall.Signal{syscall.SIGHUP, syscall.SIGQUIT} { 65 | origSignal := miyabi.ShutdownSignal 66 | miyabi.ShutdownSignal = sig 67 | defer func() { 68 | miyabi.ShutdownSignal = origSignal 69 | }() 70 | testServerServeGracefulShutdown(t) 71 | } 72 | } 73 | 74 | func testServerServeGracefulShutdown(t *testing.T) { 75 | done := make(chan struct{}) 76 | server := &miyabi.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 77 | done <- struct{}{} 78 | })} 79 | l := newTestListener(t) 80 | defer l.Close() 81 | go server.Serve(l) 82 | wait := make(chan struct{}) 83 | go func() { 84 | if _, err := http.Get("http://" + l.Addr().String()); err != nil { 85 | t.Errorf("http.Get => %v; want nil", err) 86 | } 87 | wait <- struct{}{} 88 | }() 89 | <-time.After(1 * time.Second) 90 | pid := os.Getpid() 91 | p, err := os.FindProcess(pid) 92 | if err != nil { 93 | t.Fatal(err) 94 | } 95 | if err := p.Signal(miyabi.ShutdownSignal); err != nil { 96 | t.Fatal(err) 97 | } 98 | go func() { 99 | <-time.After(1 * time.Second) 100 | if _, err = http.Get("http://" + l.Addr().String()); err == nil { 101 | t.Errorf("http.Get after shutdown => nil; want error") 102 | } 103 | <-done 104 | <-wait 105 | wait <- struct{}{} 106 | }() 107 | select { 108 | case <-wait: 109 | case <-time.After(5 * time.Second): 110 | t.Errorf("timeout") 111 | } 112 | } 113 | 114 | func TestServerState_StateStart(t *testing.T) { 115 | done := make(chan struct{}) 116 | origServerState := miyabi.ServerState 117 | miyabi.ServerState = func(state miyabi.State) { 118 | switch state { 119 | case miyabi.StateStart: 120 | done <- struct{}{} 121 | } 122 | } 123 | defer func() { 124 | miyabi.ServerState = origServerState 125 | }() 126 | go miyabi.ListenAndServe(addr, nil) 127 | select { 128 | case <-done: 129 | case <-time.After(5 * time.Second): 130 | t.Errorf("timeout") 131 | } 132 | } 133 | 134 | func TestServerState_StateShutdown(t *testing.T) { 135 | done := make(chan struct{}) 136 | started := make(chan struct{}) 137 | origServerState := miyabi.ServerState 138 | miyabi.ServerState = func(state miyabi.State) { 139 | switch state { 140 | case miyabi.StateStart: 141 | started <- struct{}{} 142 | case miyabi.StateShutdown: 143 | done <- struct{}{} 144 | } 145 | } 146 | defer func() { 147 | miyabi.ServerState = origServerState 148 | }() 149 | go miyabi.ListenAndServe(addr, nil) 150 | select { 151 | case <-started: 152 | pid := os.Getpid() 153 | p, err := os.FindProcess(pid) 154 | if err != nil { 155 | t.Fatal(err) 156 | } 157 | if err := p.Signal(miyabi.ShutdownSignal); err != nil { 158 | t.Fatal(err) 159 | } 160 | case <-time.After(5 * time.Second): 161 | t.Errorf("timeout") 162 | } 163 | select { 164 | case <-done: 165 | case <-time.After(5 * time.Second): 166 | t.Errorf("timeout") 167 | } 168 | } 169 | 170 | func TestIsMaster(t *testing.T) { 171 | origEnv := make([]string, len(os.Environ())) 172 | copy(origEnv, os.Environ()) 173 | for _, v := range []struct { 174 | env string 175 | expect bool 176 | }{ 177 | {miyabi.FDEnvKey, false}, 178 | {"UNKNOWN_KEY", true}, 179 | } { 180 | func() { 181 | defer func() { 182 | os.Clearenv() 183 | for _, v := range origEnv { 184 | env := strings.SplitN(v, "=", 2) 185 | os.Setenv(env[0], env[1]) 186 | } 187 | }() 188 | if err := os.Setenv(v.env, "1"); err != nil { 189 | t.Error(err) 190 | return 191 | } 192 | actual := miyabi.IsMaster() 193 | expect := v.expect 194 | if !reflect.DeepEqual(actual, expect) { 195 | t.Errorf(`IsMaster() with %v=1 => %#v; want %#v`, v.env, actual, expect) 196 | } 197 | }() 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /state_string.go: -------------------------------------------------------------------------------- 1 | // generated by stringer -type State; DO NOT EDIT 2 | 3 | package miyabi 4 | 5 | import "fmt" 6 | 7 | const _State_name = "StateStartStateRestartStateShutdown" 8 | 9 | var _State_index = [...]uint8{10, 22, 35} 10 | 11 | func (i State) String() string { 12 | if i >= State(len(_State_index)) { 13 | return fmt.Sprintf("State(%d)", i) 14 | } 15 | hi := _State_index[i] 16 | lo := uint8(0) 17 | if i > 0 { 18 | lo = _State_index[i-1] 19 | } 20 | return _State_name[lo:hi] 21 | } 22 | --------------------------------------------------------------------------------