From 0e96a285acc193bb7ebe76a148423cf63739f8ee Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Sat, 11 Jul 2020 11:46:16 +0200 Subject: [PATCH] Properly split routing in 2 steps: init, handle --- src/Application.ts | 36 ++++++++++--------- src/ApplicationComponent.ts | 13 +++++-- src/auth/AuthComponent.ts | 7 ++-- src/components/AutoUpdateComponent.ts | 28 ++++++--------- src/components/CsrfProtectionComponent.ts | 7 ++-- src/components/ExpressAppComponent.ts | 8 ++--- src/components/FormHelperComponent.ts | 8 ++--- src/components/LogRequestsComponent.ts | 8 ++--- src/components/MailComponent.ts | 4 +-- src/components/MaintenanceComponent.ts | 8 ++--- src/components/MysqlComponent.ts | 4 +-- src/components/NunjucksComponent.ts | 15 ++++---- src/components/RedirectBackComponent.ts | 8 ++--- src/components/RedisComponent.ts | 4 +-- .../ServeStaticDirectoryComponent.ts | 4 +-- src/components/SessionComponent.ts | 8 ++--- src/components/WebSocketServerComponent.ts | 4 +-- 17 files changed, 77 insertions(+), 97 deletions(-) diff --git a/src/Application.ts b/src/Application.ts index 2677958..17a23f5 100644 --- a/src/Application.ts +++ b/src/Application.ts @@ -64,16 +64,14 @@ export default abstract class Application { // Init express const app = express(); - const mainRouter = express.Router(); - const fileUploadFormRouter = express.Router(); - app.use(fileUploadFormRouter); - app.use(mainRouter); + const initRouter = express.Router(); + const handleRouter = express.Router(); + app.use(initRouter); + app.use(handleRouter); - // Error handler + // Error handlers app.use((err: any, req: Request, res: Response, next: NextFunction) => { - if (res.headersSent) { - return next(err); - } + if (res.headersSent) return next(err); if (err instanceof ValidationBag) { res.format({ @@ -135,13 +133,19 @@ export default abstract class Application { }); }); - // Start all components + // Start components for (const component of this.components) { - await component.start(app, mainRouter); + await component.start(app); + } + + // Components routes + for (const component of this.components) { + await component.init(initRouter); + await component.handle(handleRouter); } // Routes - this.routes(mainRouter, fileUploadFormRouter); + this.routes(initRouter, handleRouter); this.ready = true; } @@ -177,10 +181,10 @@ export default abstract class Application { Logger.info(`${this.constructor.name} v${this.version} - bye`); } - private routes(mainRootRouter: Router, rootFileUploadFormRouter: Router) { + private routes(initRouter: Router, handleRouter: Router) { for (const controller of this.controllers) { if (controller.hasGlobalHandlers()) { - controller.setupGlobalHandlers(mainRootRouter); + controller.setupGlobalHandlers(handleRouter); Logger.info(`Registered global middlewares for controller ${controller.constructor.name}`); } @@ -188,13 +192,13 @@ export default abstract class Application { for (const controller of this.controllers) { const {mainRouter, fileUploadFormRouter} = controller.setupRoutes(); - mainRootRouter.use(controller.getRoutesPrefix(), mainRouter); - rootFileUploadFormRouter.use(controller.getRoutesPrefix(), fileUploadFormRouter); + initRouter.use(controller.getRoutesPrefix(), fileUploadFormRouter); + handleRouter.use(controller.getRoutesPrefix(), mainRouter); Logger.info(`> Registered routes for controller ${controller.constructor.name}`); } - mainRootRouter.use((req: Request) => { + handleRouter.use((req: Request) => { throw new NotFoundHttpError('page', req.originalUrl); }); } diff --git a/src/ApplicationComponent.ts b/src/ApplicationComponent.ts index c34b1cf..200c5fb 100644 --- a/src/ApplicationComponent.ts +++ b/src/ApplicationComponent.ts @@ -7,9 +7,18 @@ export default abstract class ApplicationComponent { private val?: T; protected app?: Application; - public abstract async start(app: Express, router: Router): Promise; + public async start(app: Express): Promise { + } - public abstract async stop(): Promise; + public async init(router: Router): Promise { + } + + public async handle(router: Router): Promise { + } + + public async stop(): Promise { + + } protected export(val: T) { this.val = val; diff --git a/src/auth/AuthComponent.ts b/src/auth/AuthComponent.ts index 3e5d8dd..a3db2d5 100644 --- a/src/auth/AuthComponent.ts +++ b/src/auth/AuthComponent.ts @@ -1,5 +1,5 @@ import ApplicationComponent from "../ApplicationComponent"; -import {Express, NextFunction, Request, Response, Router} from "express"; +import {NextFunction, Request, Response, Router} from "express"; import AuthGuard from "./AuthGuard"; import Controller from "../Controller"; import {ForbiddenHttpError} from "../HttpError"; @@ -12,16 +12,13 @@ export default class AuthComponent extends ApplicationComponent { this.authGuard = authGuard; } - public async start(app: Express, router: Router): Promise { + public async init(router: Router): Promise { router.use(async (req, res, next) => { req.authGuard = this.authGuard; res.locals.user = await req.authGuard.getUserForSession(req.session!); next(); }); } - - public async stop(): Promise { - } } export const REQUIRE_REQUEST_AUTH_MIDDLEWARE = async (req: Request, res: Response, next: NextFunction): Promise => { diff --git a/src/components/AutoUpdateComponent.ts b/src/components/AutoUpdateComponent.ts index c6b75b3..cc275ca 100644 --- a/src/components/AutoUpdateComponent.ts +++ b/src/components/AutoUpdateComponent.ts @@ -1,20 +1,17 @@ -import {Express, Router} from "express"; +import {Router} from "express"; import config from "config"; import * as child_process from "child_process"; import ApplicationComponent from "../ApplicationComponent"; import {ForbiddenHttpError} from "../HttpError"; import Logger from "../Logger"; -const ROUTE = '/update/push.json'; - export default class AutoUpdateComponent extends ApplicationComponent { - public async start(app: Express, router: Router): Promise { - router.post(ROUTE, (req, res, next) => { + public async init(router: Router): Promise { + router.post('/update/push.json', (req, res) => { const token = req.header('X-Gitlab-Token'); if (!token || token !== config.get('gitlab_webhook_token')) throw new ForbiddenHttpError('Invalid token', req.url); - this.update(req.body.checkout_sha) - .catch(Logger.error); + this.update(req.body).catch(Logger.error); res.json({ 'status': 'ok', @@ -22,20 +19,14 @@ export default class AutoUpdateComponent extends ApplicationComponent { }); } - public async stop(): Promise { - } - - private async update(checkout_sha: string) { - await this.app!.stop(); + private async update(params: any) { + Logger.info('Update params:', params); try { Logger.info('Starting auto update...'); // Fetch - await this.runCommand(`git fetch`); - - // Checkout new source - await this.runCommand(`git checkout ${checkout_sha}`); + await this.runCommand(`git pull`); // Install new dependencies await this.runCommand(`yarn install --production=false`); @@ -43,13 +34,16 @@ export default class AutoUpdateComponent extends ApplicationComponent { // Process assets await this.runCommand(`yarn dist`); + // Stop app + await this.app!.stop(); + Logger.info('Success!'); } catch (e) { Logger.error(e, 'An error occurred while running the auto update.'); } } - private async runCommand(command: string) { + private async runCommand(command: string): Promise { Logger.info(`> ${command}`); Logger.info(child_process.execSync(command).toString()); } diff --git a/src/components/CsrfProtectionComponent.ts b/src/components/CsrfProtectionComponent.ts index 5650a57..621e155 100644 --- a/src/components/CsrfProtectionComponent.ts +++ b/src/components/CsrfProtectionComponent.ts @@ -1,5 +1,5 @@ import ApplicationComponent from "../ApplicationComponent"; -import {Express, Router} from "express"; +import {Router} from "express"; import crypto from "crypto"; import {BadRequestError} from "../HttpError"; @@ -10,7 +10,7 @@ export default class CsrfProtectionComponent extends ApplicationComponent this.routeExcluders.push(excluder); } - public async start(app: Express, router: Router): Promise { + public async handle(router: Router): Promise { router.use(async (req, res, next) => { for (const excluder of CsrfProtectionComponent.routeExcluders) { if (excluder(req.path)) return next(); @@ -46,9 +46,6 @@ export default class CsrfProtectionComponent extends ApplicationComponent next(); }); } - - public async stop(): Promise { - } } class InvalidCsrfTokenError extends BadRequestError { diff --git a/src/components/ExpressAppComponent.ts b/src/components/ExpressAppComponent.ts index 91f4204..9c61146 100644 --- a/src/components/ExpressAppComponent.ts +++ b/src/components/ExpressAppComponent.ts @@ -1,9 +1,7 @@ import ApplicationComponent from "../ApplicationComponent"; -import express, {Express, RequestHandler, Router} from "express"; +import express, {Express, Router} from "express"; import Logger from "../Logger"; import {Server} from "http"; -import {IncomingForm} from "formidable"; -import {FileError, ValidationBag} from "../db/Validator"; import compression from "compression"; export default class ExpressAppComponent extends ApplicationComponent { @@ -15,11 +13,13 @@ export default class ExpressAppComponent extends ApplicationComponent { this.port = port; } - public async start(app: Express, router: Router): Promise { + public async start(app: Express): Promise { this.server = app.listen(this.port, 'localhost', () => { Logger.info(`Web server running on localhost:${this.port}.`); }); + } + public async init(router: Router): Promise { router.use(express.json()); router.use(express.urlencoded({ extended: true, diff --git a/src/components/FormHelperComponent.ts b/src/components/FormHelperComponent.ts index 607e223..4937107 100644 --- a/src/components/FormHelperComponent.ts +++ b/src/components/FormHelperComponent.ts @@ -1,8 +1,8 @@ import ApplicationComponent from "../ApplicationComponent"; -import {Express, Router} from "express"; +import {Router} from "express"; export default class FormHelperComponent extends ApplicationComponent { - public async start(app: Express, router: Router): Promise { + public async init(router: Router): Promise { router.use((req, res, next) => { if (!req.session) { throw new Error('Session is unavailable.'); @@ -41,8 +41,4 @@ export default class FormHelperComponent extends ApplicationComponent { next(); }); } - - public async stop(): Promise { - } - } \ No newline at end of file diff --git a/src/components/LogRequestsComponent.ts b/src/components/LogRequestsComponent.ts index 7ea9ea9..daf1a3e 100644 --- a/src/components/LogRequestsComponent.ts +++ b/src/components/LogRequestsComponent.ts @@ -1,7 +1,7 @@ import ApplicationComponent from "../ApplicationComponent"; import onFinished from "on-finished"; import Logger from "../Logger"; -import {Express, Request, Response, Router} from "express"; +import {Request, Response, Router} from "express"; export default class LogRequestsComponent extends ApplicationComponent { private static fullRequests: boolean = false; @@ -51,7 +51,7 @@ export default class LogRequestsComponent extends ApplicationComponent { return ''; } - public async start(app: Express, router: Router): Promise { + public async init(router: Router): Promise { router.use((req, res, next) => { onFinished(res, (err) => { if (!err) { @@ -61,8 +61,4 @@ export default class LogRequestsComponent extends ApplicationComponent { next(); }); } - - public async stop(): Promise { - } - } \ No newline at end of file diff --git a/src/components/MailComponent.ts b/src/components/MailComponent.ts index e204b14..20f2db7 100644 --- a/src/components/MailComponent.ts +++ b/src/components/MailComponent.ts @@ -1,9 +1,9 @@ import ApplicationComponent from "../ApplicationComponent"; -import {Express, Router} from "express"; +import {Express} from "express"; import Mail from "../Mail"; export default class MailComponent extends ApplicationComponent { - public async start(app: Express, router: Router): Promise { + public async start(app: Express): Promise { await this.prepare('Mail connection', () => Mail.prepare()); } diff --git a/src/components/MaintenanceComponent.ts b/src/components/MaintenanceComponent.ts index fc766f4..08442b6 100644 --- a/src/components/MaintenanceComponent.ts +++ b/src/components/MaintenanceComponent.ts @@ -1,5 +1,5 @@ import ApplicationComponent from "../ApplicationComponent"; -import {Express, NextFunction, Request, Response, Router} from "express"; +import {NextFunction, Request, Response, Router} from "express"; import {ServiceUnavailableHttpError} from "../HttpError"; import Application from "../Application"; import config from "config"; @@ -14,7 +14,7 @@ export default class MaintenanceComponent extends ApplicationComponent { this.canServe = canServe; } - public async start(app: Express, router: Router): Promise { + public async handle(router: Router): Promise { router.use((req: Request, res: Response, next: NextFunction) => { if (res.headersSent) { return next(); @@ -34,8 +34,4 @@ export default class MaintenanceComponent extends ApplicationComponent { next(); }); } - - public async stop(): Promise { - } - } \ No newline at end of file diff --git a/src/components/MysqlComponent.ts b/src/components/MysqlComponent.ts index 0bf03e6..a1ab499 100644 --- a/src/components/MysqlComponent.ts +++ b/src/components/MysqlComponent.ts @@ -1,9 +1,9 @@ import ApplicationComponent from "../ApplicationComponent"; -import {Express, Router} from "express"; +import {Express} from "express"; import MysqlConnectionManager from "../db/MysqlConnectionManager"; export default class MysqlComponent extends ApplicationComponent { - public async start(app: Express, router: Router): Promise { + public async start(app: Express): Promise { await this.prepare('Mysql connection', () => MysqlConnectionManager.prepare()); } diff --git a/src/components/NunjucksComponent.ts b/src/components/NunjucksComponent.ts index f49c860..b887506 100644 --- a/src/components/NunjucksComponent.ts +++ b/src/components/NunjucksComponent.ts @@ -1,4 +1,4 @@ -import nunjucks from "nunjucks"; +import nunjucks, {Environment} from "nunjucks"; import config from "config"; import {Express, Router} from "express"; import ApplicationComponent from "../ApplicationComponent"; @@ -7,13 +7,14 @@ import {ServerError} from "../HttpError"; export default class NunjucksComponent extends ApplicationComponent { private readonly viewsPath: string; + private env?: Environment; constructor(viewsPath: string = 'views') { super(); this.viewsPath = viewsPath; } - public async start(app: Express, router: Router): Promise { + public async start(app: Express): Promise { let coreVersion = 'unknown'; try { coreVersion = require('../../package.json').version; @@ -24,7 +25,7 @@ export default class NunjucksComponent extends ApplicationComponent { } } - const env = nunjucks.configure(this.viewsPath, { + this.env = nunjucks.configure(this.viewsPath, { autoescape: true, express: app, noCache: !config.get('view.cache'), @@ -41,9 +42,11 @@ export default class NunjucksComponent extends ApplicationComponent { return v.toString(16); }); app.set('view engine', 'njk'); + } + public async init(router: Router): Promise { router.use((req, res, next) => { - req.env = env; + req.env = this.env!; res.locals.url = req.url; res.locals.params = () => req.params; @@ -52,8 +55,4 @@ export default class NunjucksComponent extends ApplicationComponent { next(); }); } - - public async stop(): Promise { - } - } \ No newline at end of file diff --git a/src/components/RedirectBackComponent.ts b/src/components/RedirectBackComponent.ts index c3b9712..3b11582 100644 --- a/src/components/RedirectBackComponent.ts +++ b/src/components/RedirectBackComponent.ts @@ -1,11 +1,11 @@ import ApplicationComponent from "../ApplicationComponent"; -import {Express, Router} from "express"; +import {Router} from "express"; import onFinished from "on-finished"; import Logger from "../Logger"; import {ServerError} from "../HttpError"; export default class RedirectBackComponent extends ApplicationComponent { - public async start(app: Express, router: Router): Promise { + public async init(router: Router): Promise { router.use((req, res, next) => { if (!req.session) { throw new Error('Session is unavailable.'); @@ -36,8 +36,4 @@ export default class RedirectBackComponent extends ApplicationComponent { next(); }); } - - public async stop(): Promise { - } - } \ No newline at end of file diff --git a/src/components/RedisComponent.ts b/src/components/RedisComponent.ts index 14855c9..1523267 100644 --- a/src/components/RedisComponent.ts +++ b/src/components/RedisComponent.ts @@ -1,5 +1,5 @@ import ApplicationComponent from "../ApplicationComponent"; -import {Express, Router} from "express"; +import {Express} from "express"; import redis, {RedisClient} from "redis"; import config from "config"; import Logger from "../Logger"; @@ -12,7 +12,7 @@ export default class RedisComponent extends ApplicationComponent { private redisClient?: RedisClient; private store?: Store; - public async start(app: Express, router: Router): Promise { + public async start(app: Express): Promise { this.redisClient = redis.createClient(config.get('redis.port'), config.get('redis.host'), { password: config.has('redis.password') ? config.get('redis.password') : undefined, }); diff --git a/src/components/ServeStaticDirectoryComponent.ts b/src/components/ServeStaticDirectoryComponent.ts index 5573c37..ff3429c 100644 --- a/src/components/ServeStaticDirectoryComponent.ts +++ b/src/components/ServeStaticDirectoryComponent.ts @@ -1,5 +1,5 @@ import ApplicationComponent from "../ApplicationComponent"; -import express, {Express, Router} from "express"; +import express, {Router} from "express"; import {PathParams} from "express-serve-static-core"; export default class ServeStaticDirectoryComponent extends ApplicationComponent { @@ -12,7 +12,7 @@ export default class ServeStaticDirectoryComponent extends ApplicationComponent< this.path = routePath; } - public async start(app: Express, router: Router): Promise { + public async handle(router: Router): Promise { if (typeof this.path !== 'undefined') { router.use(this.path, express.static(this.root, {maxAge: 1000 * 3600 * 72})); } else { diff --git a/src/components/SessionComponent.ts b/src/components/SessionComponent.ts index 709cadb..c1993dd 100644 --- a/src/components/SessionComponent.ts +++ b/src/components/SessionComponent.ts @@ -3,18 +3,17 @@ import session from "express-session"; import config from "config"; import RedisComponent from "./RedisComponent"; import flash from "connect-flash"; -import {Express, Router} from "express"; +import {Router} from "express"; export default class SessionComponent extends ApplicationComponent { private readonly storeComponent: RedisComponent; - public constructor(storeComponent: RedisComponent) { super(); this.storeComponent = storeComponent; } - public async start(app: Express, router: Router): Promise { + public async init(router: Router): Promise { router.use(session({ saveUninitialized: true, secret: config.get('session.secret'), @@ -56,7 +55,4 @@ export default class SessionComponent extends ApplicationComponent { next(); }); } - - public async stop(): Promise { - } } \ No newline at end of file diff --git a/src/components/WebSocketServerComponent.ts b/src/components/WebSocketServerComponent.ts index 4081e96..92b7940 100644 --- a/src/components/WebSocketServerComponent.ts +++ b/src/components/WebSocketServerComponent.ts @@ -1,5 +1,5 @@ import ApplicationComponent from "../ApplicationComponent"; -import {Express, Request, Router} from "express"; +import {Express, Request} from "express"; import WebSocket, {Server as WebSocketServer} from "ws"; import Logger from "../Logger"; import cookie from "cookie"; @@ -24,7 +24,7 @@ export default class WebSocketServerComponent extends ApplicationComponent this.storeComponent = storeComponent; } - public async start(app: Express, router: Router): Promise { + public async start(app: Express): Promise { const listeners: { [p: string]: WebSocketListener } = this.application.getWebSocketListeners(); this.wss = new WebSocketServer({ server: this.expressAppComponent.getServer(),