Go Health Checker: Building a Resilient Application Endpoint
Modern applications, especially microservices, need to be robust and reliable. A crucial aspect of this is ensuring that services are healthy and responsive. Implementing health checks allows external systems, like load balancers or orchestration platforms, to monitor the status of your application and react accordingly, such as rerouting traffic away from unhealthy instances.
Problem Description
Your task is to implement a simple HTTP health check endpoint for a Go application. This endpoint will be responsible for reporting the overall health status of the application.
Key Requirements:
- HTTP Endpoint: Create an HTTP server that listens on a specific port (e.g., 8080).
- Health Check Path: Define an HTTP GET endpoint, typically
/health, that external systems can query. - Status Reporting:
- If the application is healthy, the
/healthendpoint should return an HTTP status code of200 OKand a JSON response indicating "status": "healthy". - If the application is unhealthy, the
/healthendpoint should return an HTTP status code of503 Service Unavailableand a JSON response indicating "status": "unhealthy".
- If the application is healthy, the
- Simulating Unhealthiness: For demonstration purposes, you should be able to toggle the application's health status. This could be through a simple mechanism like a global variable or by having a separate administrative endpoint (though for this challenge, a global variable is sufficient).
- Graceful Shutdown: The server should handle interrupt signals (like
SIGINTfrom Ctrl+C) gracefully, allowing it to shut down cleanly.
Expected Behavior:
- When the application starts, it should be considered healthy by default.
- When a request is made to
/healthwhile the application is healthy, a200 OKwith{"status": "healthy"}should be returned. - When the application's health is toggled to unhealthy, a request to
/healthshould return503 Service Unavailablewith{"status": "unhealthy"}. - When the application's health is toggled back to healthy,
/healthshould return200 OKagain. - Upon receiving an interrupt signal, the server should stop listening for new connections and exit.
Edge Cases to Consider:
- What happens if multiple requests are made concurrently to the health check endpoint?
- How can we ensure the status change is reflected immediately?
Examples
Example 1: Healthy Application
Input:
GET /health HTTP/1.1
Host: localhost:8080
Output:
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 20
{"status": "healthy"}
Example 2: Unhealthy Application
Assume the health status has been toggled to unhealthy.
Input:
GET /health HTTP/1.1
Host: localhost:8080
Output:
HTTP/1.1 503 Service Unavailable
Content-Type: application/json
Content-Length: 21
{"status": "unhealthy"}
Example 3: Toggling Health Status (Conceptual - no direct HTTP interaction shown)
This illustrates how the internal state might change.
- Initial State:
isHealthy = true - Action: Code logic changes
isHealthytofalse. - Subsequent Request:
GET /healthnow returns503 Service Unavailable. - Action: Code logic changes
isHealthyback totrue. - Subsequent Request:
GET /healthnow returns200 OK.
Constraints
- The HTTP server must listen on port
8080. - The health check endpoint path must be
/health. - The JSON response must strictly adhere to the format:
{"status": "healthy"}or{"status": "unhealthy"}. - The application should handle at least 100 concurrent requests to the
/healthendpoint without significant latency degradation (for this simplified version, basic Gonet/httpconcurrency is acceptable). - The solution must be written in Go.
Notes
- You'll likely want to use the built-in
net/httppackage in Go. - Consider how to manage the health status safely across potential concurrent requests. A
sync.Mutexmight be useful if you decide to implement a toggle mechanism that modifies a shared state. - For graceful shutdown, look into
os/signaland how to pass a context tohttp.Server.ListenAndServe. - Think about how you would extend this to check the health of external dependencies (databases, other microservices) if this were a more complex scenario.