@loopback/health
Version:
An extension exposes health check related endpoints with LoopBack 4
158 lines (144 loc) • 4.04 kB
text/typescript
// 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
*/
({scope: BindingScope.SINGLETON})
class HealthController {
constructor(
(HealthBindings.HEALTH_CHECKER)
private healthChecker: HealthChecker,
) {}
(options.healthPath, spec)
async health((RestBindings.Http.RESPONSE) response: Response) {
const status = await this.healthChecker.getStatus();
return handleStatus(response, status);
}
(options.readyPath, spec)
async ready((RestBindings.Http.RESPONSE) response: Response) {
const status = await this.healthChecker.getReadinessStatus();
return handleStatus(response, status, 503);
}
(options.livePath, spec)
async live((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;
}