@deepkit/framework
Version:
360 lines • 19.3 kB
JavaScript
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