From 4628dcee00aecb6b232aa475106b1ef5ab4d66f6 Mon Sep 17 00:00:00 2001 From: melinda Date: Tue, 16 Jan 2024 18:19:05 -0800 Subject: [PATCH 1/6] Expose error to user (this would have saved me soem time) --- main_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main_test.go b/main_test.go index 59b3f7d..e620010 100644 --- a/main_test.go +++ b/main_test.go @@ -2,6 +2,7 @@ package main import ( "context" + "fmt" "net/http" "net/http/httptest" "os" @@ -27,6 +28,7 @@ func TestMain(m *testing.M) { sql := "CREATE EXTENSION IF NOT EXISTS postgis" _, err = db.Exec(context.Background(), sql) if err != nil { + fmt.Printf("Error creating extension: %s", err) os.Exit(1) } From be14b845ab66db2601b23c3181336d42fdbe08a7 Mon Sep 17 00:00:00 2001 From: melinda Date: Wed, 17 Jan 2024 13:25:40 -0800 Subject: [PATCH 2/6] Clean up BasePath setting in preparation for more tests --- main_test.go | 4 ++++ util_test.go | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/main_test.go b/main_test.go index e620010..181c183 100644 --- a/main_test.go +++ b/main_test.go @@ -71,4 +71,8 @@ func TestBasePath(t *testing.T) { assert.Equal(t, 200, response.Code, "OK response is expected") } + // cleanup + viper.Set("BasePath", "/") } + + diff --git a/util_test.go b/util_test.go index 1807158..0b39375 100644 --- a/util_test.go +++ b/util_test.go @@ -48,7 +48,7 @@ func TestMetrics(t *testing.T) { viper.Set("EnableMetrics", true) r := tileRouter() - request, _ := http.NewRequest("GET", "/test/metrics", nil) + request, _ := http.NewRequest("GET", "/metrics", nil) response := httptest.NewRecorder() r.ServeHTTP(response, request) assert.Equal(t, 200, response.Code, "OK response is expected") From c6f8dd9a6bb39ce69016743a547cebc0cca59469 Mon Sep 17 00:00:00 2001 From: melinda Date: Wed, 17 Jan 2024 13:26:11 -0800 Subject: [PATCH 3/6] Test show/hide preview functionality --- main_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/main_test.go b/main_test.go index 181c183..fea1659 100644 --- a/main_test.go +++ b/main_test.go @@ -75,4 +75,43 @@ func TestBasePath(t *testing.T) { viper.Set("BasePath", "/") } +// Test that the preview endpoints are hidden or shown according to the config +func TestShowPreview(t *testing.T) { + viper.Set("ShowPreview", true) + r := tileRouter() + request, _ := http.NewRequest("GET", "/index.json", nil) + response := httptest.NewRecorder() + r.ServeHTTP(response, request) + assert.Equal(t, 200, response.Code, "OK response is expected") + request, _ = http.NewRequest("GET", "/index.html", nil) + response = httptest.NewRecorder() + r.ServeHTTP(response, request) + assert.Equal(t, 200, response.Code, "OK response is expected") +} + +// the current default behavior is to show the preview +func TestShowPreviewDefault(t *testing.T) { + r := tileRouter() + request, _ := http.NewRequest("GET", "/index.json", nil) + response := httptest.NewRecorder() + r.ServeHTTP(response, request) + assert.Equal(t, 200, response.Code, "OK response is expected") + request, _ = http.NewRequest("GET", "/index.html", nil) + response = httptest.NewRecorder() + r.ServeHTTP(response, request) + assert.Equal(t, 200, response.Code, "OK response is expected") +} + +func TestHidePreview(t *testing.T) { + viper.Set("ShowPreview", false) + r := tileRouter() + request, _ := http.NewRequest("GET", "/index.json", nil) + response := httptest.NewRecorder() + r.ServeHTTP(response, request) + assert.Equal(t, 404, response.Code, "Not Found response is expected") + request, _ = http.NewRequest("GET", "/index.html", nil) + response = httptest.NewRecorder() + r.ServeHTTP(response, request) + assert.Equal(t, 404, response.Code, "Not Found response is expected") +} From 47707c580e3c2a79e06ceb7005b78f6cecd8686c Mon Sep 17 00:00:00 2001 From: melinda Date: Wed, 17 Jan 2024 13:58:26 -0800 Subject: [PATCH 4/6] Simple health check endpoint and tests --- main.go | 10 ++++++++++ main_test.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/main.go b/main.go index 808b96a..8eb84f2 100644 --- a/main.go +++ b/main.go @@ -100,6 +100,8 @@ func init() { viper.SetDefault("CoordinateSystem.Ymin", -20037508.3427892) viper.SetDefault("CoordinateSystem.Xmax", 20037508.3427892) viper.SetDefault("CoordinateSystem.Ymax", 20037508.3427892) + + viper.SetDefault("HealthEndpoint", "/health") } func main() { @@ -391,6 +393,12 @@ func requestTiles(w http.ResponseWriter, r *http.Request) error { return nil } +// A simple health check endpoint +func healthCheck(w http.ResponseWriter, r *http.Request) error { + w.WriteHeader(http.StatusOK) + return nil +} + /******************************************************************************/ // tileAppError is an optional error structure functions can return @@ -488,6 +496,8 @@ func tileRouter() *mux.Router { if viper.GetBool("EnableMetrics") { r.Handle("/metrics", promhttp.Handler()) } + + r.Handle(viper.GetString("HealthEndpoint"), tileAppHandler(healthCheck)).Methods("GET") return r } diff --git a/main_test.go b/main_test.go index fea1659..c82a5fd 100644 --- a/main_test.go +++ b/main_test.go @@ -69,6 +69,11 @@ func TestBasePath(t *testing.T) { response := httptest.NewRecorder() r.ServeHTTP(response, request) assert.Equal(t, 200, response.Code, "OK response is expected") + + request, _ = http.NewRequest("GET", "/test/health", nil) + response = httptest.NewRecorder() + r.ServeHTTP(response, request) + assert.Equal(t, 200, response.Code, "OK response is expected") } // cleanup @@ -113,5 +118,34 @@ func TestHidePreview(t *testing.T) { response = httptest.NewRecorder() r.ServeHTTP(response, request) assert.Equal(t, 404, response.Code, "Not Found response is expected") + + // cleanup + viper.Set("ShowPreview", true) +} + +// Test that the health endpoint gives a 200 if the server is running +func TestHealth(t *testing.T) { + r := tileRouter() + request, _ := http.NewRequest("GET", "/health", nil) + response := httptest.NewRecorder() + r.ServeHTTP(response, request) + assert.Equal(t, 200, response.Code, "OK response is expected") + assert.Equal(t, "200 OK", string(response.Result().Status), "Response status should say ok") } +func TestHealthCustomUrl(t *testing.T) { + viper.Set("HealthEndpoint", "/testHealthABC") + r := tileRouter() + request, _ := http.NewRequest("GET", "/health", nil) + response := httptest.NewRecorder() + r.ServeHTTP(response, request) + assert.Equal(t, 404, response.Code, "Not Found response is expected") + request, _ = http.NewRequest("GET", "/testHealthABC", nil) + response = httptest.NewRecorder() + r.ServeHTTP(response, request) + assert.Equal(t, 200, response.Code, "OK response is expected") + assert.Equal(t, "200 OK", string(response.Result().Status), "Response status should say ok") + + // cleanup + viper.Set("HealthEndpoint", "/health") +} From 77b9bdba1b59141c1b0c6388bb5227fc0650ec6d Mon Sep 17 00:00:00 2001 From: melinda Date: Wed, 17 Jan 2024 14:45:14 -0800 Subject: [PATCH 5/6] Make response make more sense when viewed in a browser --- main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index 8eb84f2..fa065b9 100644 --- a/main.go +++ b/main.go @@ -396,6 +396,7 @@ func requestTiles(w http.ResponseWriter, r *http.Request) error { // A simple health check endpoint func healthCheck(w http.ResponseWriter, r *http.Request) error { w.WriteHeader(http.StatusOK) + w.Write([]byte("200 OK")) return nil } @@ -497,7 +498,7 @@ func tileRouter() *mux.Router { r.Handle("/metrics", promhttp.Handler()) } - r.Handle(viper.GetString("HealthEndpoint"), tileAppHandler(healthCheck)).Methods("GET") + r.Handle(viper.GetString("HealthEndpoint"), tileAppHandler(healthCheck)).Methods(http.MethodGet) return r } From 0877037b9211e9aea395f242ae6729bbbd260b02 Mon Sep 17 00:00:00 2001 From: melinda Date: Wed, 17 Jan 2024 14:56:13 -0800 Subject: [PATCH 6/6] Update readme and add a test to make sure i am not lying --- README.md | 4 ++++ main.go | 5 +++++ main_test.go | 15 +++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/README.md b/README.md index badd6a1..a1b4cf0 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,10 @@ After start-up you can connect to the server and explore the published tables an To disable the web interface, supply the run time flag `--no-preview` +## Health Check Endpoint + +In order to run the server in an orchestrated environment, like Docker, it can be useful to have a health check endpoint. This is `/health` by default, and returns a `200 OK` if the server is responding to requests. To use a custom URL for the health check endpoint, supply the run time flag `-e` and your path. This setting will respect the base path setting, so if you choose a base path of `/example` and a health check endpoint of `/foo`, your health check URL becomes `/example/foo`. + ## Layers List A list of layers is available in JSON at: diff --git a/main.go b/main.go index fa065b9..11f4ee5 100644 --- a/main.go +++ b/main.go @@ -112,6 +112,7 @@ func main() { flagHelpOn := getopt.BoolLong("help", 'h', "display help output") flagVersionOn := getopt.BoolLong("version", 'v', "display version number") flagHidePreview := getopt.BoolLong("no-preview", 'n', "hide web interface") + flagHealthEndpoint := getopt.StringLong("health", 'e', "", "desired path to health endpoint, e.g. \"/health\"") getopt.Parse() if *flagHelpOn { @@ -147,6 +148,10 @@ func main() { viper.Set("ShowPreview", false) } + if *flagHealthEndpoint != "" { + viper.Set("HealthEndpoint", *flagHealthEndpoint) + } + // Report our status log.Infof("%s %s", programName, programVersion) log.Info("Run with --help parameter for commandline options") diff --git a/main_test.go b/main_test.go index c82a5fd..37c5d60 100644 --- a/main_test.go +++ b/main_test.go @@ -149,3 +149,18 @@ func TestHealthCustomUrl(t *testing.T) { // cleanup viper.Set("HealthEndpoint", "/health") } + +func TestHealthCustomUrlWithBasePath(t *testing.T) { + viper.Set("BasePath", "/foo") + viper.Set("HealthEndpoint", "/bar") + r := tileRouter() + request, _ := http.NewRequest("GET", "/foo/bar", nil) + response := httptest.NewRecorder() + r.ServeHTTP(response, request) + assert.Equal(t, 200, response.Code, "OK response is expected") + assert.Equal(t, "200 OK", string(response.Result().Status), "Response status should say ok") + + // cleanup + viper.Set("HealthEndpoint", "/health") + viper.Set("BasePath", "/") +}