feat: add backend status check handler middleware
The statusCheckHandler is a simple express middleware that helps implement healthcheck/readiness endpoints. It can provided an optional status function that should return a promise that resolves in either true or false. The response will be either a 200 or 503 respectively. Any errors that occur will be forwarded to next.
This commit is contained in:
@@ -17,3 +17,4 @@
|
||||
export * from './errorHandler';
|
||||
export * from './notFoundHandler';
|
||||
export * from './requestLoggingHandler';
|
||||
export * from './statusCheckHandler';
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
import request from 'supertest';
|
||||
import { statusCheckHandler } from './statusCheckHandler';
|
||||
|
||||
describe('statusCheckHandler', () => {
|
||||
it('gives status 200 when using default', async () => {
|
||||
const app = express();
|
||||
app.use('/healthcheck', await statusCheckHandler());
|
||||
|
||||
const response = await request(app).get('/healthcheck');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.text).toBe(JSON.stringify({ status: 'ok' }));
|
||||
});
|
||||
|
||||
it('gives status 200 when status function returns true', async () => {
|
||||
const app = express();
|
||||
const status = { foo: 'bar' };
|
||||
const statusCheck = () => Promise.resolve(status);
|
||||
app.use('/healthcheck', await statusCheckHandler({ statusCheck }));
|
||||
|
||||
const response = await request(app).get('/healthcheck');
|
||||
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.text).toBe(JSON.stringify(status));
|
||||
});
|
||||
|
||||
it('gives status 500 when status check throws an error', async () => {
|
||||
const app = express();
|
||||
const statusCheck = () => {
|
||||
throw Error('error!');
|
||||
};
|
||||
app.use('/healthcheck', await statusCheckHandler({ statusCheck }));
|
||||
|
||||
const response = await request(app).get('/healthcheck');
|
||||
|
||||
expect(response.status).toBe(500);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { NextFunction, Request, Response, RequestHandler } from 'express';
|
||||
|
||||
export type StatusCheck = () => Promise<any>;
|
||||
|
||||
export interface StatusCheckHandlerOptions {
|
||||
/**
|
||||
* Optional status function which returns a message.
|
||||
*/
|
||||
statusCheck?: StatusCheck;
|
||||
}
|
||||
|
||||
/**
|
||||
* Express middleware for status checks.
|
||||
*
|
||||
* This is commonly used to implement healthcheck and readiness routes.
|
||||
*
|
||||
* @param options An optional configuration object.
|
||||
* @returns An Express error request handler
|
||||
*/
|
||||
export async function statusCheckHandler(
|
||||
options: StatusCheckHandlerOptions = {},
|
||||
): Promise<RequestHandler> {
|
||||
const statusCheck: StatusCheck = options.statusCheck
|
||||
? options.statusCheck
|
||||
: () => Promise.resolve({ status: 'ok' });
|
||||
|
||||
return async (_request: Request, response: Response, next: NextFunction) => {
|
||||
try {
|
||||
const status = await statusCheck();
|
||||
response.status(200).header('').send(status);
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import compression from 'compression';
|
||||
import cors from 'cors';
|
||||
import express, { Router } from 'express';
|
||||
import express, { Router, RequestHandler } from 'express';
|
||||
import helmet from 'helmet';
|
||||
import { Server } from 'http';
|
||||
import stoppable from 'stoppable';
|
||||
@@ -40,7 +40,7 @@ export class ServiceBuilderImpl implements ServiceBuilder {
|
||||
private host: string | undefined;
|
||||
private logger: Logger | undefined;
|
||||
private corsOptions: cors.CorsOptions | undefined;
|
||||
private routers: [string, Router][];
|
||||
private routers: [string, Router | RequestHandler][];
|
||||
// Reference to the module where builder is created - needed for hot module
|
||||
// reloading
|
||||
private module: NodeModule;
|
||||
@@ -92,7 +92,7 @@ export class ServiceBuilderImpl implements ServiceBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
addRouter(root: string, router: Router): ServiceBuilder {
|
||||
addRouter(root: string, router: Router | RequestHandler): ServiceBuilder {
|
||||
this.routers.push([root, router]);
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import cors from 'cors';
|
||||
import { Router } from 'express';
|
||||
import { Router, RequestHandler } from 'express';
|
||||
import { Server } from 'http';
|
||||
import { Logger } from 'winston';
|
||||
|
||||
@@ -64,7 +64,7 @@ export type ServiceBuilder = {
|
||||
* @param root The root URL to bind to (e.g. "/api/function1")
|
||||
* @param router An express router
|
||||
*/
|
||||
addRouter(root: string, router: Router): ServiceBuilder;
|
||||
addRouter(root: string, router: Router | RequestHandler): ServiceBuilder;
|
||||
|
||||
/**
|
||||
* Starts the server using the given settings.
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
import {
|
||||
createServiceBuilder,
|
||||
getRootLogger,
|
||||
statusCheckHandler,
|
||||
useHotMemoize,
|
||||
} from '@backstage/backend-common';
|
||||
import { ConfigReader, AppConfig } from '@backstage/config';
|
||||
@@ -73,6 +74,7 @@ async function main() {
|
||||
|
||||
const service = createServiceBuilder(module)
|
||||
.loadConfig(configReader)
|
||||
.addRouter('/healthcheck', await statusCheckHandler())
|
||||
.addRouter('/catalog', await catalog(catalogEnv))
|
||||
.addRouter('/rollbar', await rollbar(rollbarEnv))
|
||||
.addRouter('/scaffolder', await scaffolder(scaffolderEnv))
|
||||
|
||||
Reference in New Issue
Block a user