UNPKG

@loopback/health

Version:

An extension exposes health check related endpoints with LoopBack 4

158 lines (144 loc) 4.04 kB
// Copyright IBM Corp. and LoopBack contributors 2019. All Rights Reserved. // Node module: @loopback/health // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT import {HealthChecker, HealthStatus, State} from '@cloudnative/health'; import {BindingScope, Constructor, inject, injectable} from '@loopback/core'; import { get, OperationObject, Response, ResponseObject, RestBindings, SchemaObject, } from '@loopback/rest'; import {HealthBindings} from '../keys'; import {DEFAULT_HEALTH_OPTIONS, HealthOptions} from '../types'; function getHealthResponseObject() { /** * OpenAPI definition of health response schema */ const HEALTH_RESPONSE_SCHEMA: SchemaObject = { type: 'object', properties: { status: {type: 'string'}, checks: { type: 'array', items: { type: 'object', properties: { name: {type: 'string'}, state: {type: 'string'}, data: { type: 'object', properties: { reason: {type: 'string'}, }, }, }, }, }, }, }; /** * OpenAPI definition of health response */ const HEALTH_RESPONSE: ResponseObject = { description: 'Health Response', content: { 'application/json': { schema: HEALTH_RESPONSE_SCHEMA, }, }, }; return HEALTH_RESPONSE; } /** * OpenAPI spec for health endpoints */ const HEALTH_SPEC: OperationObject = { // response object needs to be cloned because the oas-validator throws an // error if the same object is referenced twice responses: { '200': getHealthResponseObject(), '500': getHealthResponseObject(), '503': getHealthResponseObject(), }, }; /** * OpenAPI spec to hide endpoints */ const HIDDEN_SPEC: OperationObject = { responses: {}, 'x-visibility': 'undocumented', }; /** * A factory function to create a controller class for health endpoints. This * makes it possible to customize decorations such as `@get` with a dynamic * path value not known at compile time. * * @param options - Options for health endpoints */ export function createHealthController( options: HealthOptions = DEFAULT_HEALTH_OPTIONS, ): Constructor<unknown> { const spec = options.openApiSpec ? HEALTH_SPEC : HIDDEN_SPEC; /** * Controller for health endpoints */ @injectable({scope: BindingScope.SINGLETON}) class HealthController { constructor( @inject(HealthBindings.HEALTH_CHECKER) private healthChecker: HealthChecker, ) {} @get(options.healthPath, spec) async health(@inject(RestBindings.Http.RESPONSE) response: Response) { const status = await this.healthChecker.getStatus(); return handleStatus(response, status); } @get(options.readyPath, spec) async ready(@inject(RestBindings.Http.RESPONSE) response: Response) { const status = await this.healthChecker.getReadinessStatus(); return handleStatus(response, status, 503); } @get(options.livePath, spec) async live(@inject(RestBindings.Http.RESPONSE) response: Response) { const status = await this.healthChecker.getLivenessStatus(); return handleStatus(response, status, 500); } } return HealthController; } /** * Create response for the given status * @param response - Http response * @param status - Health status * @param failingCode - Status code for `DOWN` */ function handleStatus( response: Response, status: HealthStatus, failingCode: 500 | 503 = 503, ) { let statusCode = 200; switch (status.status) { case State.STARTING: statusCode = 503; break; case State.UP: statusCode = 200; break; case State.DOWN: statusCode = failingCode; break; case State.STOPPING: statusCode = 503; break; case State.STOPPED: statusCode = 503; break; } response.status(statusCode).send(status); return response; }