aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2026-02-11 21:43:11 +0700
committerShulhan <ms@kilabit.info>2026-02-11 21:43:11 +0700
commit5b41090e6c523a1e66047329e51c2b0db280a581 (patch)
tree10635c88bc6510a7405259199fa6f99675936240
parent37c0e3f259204842c1ac82a04effb6c12e76078d (diff)
downloadpakakeh.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.adoc13
-rw-r--r--lib/http/server.go42
-rw-r--r--lib/http/server_options.go6
-rw-r--r--lib/http/server_test.go23
-rw-r--r--pakakeh.go2
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)
+ }
+}
diff --git a/pakakeh.go b/pakakeh.go
index 97ad48f2..51c7baa0 100644
--- a/pakakeh.go
+++ b/pakakeh.go
@@ -6,4 +6,4 @@
package pakakeh
// Version of this module.
-var Version = `0.61.1`
+var Version = `0.62.0`