diff --git a/README.md b/README.md index 1e916cf..5c79ebe 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,12 @@ See the [GoDoc examples](https://godoc.org/github.com/heptiolabs/healthcheck) fo ```go health := healthcheck.NewHandler() ``` + or + ```go + liveURL := "/liveness" + readyURL := "/readiness" + health := healthcheck.NewHandlerCustomURL(liveURL, readyURL) + ``` - Configure some application-specific liveness checks (whether the app itself is unhealthy): ```go diff --git a/example_test.go b/example_test.go index 245c6c2..88d5381 100644 --- a/example_test.go +++ b/example_test.go @@ -163,10 +163,10 @@ func Example_metrics() { adminMux.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{})) // Expose a liveness check on /live - adminMux.HandleFunc("/live", health.LiveEndpoint) + adminMux.HandleFunc(oriLiveEndpoint, health.LiveEndpoint) // Expose a readiness check on /ready - adminMux.HandleFunc("/ready", health.ReadyEndpoint) + adminMux.HandleFunc(oriReadyEndpoint, health.ReadyEndpoint) // Make a request to the metrics endpoint and print the response. fmt.Println(dumpRequest(adminMux, "GET", "/metrics")) diff --git a/handler.go b/handler.go index 6ea9740..51638e3 100644 --- a/handler.go +++ b/handler.go @@ -20,6 +20,11 @@ import ( "sync" ) +const ( + oriLiveEndpoint = "/live" + oriReadyEndpoint = "/ready" +) + // basicHandler is a basic Handler implementation. type basicHandler struct { http.ServeMux @@ -34,8 +39,29 @@ func NewHandler() Handler { livenessChecks: make(map[string]Check), readinessChecks: make(map[string]Check), } - h.Handle("/live", http.HandlerFunc(h.LiveEndpoint)) - h.Handle("/ready", http.HandlerFunc(h.ReadyEndpoint)) + + h.Handle(oriLiveEndpoint, http.HandlerFunc(h.LiveEndpoint)) + h.Handle(oriReadyEndpoint, http.HandlerFunc(h.ReadyEndpoint)) + return h +} + +// NewHandlerCustomURL creates a new basic Handler with custom URL for liveness and readiness +func NewHandlerCustomURL(customLiveEndpoint, customReadyEndpoint string) Handler { + if customLiveEndpoint == "" { + customLiveEndpoint = oriLiveEndpoint + } + + if customReadyEndpoint == "" { + customReadyEndpoint = oriReadyEndpoint + } + + h := &basicHandler{ + livenessChecks: make(map[string]Check), + readinessChecks: make(map[string]Check), + } + + h.Handle(customLiveEndpoint, http.HandlerFunc(h.LiveEndpoint)) + h.Handle(customReadyEndpoint, http.HandlerFunc(h.ReadyEndpoint)) return h } diff --git a/handler_test.go b/handler_test.go index 2e894b4..c76564c 100644 --- a/handler_test.go +++ b/handler_test.go @@ -23,6 +23,20 @@ import ( "github.com/stretchr/testify/assert" ) +func checkEnpoint(t *testing.T, handler Handler, httpMethod, testPath, expectedBody string, expectedHTTPCode int) { + req, err := http.NewRequest(httpMethod, testPath, nil) + assert.NoError(t, err) + + reqStr := httpMethod + " " + testPath + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + assert.Equal(t, expectedHTTPCode, rr.Code, "wrong code for %q", reqStr) + + if expectedBody != "" { + assert.Equal(t, expectedBody, rr.Body.String(), "wrong body for %q", reqStr) + } +} + func TestNewHandler(t *testing.T) { tests := []struct { name string @@ -137,17 +151,63 @@ func TestNewHandler(t *testing.T) { }) } - req, err := http.NewRequest(tt.method, tt.path, nil) - assert.NoError(t, err) + checkEnpoint(t, h, tt.method, tt.path, tt.expectBody, tt.expect) + }) + } +} - reqStr := tt.method + " " + tt.path - rr := httptest.NewRecorder() - h.ServeHTTP(rr, req) - assert.Equal(t, tt.expect, rr.Code, "wrong code for %q", reqStr) +func TestNewHandlerCustomURL(t *testing.T) { + liveEndpoint := "/liveness" + readyEndpoint := "/readiness" + emptyEndpoint := "" - if tt.expectBody != "" { - assert.Equal(t, tt.expectBody, rr.Body.String(), "wrong body for %q", reqStr) - } + tests := []struct { + name string + method string + customLiveEndpoint string + custoReadyEndpoint string + expectedLivenessPath string + expectedReadinessPath string + expect int + expectBody string + }{ + { + name: "using only custom liveness endpoint, call to custom liveness endpoint and original readiness endpoint should succeed", + method: http.MethodGet, + customLiveEndpoint: liveEndpoint, + custoReadyEndpoint: emptyEndpoint, + expectedLivenessPath: liveEndpoint, + expectedReadinessPath: "/ready", + expect: http.StatusOK, + expectBody: "{}\n", + }, + { + name: "using only custom readiness endpoint, call to custom readiness endpoint and original liveness endpoint should succeed", + method: http.MethodGet, + customLiveEndpoint: emptyEndpoint, + custoReadyEndpoint: readyEndpoint, + expectedLivenessPath: "/live", + expectedReadinessPath: readyEndpoint, + expect: http.StatusOK, + expectBody: "{}\n", + }, + { + name: "with no custom liveness endpoints, the default hanlder should be called and hence original liveness and readiness endpoint should succeed", + method: http.MethodGet, + customLiveEndpoint: emptyEndpoint, + custoReadyEndpoint: emptyEndpoint, + expectedLivenessPath: "/live", + expectedReadinessPath: "/ready", + expect: http.StatusOK, + expectBody: "{}\n", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := NewHandlerCustomURL(tt.customLiveEndpoint, tt.custoReadyEndpoint) + checkEnpoint(t, h, tt.method, tt.expectedLivenessPath, tt.expectBody, tt.expect) + checkEnpoint(t, h, tt.method, tt.expectedReadinessPath, tt.expectBody, tt.expect) }) } }