diff options
| author | Shulhan <ms@kilabit.info> | 2026-02-11 21:43:11 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2026-02-11 21:43:11 +0700 |
| commit | 5b41090e6c523a1e66047329e51c2b0db280a581 (patch) | |
| tree | 10635c88bc6510a7405259199fa6f99675936240 | |
| parent | 37c0e3f259204842c1ac82a04effb6c12e76078d (diff) | |
| download | pakakeh.go-5b41090e6c523a1e66047329e51c2b0db280a581.tar.xz | |
lib/http: implement server auto shutdown when idle
In the `ServerOptions`, we add option `ShutdownIdleDuration` when set to
non-zero value it will start a timer.
When the timer expired, the server will stop accepting new connection and
then shutting down.
This allow de-activating HTTP server when no connections received after
specific duration to reduce the system resources.
| -rw-r--r-- | CHANGELOG.adoc | 13 | ||||
| -rw-r--r-- | lib/http/server.go | 42 | ||||
| -rw-r--r-- | lib/http/server_options.go | 6 | ||||
| -rw-r--r-- | lib/http/server_test.go | 23 | ||||
| -rw-r--r-- | pakakeh.go | 2 |
5 files changed, 83 insertions, 3 deletions
diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 666dbaad..3130ddba 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -38,8 +38,8 @@ Legend, * 💧: Chores -[#v0_61_1] -== pakakeh.go v0.61.1 (2026-xx-xx) +[#v0_62_0] +== pakakeh.go v0.62.0 (2026-xx-xx) //{{{ [#v0_61_1_lib_http] @@ -56,6 +56,15 @@ to redirect URL. Any trailing slash in the BasePath will be removed. +=== 🌱 Implement server auto shutdown when idle + +In the `ServerOptions`, we add option `ShutdownIdleDuration` when set to +non-zero value it will start a timer. +When the timer expired, the server will stop accepting new connection and +then shutting down. + +This allow de-activating HTTP server when no connections received after +specific duration to reduce the system resources. //}}} [#v0_61_0] diff --git a/lib/http/server.go b/lib/http/server.go index 4501cb9f..bbd1cf3e 100644 --- a/lib/http/server.go +++ b/lib/http/server.go @@ -46,6 +46,8 @@ type Server struct { // Modifying the value of Options after server has been started may // cause undefined effects. Options ServerOptions + + shutdownIdleTimer *time.Timer } // NewServer create and initialize new HTTP server that serve root directory @@ -323,6 +325,14 @@ func (srv *Server) registerPut(ep *Endpoint) (err error) { // ServeHTTP handle mapping of client request to registered endpoints. func (srv *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { + if srv.shutdownIdleTimer != nil { + ok := srv.shutdownIdleTimer.Reset(srv.Options.ShutdownIdleDuration) + if !ok { + // Timer had expired or been stopped. + return + } + } + req.URL.Path = strings.TrimPrefix(req.URL.Path, srv.Options.BasePath) switch req.Method { @@ -358,6 +368,31 @@ func (srv *Server) ServeHTTP(res http.ResponseWriter, req *http.Request) { // Start the HTTP server. func (srv *Server) Start() (err error) { + if srv.Options.ShutdownIdleDuration == 0 { + return srv.serve() + } + + srv.shutdownIdleTimer = time.NewTimer(srv.Options.ShutdownIdleDuration) + go func() { + err = srv.serve() + }() + ever := true + for ever { + select { + case <-srv.shutdownIdleTimer.C: + ever = false + ok := srv.shutdownIdleTimer.Stop() + if !ok { + // Stop has been called manually. + break + } + err = srv.Stop(0) + } + } + return err +} + +func (srv *Server) serve() (err error) { if srv.Server.TLSConfig == nil { if srv.Options.Listener == nil { err = srv.Server.ListenAndServe() @@ -380,6 +415,13 @@ func (srv *Server) Start() (err error) { // Stop the server using Shutdown method. The wait is set default and minimum // to five seconds. func (srv *Server) Stop(wait time.Duration) (err error) { + if srv.shutdownIdleTimer != nil { + ok := srv.shutdownIdleTimer.Stop() + if !ok { + // The timer has been stopped by other goroutine. + return nil + } + } var defWait = 5 * time.Second if wait <= defWait { wait = defWait diff --git a/lib/http/server_options.go b/lib/http/server_options.go index 8e168885..61fcff33 100644 --- a/lib/http/server_options.go +++ b/lib/http/server_options.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "strings" + "time" "git.sr.ht/~shulhan/pakakeh.go/lib/memfs" ) @@ -63,6 +64,11 @@ type ServerOptions struct { // The options for Cross-Origin Resource Sharing. CORS CORSOptions + // ShutdownIdleDuration define the duration where the server will + // automatically stop accepting new connection and then shutting down + // the server. + ShutdownIdleDuration time.Duration + // If true, server generate index.html automatically if its not // exist in the directory. // The index.html contains the list of files inside the requested diff --git a/lib/http/server_test.go b/lib/http/server_test.go index a333628c..0bdcce36 100644 --- a/lib/http/server_test.go +++ b/lib/http/server_test.go @@ -1569,3 +1569,26 @@ func runServerFS(t *testing.T, address, dir string) (srv *Server) { return srv } + +func TestServer_ShutdownIdleDuration(t *testing.T) { + opts := ServerOptions{ + Address: `127.0.0.1:21000`, + ShutdownIdleDuration: 500 * time.Millisecond, + } + srv, err := NewServer(opts) + if err != nil { + t.Fatal(err) + } + waitq := make(chan struct{}, 1) + waitTimer := time.NewTimer(time.Second) + go func() { + err = srv.Start() + waitq <- struct{}{} + }() + select { + case <-waitq: + // Server shutdown after idle. + case <-waitTimer.C: + t.Fatalf(`expecting shutdown after %v`, opts.ShutdownIdleDuration) + } +} @@ -6,4 +6,4 @@ package pakakeh // Version of this module. -var Version = `0.61.1` +var Version = `0.62.0` |
