Use maintenance component to throw 503s when some components are unavailable
This commit is contained in:
parent
a3ebf46b54
commit
c9fed2d873
@ -214,11 +214,6 @@ export default abstract class Application implements Extendable<ApplicationCompo
|
||||
});
|
||||
});
|
||||
|
||||
// Start components
|
||||
for (const component of this.components) {
|
||||
await component.start?.(app);
|
||||
}
|
||||
|
||||
// Components routes
|
||||
for (const component of this.components) {
|
||||
if (component.initRoutes) {
|
||||
@ -234,6 +229,11 @@ export default abstract class Application implements Extendable<ApplicationCompo
|
||||
component.setCurrentRouter(null);
|
||||
}
|
||||
|
||||
// Start components
|
||||
for (const component of this.components) {
|
||||
await component.start?.(app);
|
||||
}
|
||||
|
||||
// Routes
|
||||
this.routes(initRouter, handleRouter);
|
||||
|
||||
@ -373,6 +373,10 @@ export default abstract class Application implements Extendable<ApplicationCompo
|
||||
return this.cacheProvider || null;
|
||||
}
|
||||
|
||||
public getComponents(): ApplicationComponent[] {
|
||||
return [...this.components];
|
||||
}
|
||||
|
||||
public as<C extends ApplicationComponent | WebSocketListener<Application>>(type: Type<C>): C {
|
||||
const module = this.components.find(component => component.constructor === type) ||
|
||||
Object.values(this.webSocketListeners).find(listener => listener.constructor === type);
|
||||
|
@ -15,14 +15,18 @@ export default abstract class ApplicationComponent {
|
||||
|
||||
public async init?(): Promise<void>;
|
||||
|
||||
public async start?(expressApp: Express): Promise<void>;
|
||||
|
||||
public async initRoutes?(router: Router): Promise<void>;
|
||||
|
||||
public async handleRoutes?(router: Router): Promise<void>;
|
||||
|
||||
public async start?(expressApp: Express): Promise<void>;
|
||||
|
||||
public async stop?(): Promise<void>;
|
||||
|
||||
public isReady(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected async prepare(name: string, prepare: () => Promise<void>): Promise<void> {
|
||||
let err;
|
||||
do {
|
||||
|
@ -21,6 +21,7 @@ import FormHelperComponent from "./components/FormHelperComponent.js";
|
||||
import FrontendToolsComponent from "./components/FrontendToolsComponent.js";
|
||||
import LogRequestsComponent from "./components/LogRequestsComponent.js";
|
||||
import MailComponent from "./components/MailComponent.js";
|
||||
import MaintenanceComponent from "./components/MaintenanceComponent.js";
|
||||
import MysqlComponent from "./components/MysqlComponent.js";
|
||||
import PreviousUrlComponent from "./components/PreviousUrlComponent.js";
|
||||
import RedisComponent from "./components/RedisComponent.js";
|
||||
@ -79,6 +80,9 @@ export default class TestApp extends Application {
|
||||
// Static files
|
||||
this.use(new ServeStaticDirectoryComponent('public'));
|
||||
|
||||
// Maintenance
|
||||
this.use(new MaintenanceComponent());
|
||||
|
||||
// Dynamic views and routes
|
||||
const intermediateDirectory = 'build';
|
||||
this.use(new FrontendToolsComponent(
|
||||
|
@ -1,35 +1,28 @@
|
||||
import config from "config";
|
||||
import {NextFunction, Request, Response, Router} from "express";
|
||||
|
||||
import Application from "../Application.js";
|
||||
import ApplicationComponent from "../ApplicationComponent.js";
|
||||
import {ServiceUnavailableHttpError} from "../HttpError.js";
|
||||
|
||||
export default class MaintenanceComponent extends ApplicationComponent {
|
||||
private readonly application: Application;
|
||||
private readonly canServe: () => boolean;
|
||||
|
||||
public constructor(application: Application, canServe: () => boolean) {
|
||||
super();
|
||||
this.application = application;
|
||||
this.canServe = canServe;
|
||||
}
|
||||
|
||||
public async handleRoutes(router: Router): Promise<void> {
|
||||
public async initRoutes(router: Router): Promise<void> {
|
||||
router.use((req: Request, res: Response, next: NextFunction) => {
|
||||
if (res.headersSent) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (!this.application.isReady()) {
|
||||
if (!this.getApp().isReady()) {
|
||||
res.header({'Retry-After': 60});
|
||||
res.locals.refresh_after = 5;
|
||||
throw new ServiceUnavailableHttpError(`${config.get('app.name')} is readying up. Please wait a few seconds...`);
|
||||
}
|
||||
|
||||
if (!this.canServe()) {
|
||||
res.locals.refresh_after = 30;
|
||||
throw new ServiceUnavailableHttpError(`${config.get('app.name')} is unavailable due to failure of dependent services.`);
|
||||
for (const component of this.getApp().getComponents()) {
|
||||
if (!component.isReady()) {
|
||||
res.header({'Retry-After': 60});
|
||||
res.locals.refresh_after = 30;
|
||||
throw new ServiceUnavailableHttpError(`${config.get('app.name')} is unavailable due to failure of dependent services.`);
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
|
@ -12,7 +12,7 @@ export default class MysqlComponent extends ApplicationComponent {
|
||||
await MysqlConnectionManager.endPool();
|
||||
}
|
||||
|
||||
public canServe(): boolean {
|
||||
public isReady(): boolean {
|
||||
return MysqlConnectionManager.isReady();
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import {logger} from "../Logger.js";
|
||||
|
||||
export default class RedisComponent extends ApplicationComponent implements CacheProvider {
|
||||
private redisClient?: RedisClient;
|
||||
private store?: Store;
|
||||
private store: Store = new RedisStore(this);
|
||||
|
||||
public async start(_app: Express): Promise<void> {
|
||||
this.redisClient = redis.createClient(config.get('redis.port'), config.get('redis.host'), {
|
||||
@ -18,8 +18,6 @@ export default class RedisComponent extends ApplicationComponent implements Cach
|
||||
this.redisClient.on('error', (err: Error) => {
|
||||
logger.error(err, 'An error occurred with redis.');
|
||||
});
|
||||
|
||||
this.store = new RedisStore(this);
|
||||
}
|
||||
|
||||
public async stop(): Promise<void> {
|
||||
@ -30,11 +28,10 @@ export default class RedisComponent extends ApplicationComponent implements Cach
|
||||
}
|
||||
|
||||
public getStore(): Store {
|
||||
if (!this.store) throw `Redis store was not initialized.`;
|
||||
return this.store;
|
||||
}
|
||||
|
||||
public canServe(): boolean {
|
||||
public isReady(): boolean {
|
||||
return this.redisClient !== undefined && this.redisClient.connected;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user