Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configurable and very basic health check endpoint #203

Merged
merged 7 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
16 changes: 16 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -110,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 {
Expand Down Expand Up @@ -145,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")
Expand Down Expand Up @@ -391,6 +398,13 @@ 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)
w.Write([]byte("200 OK"))
return nil
}

/******************************************************************************/

// tileAppError is an optional error structure functions can return
Expand Down Expand Up @@ -488,6 +502,8 @@ func tileRouter() *mux.Router {
if viper.GetBool("EnableMetrics") {
r.Handle("/metrics", promhttp.Handler())
}

r.Handle(viper.GetString("HealthEndpoint"), tileAppHandler(healthCheck)).Methods(http.MethodGet)
return r
}

Expand Down
94 changes: 94 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"os"
Expand All @@ -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)
}

Expand Down Expand Up @@ -67,6 +69,98 @@ 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
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")

// 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")
}

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", "/")
}
2 changes: 1 addition & 1 deletion util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Loading