Server.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import express, {Express} from 'express';
  2. import { Middleware, Route, HttpMethod, HttpHandler } from '../base/http';
  3. import { RouteNotSetException } from '../base/exceptions';
  4. import { IncorrectMethodException } from '../base/exceptions';
  5. import { Logger, Message, MessageTypes } from '../base/logger';
  6. import { ServerProperties } from './ServerProperties';
  7. import { i18nLoader, $$ } from '../base/i18n';
  8. import path from 'path';
  9. import fs from 'fs';
  10. import http from 'http';
  11. import { Server as WebSocketServer, WebSocket as WS } from 'ws';
  12. import { InvalidMiddlewareException } from '../base/exceptions';
  13. import { InvalidRouteException } from '../base/exceptions';
  14. import { ServerNotInitializedException } from '../base/exceptions';
  15. import { WebSocketHandler } from '../base/websocket';
  16. /** @sealed */
  17. class Server {
  18. private instance: Express;
  19. private httpServer: http.Server;
  20. private readonly port: number;
  21. private readonly logger: Logger;
  22. private i18n: i18nLoader;
  23. public static readonly i18nDefaultPath = '../resources/i18n.json';
  24. public static readonly defaultMiddlewaresPath = '../middlewares';
  25. public static readonly defaultRoutesPath = '../routes';
  26. private readonly httpHandlers: {[key: string]: HttpHandler};
  27. private readonly wsHandlers: {[url: string]: WebSocketHandler};
  28. private readonly wsServers: {[url: string]: WebSocketServer};
  29. private initialized: boolean;
  30. private readonly i18nPath?: string;
  31. private readonly middlewaresPath?: string;
  32. private readonly routesPath?: string;
  33. private readonly viewEngine?: string;
  34. private readonly viewsPath?: string;
  35. private readonly options?: {[key: string]: any};
  36. public constructor(properties: ServerProperties) {
  37. this.instance = express();
  38. this.httpServer = http.createServer(this.instance);
  39. this.port = properties.port;
  40. this.i18n = i18nLoader.getInstance();
  41. if(properties.locale)
  42. this.i18n.setLocale(properties.locale);
  43. this.logger = new Logger();
  44. this.httpHandlers = {};
  45. this.wsHandlers = properties.wsHandlers || {};
  46. this.wsServers = {};
  47. this.i18nPath = properties.i18nPath;
  48. this.middlewaresPath = properties.middlewaresPath;
  49. this.routesPath = properties.routesPath;
  50. this.viewEngine = properties.viewEngine;
  51. this.viewsPath = properties.viewsPath;
  52. this.options = properties.options;
  53. this.initialized = false;
  54. }
  55. public async init(): Promise<Server> {
  56. if(this.viewEngine)
  57. this.instance.set('view engine', this.viewEngine);
  58. if(this.viewsPath)
  59. this.instance.set('views', this.viewsPath);
  60. this.i18n.load(path.resolve(__dirname, Server.i18nDefaultPath));
  61. await this.registerMiddlewares(path.resolve(__dirname, Server.defaultMiddlewaresPath));
  62. await this.registerRoutes(path.resolve(__dirname, Server.defaultRoutesPath));
  63. await this.postInit();
  64. this.initialized = true;
  65. return this;
  66. }
  67. private async postInit(): Promise<void> {
  68. if(this.i18nPath)
  69. this.i18n.load(this.i18nPath);
  70. if(this.middlewaresPath)
  71. await this.registerMiddlewares(this.middlewaresPath);
  72. if(this.routesPath)
  73. await this.registerRoutes(this.routesPath);
  74. if(Object.keys(this.wsHandlers).length > 0) {
  75. this.registerWsServers();
  76. this.applyWsHandlers();
  77. }
  78. }
  79. private processHttpHandlers(): void {
  80. const handlers: HttpHandler[] = [];
  81. for(const key in this.httpHandlers)
  82. handlers.push(this.httpHandlers[key]);
  83. handlers.sort((a, b) => a.getOrder() - b.getOrder());
  84. for(const handler of handlers) {
  85. if(handler instanceof Middleware)
  86. this.addMiddleware(handler);
  87. else if (handler instanceof Route)
  88. this.addRoute(handler);
  89. }
  90. }
  91. public addMiddleware(middleware: Middleware): Server {
  92. if(middleware.getRoute() != null)
  93. this.instance.use(<string>middleware.getRoute(), middleware.getAction());
  94. else
  95. this.instance.use(middleware.getAction());
  96. return this;
  97. }
  98. public addRoute(route: Route): Server {
  99. if(route.getRoute() == null)
  100. throw new RouteNotSetException();
  101. switch(route.getMethod()) {
  102. case HttpMethod.GET:
  103. return this.get(route);
  104. case HttpMethod.POST:
  105. return this.post(route);
  106. default:
  107. throw new IncorrectMethodException();
  108. }
  109. }
  110. public registerWsServers(): Server {
  111. for(const url of Object.keys(this.wsHandlers)) {
  112. const wsServer = this.wsServers[url] = new WebSocketServer({ noServer: true });
  113. const wsHandler = this.wsHandlers[url];
  114. wsServer.on(WebSocketHandler.Event.CONNECTION, (ws: WS) => {
  115. wsHandler.onConnect(ws);
  116. ws.on(WebSocketHandler.Event.MESSAGE, wsHandler.onMessage);
  117. ws.on(WebSocketHandler.Event.ERROR, wsHandler.onError);
  118. ws.on(WebSocketHandler.Event.CLOSE, wsHandler.onClose);
  119. });
  120. }
  121. return this;
  122. }
  123. public applyWsHandlers(): Server {
  124. this.httpServer.on('upgrade', (request, socket, head) => {
  125. const url = request.url?.split('?')[0];
  126. if(url && this.wsHandlers[url] && this.wsServers[url]) {
  127. const wsServer = this.wsServers[url];
  128. wsServer.handleUpgrade(request, socket, head, (ws) => {
  129. wsServer.emit(WebSocketHandler.Event.CONNECTION, ws, request);
  130. });
  131. } else {
  132. socket.destroy();
  133. }
  134. });
  135. return this;
  136. }
  137. public getWsConnections(url: string): Set<WS> | null {
  138. const wsServer = this.wsServers[url];
  139. return wsServer ? wsServer.clients : null;
  140. }
  141. public logInfo(message: string): void {
  142. this.logger.getLogger().info(message);
  143. }
  144. public logError(message: string): void {
  145. this.logger.getLogger().error(message);
  146. }
  147. public logWarn(message: string): void {
  148. this.logger.getLogger().warn(message);
  149. }
  150. public log(message: Message): void {
  151. switch(message.type) {
  152. case MessageTypes.WARNING:
  153. return this.logWarn(message.text);
  154. case MessageTypes.ERROR:
  155. return this.logError(message.text);
  156. default:
  157. return this.logInfo(message.text);
  158. }
  159. }
  160. private get(route: Route): Server {
  161. this.instance.get(<string>route.getRoute(), route.getAction());
  162. return this;
  163. }
  164. private post(route: Route): Server {
  165. this.instance.post(<string>route.getRoute(), route.getAction());
  166. return this;
  167. }
  168. public async registerRoutes(dir: string): Promise<Server> {
  169. const files = fs.readdirSync(dir);
  170. for(const file of files) {
  171. if(/\.js$/.test(file)) {
  172. const {default: RouteClass} = await import(path.join(dir, file));
  173. if(RouteClass.prototype instanceof Route) {
  174. this.httpHandlers[RouteClass.name] = <HttpHandler>new RouteClass(this);
  175. } else throw new InvalidRouteException(file);
  176. }
  177. }
  178. return this;
  179. }
  180. public async registerMiddlewares(dir: string): Promise<Server> {
  181. const files = fs.readdirSync(dir);
  182. for(const file of files) {
  183. if(/\.js$/.test(file)) {
  184. const {default: MiddlewareClass} = await import(path.join(dir, file));
  185. if(MiddlewareClass.prototype instanceof Middleware) {
  186. this.httpHandlers[MiddlewareClass.name] = <HttpHandler>new MiddlewareClass(this);
  187. } else throw new InvalidMiddlewareException(file);
  188. }
  189. }
  190. return this;
  191. }
  192. public getLogger(): Logger {
  193. return this.logger;
  194. }
  195. public i18nLoad(path: string): Server {
  196. this.i18n.load(path);
  197. return this;
  198. }
  199. public getOption(key: string): any {
  200. return this.options ? this.options[key] || null : null;
  201. }
  202. public start(callback?: () => any): void {
  203. if(!this.initialized)
  204. throw new ServerNotInitializedException();
  205. this.processHttpHandlers();
  206. const cb = (): void => {
  207. this.logInfo($$('org.crazydoctor.expressts.start', { 'port': this.port }));
  208. if(callback) callback();
  209. };
  210. this.httpServer.listen(this.port, cb);
  211. }
  212. }
  213. export { Server };