UNPKG

@deepkit/framework

Version:

360 lines 19.3 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; const __ΩPick = ['T', 'K', 'Pick', 'l+e#!e"!fRb!b"Pde""N#!w#y']; /*@ts-ignore*/ import { __ΩLoggerInterface } from '@deepkit/logger'; function __assignType(fn, args) { fn.__type = args; return fn; } /* * Deepkit Framework * Copyright (C) 2021 Deepkit UG, Marc J. Schmidt * * This program is free software: you can redistribute it and/or modify * it under the terms of the MIT License. * * You should have received a copy of the MIT License along with this program. */ import { asyncOperation, getClassName, urlJoin } from '@deepkit/core'; import { RpcClient, RpcKernel } from '@deepkit/rpc'; import cluster from 'cluster'; import { HttpRouter } from '@deepkit/http'; import { BaseEvent, EventDispatcher, eventDispatcher, EventToken } from '@deepkit/event'; import { InjectorContext } from '@deepkit/injector'; import { FrameworkConfig } from './module.config.js'; import { createRpcConnection, WebWorker, WebWorkerFactory } from './worker.js'; import { RpcControllers } from './rpc.js'; import '@deepkit/type'; export class ServerBootstrapEvent extends BaseEvent { } ServerBootstrapEvent.__type = [() => BaseEvent, 'ServerBootstrapEvent', 'P7!5w"']; /** * Called only once for application server bootstrap (for main process and workers) */ export const onServerBootstrap = new EventToken('server.bootstrap', ServerBootstrapEvent); /** * Called only once for application server bootstrap (for main process and workers) * as soon as the application server has started */ export const onServerBootstrapDone = new EventToken('server.bootstrapDone', ServerBootstrapEvent); /** * Called only once for application server bootstrap (in the main process) * as soon as the application server starts. */ export const onServerMainBootstrap = new EventToken('server.main.bootstrap', ServerBootstrapEvent); /** * Called only once for application server bootstrap (in the main process) * as soon as the application server has started. */ export const onServerMainBootstrapDone = new EventToken('server.main.bootstrapDone', ServerBootstrapEvent); /** * Called for each worker as soon as the worker bootstraps. */ export const onServerWorkerBootstrap = new EventToken('server.worker.bootstrap', ServerBootstrapEvent); /** * Called only once for application server bootstrap (in the worker process) * as soon as the application server has started. */ export const onServerWorkerBootstrapDone = new EventToken('server.worker.bootstrapDone', ServerBootstrapEvent); export class ServerShutdownEvent extends BaseEvent { } ServerShutdownEvent.__type = [() => BaseEvent, 'ServerShutdownEvent', 'P7!5w"']; /** * Called when application server shuts down (in master process and each worker). */ export const onServerShutdown = new EventToken('server.shutdown', ServerBootstrapEvent); /** * Called when application server shuts down in the main process. */ export const onServerMainShutdown = new EventToken('server.main.shutdown', ServerBootstrapEvent); /** * Called when application server shuts down in the worker process. */ export const onServerWorkerShutdown = new EventToken('server.worker.shutdown', ServerBootstrapEvent); const __ΩApplicationServerConfig = [() => __ΩPick, () => FrameworkConfig, "server", "port", "host", "httpsPort", "ssl", "sslKey", "sslCertificate", "sslCa", "sslCrl", "varPath", "selfSigned", "workers", "publicDir", "debug", "debugUrl", "gracefulShutdownTimeout", "compression", "http", "logStartup", 'ApplicationServerConfig', 'P7"P.#.$.%.&.\'.(.).*.+.,.-.../.0.1.2.3.4.5Jo!#w6y']; function needsHttpWorker(config, rpcControllers, router) { return Boolean(config.publicDir || rpcControllers.controllers.size || router.getRoutes().length); } needsHttpWorker.__type = ['publicDir', 'config', () => RpcControllers, 'rpcControllers', () => HttpRouter, 'router', 'needsHttpWorker', 'PP&4!8M2"P7#2$P7%2&"/\'']; export class LogStartupListener { constructor(logger, rpcControllers, router, config, server) { this.logger = logger; this.rpcControllers = rpcControllers; this.router = router; this.config = config; this.server = server; } onBootstrapDone() { for (const [name, controller] of this.rpcControllers.controllers.entries()) { this.logger.log('RPC Controller', `<green>${getClassName(controller.controller)}</green>`, `<grey>${name}</grey>`); } const routes = this.router.getRoutes(); if (routes.length) { this.logger.log(`<green>${routes.length}</green> HTTP routes`); let lastController = undefined; for (const route of routes) { if (route.internal) continue; if (route.action.type === 'controller' && lastController !== route.action.controller) { lastController = route.action.controller; this.logger.log(`HTTP Controller <green>${getClassName(lastController)}</green>`); } this.logger.log(` <green>${route.httpMethods.length === 0 ? 'ANY' : route.httpMethods.join(',')}</green> <yellow>${route.getFullPath()}</yellow>`); } } if (this.config.server) { this.logger.log(`Server up and running`); } else { const host = this.server.getHttpHost(); if (host) { let url = `http://${host}`; if (this.config.ssl) { url = `https://${host}:${this.config.httpsPort || this.config.port}`; } this.logger.log(`HTTP listening at <yellow>${url}</yellow>`); if (this.config.debug) { this.logger.log(`Debugger enabled at <yellow>${url}${urlJoin('/', this.config.debugUrl, '/')}</yellow>`); } } } } } LogStartupListener.__type = [() => __ΩLoggerInterface, 'logger', () => RpcControllers, 'rpcControllers', () => HttpRouter, 'router', () => __ΩApplicationServerConfig, 'config', () => ApplicationServer, 'server', 'constructor', 'onBootstrapDone', 'LogStartupListener', 'Pn!2"<P7#2$<P7%2&<n\'2(<P7)2*<"0+P"0,5w-']; __decorate([ eventDispatcher.listen(onServerMainBootstrapDone), __metadata("design:type", Function), __metadata("design:paramtypes", []), __metadata("design:returntype", void 0) ], LogStartupListener.prototype, "onBootstrapDone", null); const __ΩApplicationServerOptions = ['listenOnSignals', 'startHttpServer', 'ApplicationServerOptions', 'P)4!8)4"8Mw#y']; export { __ΩApplicationServerOptions as __ΩApplicationServerOptions }; export class ApplicationServer { constructor(logger, webWorkerFactory, eventDispatcher, rootScopedContext, config, rpcControllers, rpcKernel, router) { this.logger = logger; this.webWorkerFactory = webWorkerFactory; this.eventDispatcher = eventDispatcher; this.rootScopedContext = rootScopedContext; this.config = config; this.rpcControllers = rpcControllers; this.rpcKernel = rpcKernel; this.router = router; this.started = false; this.stopping = false; this.onlineWorkers = 0; this.needsHttpWorker = needsHttpWorker(config, rpcControllers, router); this.onStop = new Promise(__assignType((resolve) => this.stopResolver = resolve, ['resolve', '', 'P"2!"/"'])); } getHttpWorker() { if (!this.httpWorker) throw new Error('HTTP worker not started'); return this.httpWorker; } /** * Closes all server listener and triggers shutdown events. This is only used for integration tests. */ async close(graceful = false) { if (!this.started) return; await this.stopWorkers(); await this.eventDispatcher.dispatch(onServerShutdown, new ServerShutdownEvent()); await this.eventDispatcher.dispatch(onServerMainShutdown, new ServerShutdownEvent()); if (this.httpWorker) await this.httpWorker.close(graceful); } stopWorkers() { if (this.config.workers === 0) return Promise.resolve(); return asyncOperation(__assignType((resolve) => { cluster.on('exit', async () => { if (this.onlineWorkers === 0) { this.logger.debug('All workers offline. Shutting down ...'); await this.eventDispatcher.dispatch(onServerShutdown, new ServerShutdownEvent()); await this.eventDispatcher.dispatch(onServerMainShutdown, new ServerShutdownEvent()); resolve(undefined); } }); for (const worker of Object.values(cluster.workers || {})) { if (worker) worker.send('stop'); } }, ['resolve', '', 'P"2!"/"'])); } async start(optionsOrListenOnSignal = false) { const options = typeof optionsOrListenOnSignal === 'boolean' ? { listenOnSignals: optionsOrListenOnSignal } : optionsOrListenOnSignal; if (this.started) throw new Error('ApplicationServer already started'); this.started = true; if (cluster.isMaster && this.config.logStartup) { if (this.config.workers) { this.logger.log(`Start server, using ${this.config.workers} workers ...`); } else { this.logger.log(`Start server ...`); } } await this.eventDispatcher.dispatch(onServerBootstrap, new ServerBootstrapEvent()); const startHttpServer = options.startHttpServer !== false; let killRequests = 0; if (this.config.workers > 1 && startHttpServer) { if (cluster.isMaster) { await this.eventDispatcher.dispatch(onServerMainBootstrap, new ServerBootstrapEvent()); for (let i = 0; i < this.config.workers; i++) { cluster.fork(); } await asyncOperation(__assignType((resolve) => { cluster.on('online', () => { this.onlineWorkers++; if (this.onlineWorkers === this.config.workers) resolve(undefined); }); cluster.on('exit', __assignType((w) => { this.onlineWorkers--; if (this.stopping) return; this.logger.warn(`Worker ${w.id} died. Restarted`); cluster.fork(); }, ['w', '', 'P"2!"/"'])); }, ['resolve', '', 'P"2!"/"'])); if (options.listenOnSignals) { const stopServer = __assignType((signal) => async () => { killRequests++; if (killRequests === 3) { this.logger.warn(`Received ${signal}. Force stopping server ...`); process.exit(1); return; } if (this.stopping) { this.logger.warn(`Received ${signal}. Stopping already in process. Try again to force stop.`); return; } this.stopping = true; this.logger.warn(`Received ${signal}. Stopping server ...`); await this.stopWorkers(); this.stopResolver(); setTimeout(() => { //give onAppShutdown a chance to react process.exit(0); }, 10); }, ['signal', '', 'P&2!"/"']); process.on('SIGINT', stopServer('SIGINT')); process.on('SIGTERM', stopServer('SIGTERM')); } await this.eventDispatcher.dispatch(onServerBootstrapDone, new ServerBootstrapEvent()); await this.eventDispatcher.dispatch(onServerMainBootstrapDone, new ServerBootstrapEvent()); } else { process.on('message', __assignType(async (msg) => { if (msg === 'stop') { await this.eventDispatcher.dispatch(onServerShutdown, new ServerShutdownEvent()); await this.eventDispatcher.dispatch(onServerWorkerShutdown, new ServerShutdownEvent()); if (this.httpWorker) await this.httpWorker.close(true); process.exit(0); } }, ['msg', '', 'P"2!"/"'])); process.on('SIGINT', async () => { //we don't do anything in sigint, as the master controls our process. //we need to register to it though so the process doesn't get killed. }); process.on('SIGTERM', async () => { //we don't do anything in sigint, as the master controls our process. //we need to register to it though so the process doesn't get killed. }); await this.eventDispatcher.dispatch(onServerWorkerBootstrap, new ServerBootstrapEvent()); if (this.needsHttpWorker) { this.httpWorker = this.webWorkerFactory.create(cluster.worker.id, this.config); this.httpWorker.start(); } await this.eventDispatcher.dispatch(onServerBootstrapDone, new ServerBootstrapEvent()); await this.eventDispatcher.dispatch(onServerWorkerBootstrapDone, new ServerBootstrapEvent()); } } else { if (options.listenOnSignals) { const stopServer = __assignType((signal) => async () => { killRequests++; if (killRequests === 3) { this.logger.warn(`Received ${signal}. Force stopping server ...`); process.exit(1); return; } if (this.stopping) { this.logger.warn(`Received ${signal}. Stopping already in process. Try again to force stop.`); return; } this.stopping = true; this.logger.warn('Received SIGINT. Stopping server ...'); await this.eventDispatcher.dispatch(onServerShutdown, new ServerShutdownEvent()); await this.eventDispatcher.dispatch(onServerMainShutdown, new ServerShutdownEvent()); if (this.httpWorker) await this.httpWorker.close(true); this.stopResolver(); setTimeout(() => { //give onAppShutdown a chance to react process.exit(0); }, 10); }, ['signal', '', 'P&2!"/"']); process.on('SIGINT', stopServer('SIGINT')); process.on('SIGTERM', stopServer('SIGTERM')); } await this.eventDispatcher.dispatch(onServerBootstrap, new ServerBootstrapEvent()); await this.eventDispatcher.dispatch(onServerMainBootstrap, new ServerBootstrapEvent()); if (this.needsHttpWorker && startHttpServer) { this.httpWorker = this.webWorkerFactory.create(1, this.config); this.httpWorker.start(); } await this.eventDispatcher.dispatch(onServerBootstrapDone, new ServerBootstrapEvent()); await this.eventDispatcher.dispatch(onServerMainBootstrapDone, new ServerBootstrapEvent()); } if (cluster.isMaster && this.config.logStartup) { this.logger.log(`Server started.`); } return options.listenOnSignals ? this.onStop : Promise.resolve(); } getHttpHost() { const port = this.config.ssl ? this.config.httpsPort || this.config.port : this.config.port; return this.httpWorker !== undefined ? `${this.config.host}:${port}` : undefined; } getWorker() { if (!this.httpWorker) throw new Error('No WebWorker registered yet. Did you start()?'); return this.httpWorker; } createClient() { const context = this.rootScopedContext; const rpcKernel = this.rpcKernel; return new RpcClient({ connect(connection) { const kernelConnection = createRpcConnection(context, rpcKernel, { writeBinary: __assignType((buffer) => connection.readBinary(buffer), ['buffer', '', 'P"2!"/"']), close: () => connection.onClose('closed'), }); connection.onConnected({ close: __assignType(function close() { kernelConnection.close(); }, ['close', 'P"/!']), writeBinary: __assignType(function writeBinary(message) { queueMicrotask(() => { kernelConnection.feed(message); }); }, ['message', 'writeBinary', 'PW2!"/"']) }); } }); } } ApplicationServer.__type = [() => WebWorker, 'httpWorker', 'started', function () { return false; }, 'stopping', function () { return false; }, 'onlineWorkers', function () { return 0; }, 'needsHttpWorker', 'onStop', '', 'stopResolver', () => __ΩLoggerInterface, 'logger', () => WebWorkerFactory, 'webWorkerFactory', () => EventDispatcher, 'eventDispatcher', () => InjectorContext, 'rootScopedContext', () => __ΩApplicationServerConfig, 'config', () => RpcControllers, 'rpcControllers', () => RpcKernel, 'rpcKernel', () => HttpRouter, 'router', 'constructor', () => WebWorker, 'getHttpWorker', 'graceful', 'close', 'stopWorkers', () => __ΩApplicationServerOptions, 'optionsOrListenOnSignal', () => false, 'start', 'getHttpHost', () => WebWorker, 'getWorker', () => RpcClient, 'createClient', 'ApplicationServer', 'P7!3"8<)3#<>$)3%<>&\'3\'<>()3)<$`3*P$/+3,<Pn-2.<P7/20<P7122<P7324<n526:P7728<P792:<P7;2<<"0=PP7>0?P"2@"0AP$`0B<PP)nCJ2D>E"0FPP&-J0GPP7H0IPP7J0K5wL']; export class InMemoryApplicationServer extends ApplicationServer { } InMemoryApplicationServer.__type = [() => ApplicationServer, 'InMemoryApplicationServer', 'P7!5w"']; //# sourceMappingURL=application-server.js.map