Skip to content

Commit

Permalink
feat: Rework shutting down for HTTP server to use a signal string
Browse files Browse the repository at this point in the history
Revised `run_modes.go` to stop the HTTP server if the
"X-Server-Shutdown" header value matches the
`shutdownSignalHeaderValue`.
Now it's possible to shutdown the server based on header set in the request
rather than closing after a set number of responses. This provides more
efficiency and control over the server's operations.
  • Loading branch information
farzadghanei committed Apr 27, 2024
1 parent 11acfb1 commit 278acb6
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 39 deletions.
1 change: 1 addition & 0 deletions cmd/chkok_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func TestRunHttp(t *testing.T) {
if err != nil {
t.Fatalf("Failed to create HTTP request: %v", err)
}
req.Header.Set("X-Server-Shutdown", "test-shutdown-signal") // shutdown the server after the request

// Send the request multiple times, waiting for the server to
// start up and respond
Expand Down
6 changes: 5 additions & 1 deletion examples/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
runners:
default:
timeout: 5m
shutdown_after_requests: 100 # mainly useful for testing http mode
listen_address: "127.0.0.1:51234"
http:
# shutdown_signal_header is mainly useful for testing http mode, do not set it in production
# if set, better be treated like a secret, and a secure transport layer should be used.
# this is the value set on "X-Shutdown-Signal" header in the http request
# shutdown_signal_header: "test-shutdown-signal"

check_suites:
etc:
Expand Down
2 changes: 1 addition & 1 deletion examples/test-http.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ runners:
http:
timeout: 5s
listen_address: "127.0.0.1:51234"
shutdown_after_requests: 1 # stop http server after 1 request, used for testing http mode
shutdown_signal_header: "test-shutdown-signal" # mainly useful for testing http mode
request_read_timeout: 2s
response_write_timeout: 2s

Expand Down
24 changes: 12 additions & 12 deletions internal/conf.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ type Conf struct {

// ConfRunner is config for the check runners
type ConfRunner struct {
Timeout time.Duration
ShutdownAfterRequests uint32 `yaml:"shutdown_after_requests"`
ListenAddress string `yaml:"listen_address"`
RequestReadTimeout time.Duration `yaml:"request_read_timeout"`
ResponseWriteTimeout time.Duration `yaml:"response_write_timeout"`
Timeout time.Duration
ShutdownSignalHeader *string `yaml:"shutdown_signal_header"`
ListenAddress string `yaml:"listen_address"`
RequestReadTimeout time.Duration `yaml:"request_read_timeout"`
ResponseWriteTimeout time.Duration `yaml:"response_write_timeout"`
}

// ConfCheckSpec is the spec for each check configuration
Expand Down Expand Up @@ -73,18 +73,18 @@ func GetConfRunner(runners *ConfRunners, name string) (ConfRunner, bool) {

// Merge the requested runner with the default runner
mergedConf := ConfRunner{
Timeout: namedConf.Timeout,
ShutdownAfterRequests: namedConf.ShutdownAfterRequests,
ListenAddress: namedConf.ListenAddress,
RequestReadTimeout: namedConf.RequestReadTimeout,
ResponseWriteTimeout: namedConf.ResponseWriteTimeout,
Timeout: namedConf.Timeout,
ShutdownSignalHeader: namedConf.ShutdownSignalHeader,
ListenAddress: namedConf.ListenAddress,
RequestReadTimeout: namedConf.RequestReadTimeout,
ResponseWriteTimeout: namedConf.ResponseWriteTimeout,
}

if mergedConf.Timeout == 0 {
mergedConf.Timeout = defaultConf.Timeout
}
if mergedConf.ShutdownAfterRequests == 0 {
mergedConf.ShutdownAfterRequests = defaultConf.ShutdownAfterRequests
if mergedConf.ShutdownSignalHeader == nil {
mergedConf.ShutdownSignalHeader = defaultConf.ShutdownSignalHeader
}
if mergedConf.ListenAddress == "" {
mergedConf.ListenAddress = defaultConf.ListenAddress
Expand Down
44 changes: 26 additions & 18 deletions internal/conf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,6 @@ func TestReadConf(t *testing.T) {
if runner.Timeout.Minutes() != float64(wantMinutes) {
t.Errorf("invalid read conf default runner, want %v timeout got %v", wantMinutes, runner.Timeout.Minutes())
}
var wantShutdownAfterRequests uint32 = 100
if runner.ShutdownAfterRequests != wantShutdownAfterRequests {
t.Errorf("invalid read conf, want %v shutdown after requests got %v",
wantShutdownAfterRequests, runner.ShutdownAfterRequests)
}
etcChecks, ok := conf.CheckSuites["etc"]
if !ok {
t.Errorf("read conf found no etc checks")
Expand All @@ -66,18 +61,19 @@ func TestReadConf(t *testing.T) {
}

func TestGetConfRunner(t *testing.T) {
var shutdownSignalHeader = "Test-Shutdown"
runners := ConfRunners{
"default": ConfRunner{
Timeout: 5 * time.Second,
ShutdownAfterRequests: 10,
ListenAddress: "localhost:8080",
Timeout: 5 * time.Second,
ListenAddress: "localhost:8080",
},
"testMinimalHttpRunner": ConfRunner{},
"testHttpRunner": ConfRunner{
Timeout: 10 * time.Second,
ShutdownAfterRequests: 20,
ListenAddress: "localhost:9090",
RequestReadTimeout: 5 * time.Second,
ResponseWriteTimeout: 5 * time.Second,
Timeout: 10 * time.Second,
ShutdownSignalHeader: &shutdownSignalHeader,
ListenAddress: "localhost:9090",
RequestReadTimeout: 5 * time.Second,
ResponseWriteTimeout: 5 * time.Second,
},
}

Expand All @@ -91,11 +87,11 @@ func TestGetConfRunner(t *testing.T) {
name: "Existing runner",
runnerName: "testHttpRunner",
expectedRunner: ConfRunner{
Timeout: 10 * time.Second,
ShutdownAfterRequests: 20,
ListenAddress: "localhost:9090",
RequestReadTimeout: 5 * time.Second,
ResponseWriteTimeout: 5 * time.Second,
Timeout: 10 * time.Second,
ShutdownSignalHeader: &shutdownSignalHeader,
ListenAddress: "localhost:9090",
RequestReadTimeout: 5 * time.Second,
ResponseWriteTimeout: 5 * time.Second,
},
expectedExists: true,
},
Expand All @@ -105,6 +101,18 @@ func TestGetConfRunner(t *testing.T) {
expectedRunner: runners["default"],
expectedExists: true,
},
{
name: "Minimal http runner",
runnerName: "testMinimalHttpRunner",
expectedRunner: ConfRunner{
Timeout: 5 * time.Second,
ShutdownSignalHeader: nil,
ListenAddress: "localhost:8080",
RequestReadTimeout: 0,
ResponseWriteTimeout: 0,
},
expectedExists: true,
},
{
name: "Default runner",
runnerName: "default",
Expand Down
15 changes: 8 additions & 7 deletions internal/run_modes.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ func httpRequestAsString(r *http.Request) string {
// RunModeHTTP runs app in http server mode using the provided config, return exit code
func RunModeHTTP(checkGroups *CheckSuites, conf *ConfRunner, logger *log.Logger) int {
timeout := conf.Timeout
shutdownAfterRequests := conf.ShutdownAfterRequests
shutdownSignalHeaderValue := ""
if conf.ShutdownSignalHeader != nil {
shutdownSignalHeaderValue = *conf.ShutdownSignalHeader
}
listenAddress := conf.ListenAddress
requestReadTimeout := conf.RequestReadTimeout
responseWriteTimeout := conf.ResponseWriteTimeout
Expand Down Expand Up @@ -101,7 +104,7 @@ func RunModeHTTP(checkGroups *CheckSuites, conf *ConfRunner, logger *log.Logger)
for request = range reqHandlerChan {
atomic.AddUint32(&count, 1)
logger.Printf("request [%v] is processed: %v", count, httpRequestAsString(request))
if shutdownAfterRequests > 0 && atomic.LoadUint32(&count) >= shutdownAfterRequests {
if shutdownSignalHeaderValue != "" && request.Header.Get("X-Server-Shutdown") == shutdownSignalHeaderValue {
if err := server.Shutdown(timeoutCtx); err != nil {
logger.Printf("http server shutdown failed: %v", err)
}
Expand All @@ -113,11 +116,9 @@ func RunModeHTTP(checkGroups *CheckSuites, conf *ConfRunner, logger *log.Logger)
logger.Printf("starting http server ...")
err := server.ListenAndServe()
close(reqHandlerChan)
if err != nil {
if atomic.LoadUint32(&count) < 1 { // server didn't handle any requests
logger.Printf("http server failed to start: %v", err)
return ExSoftware
}
if err != nil && err != http.ErrServerClosed {
logger.Printf("http server failed to start: %v", err)
return ExSoftware
}
logger.Printf("http server shutdown!")
return ExOK
Expand Down

0 comments on commit 278acb6

Please sign in to comment.