UNPKG

rjweb-server

Version:

Easy and Robust Way to create a Web Server with Many Easy-to-use Features in NodeJS

386 lines (385 loc) 16.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var server_exports = {}; __export(server_exports, { default: () => Server }); module.exports = __toCommonJS(server_exports); var import_valueCollection = __toESM(require("./valueCollection")); var import_parseOptions = __toESM(require("../functions/parseOptions")); var import_router = __toESM(require("./router")); var import_handleHTTPRequest = __toESM(require("../functions/web/handleHTTPRequest")); var import_handleWSOpen = __toESM(require("../functions/web/handleWSOpen")); var import_handleWSMessage = __toESM(require("../functions/web/handleWSMessage")); var import_handleWSClose = __toESM(require("../functions/web/handleWSClose")); var import_middlewareBuilder = require("./middlewareBuilder"); var import_file = __toESM(require("./router/file")); var import_rjutils_collection = require("rjutils-collection"); var import_external = require("../types/external"); var import_mergeClasses = __toESM(require("../functions/mergeClasses")); var import_generateOpenAPI3 = __toESM(require("../functions/generateOpenAPI3")); var import_dataStat = __toESM(require("./dataStat")); var import_logger = __toESM(require("./logger")); var import_fs = require("fs"); var import_uws = __toESM(require("@rjweb/uws")); var import_path = __toESM(require("path")); var import_os = __toESM(require("os")); class Server extends import_router.default { /** * Initialize a new Server * @example * ``` * const controller = new Server({ * port: 8000 * }) * * module.exports.server = controller * ``` * @since 3.0.0 */ constructor(options = {}, middlewares = [], globContext = {}) { super(); this.server = import_uws.default.App(); this.socket = 0; /** * Route File Builder * @example * ``` * const { server } = require('../index.js') * * module.exports = new server.routeFile((file) => file * .http(...) * ) * ``` * @since 7.0.0 */ this.routeFile = import_file.default; this.middlewares = middlewares; const fullOptions = (0, import_parseOptions.default)(options); this.globalContext = { controller: this, contentTypes: {}, logger: new import_logger.default(fullOptions.logging), options: fullOptions, requests: new import_dataStat.default(), webSockets: { opened: new import_dataStat.default(), messages: { incoming: new import_dataStat.default(), outgoing: new import_dataStat.default() } }, classContexts: { http: (0, import_mergeClasses.default)(import_external.HttpRequest, ...this.middlewares.map((m) => m.data.classModifications.http)), wsConnect: (0, import_mergeClasses.default)(import_external.WsConnect, ...this.middlewares.map((m) => m.data.classModifications.http)), wsMessage: (0, import_mergeClasses.default)(import_external.WsMessage, ...this.middlewares.map((m) => m.data.classModifications.http)), wsClose: (0, import_mergeClasses.default)(import_external.WsClose, ...this.middlewares.map((m) => m.data.classModifications.http)) }, defaults: { globContext, headers: {} }, middlewares: this.middlewares, data: { incoming: new import_dataStat.default(), outgoing: new import_dataStat.default() }, routes: { normal: [], websocket: [], static: [], htmlBuilder: [] }, cache: { files: new import_valueCollection.default(void 0, void 0, fullOptions.cache, fullOptions.cacheLimit), middlewares: new import_valueCollection.default(void 0, void 0, fullOptions.cache, fullOptions.cacheLimit), routes: new import_valueCollection.default(void 0, void 0, fullOptions.cache, fullOptions.cacheLimit) } }; } /** * Override the set Server Options * @example * ``` * const controller = new Server({ }) * * controller.setOptions({ * port: 6900 * }) * ``` * @since 3.0.0 */ setOptions(options) { this.globalContext.options = (0, import_parseOptions.default)(options); return this; } /** * Get OpenAPI 3.1 Defininitions for this Server (make sure to let all routes load before calling) * @example * ``` * const controller = new Server({ port: 4200 }) * * controller.path('/', (path) => path * .http('GET', '/openapi', (http) => http * .onRequest((ctr) => { * ctr.print(ctr.controller.getOpenApi3Def('http://localhost:4200')) * }) * ) * ) * ``` * @since 7.6.0 */ getOpenAPI3Def(serverUrl) { return (0, import_generateOpenAPI3.default)(this.globalContext, serverUrl); } /** * Start the Server * @example * ``` * const controller = new Server({ }) * * controller.start() * .then((port) => { * console.log(`Server started on port ${port}`) * }) * .catch((err) => { * console.error(err) * }) * ``` * @since 3.0.0 */ start() { return new Promise(async (resolve, reject) => { let stopExecution = false; const externalPaths = await this.loadExternalPaths(); this.globalContext.webSockets = { opened: new import_dataStat.default(), messages: { incoming: new import_dataStat.default(), outgoing: new import_dataStat.default() } }; this.globalContext.data = { incoming: new import_dataStat.default(), outgoing: new import_dataStat.default() }; this.globalContext.requests = new import_dataStat.default(); for (let middlewareIndex = 0; middlewareIndex < this.middlewares.length; middlewareIndex++) { const middleware = this.middlewares[middlewareIndex]; if (!("data" in middleware)) { reject(new Error(`Middleware ${middleware.name} is too old!`)); stopExecution = true; break; } ; if (!("initEvent" in middleware.data)) continue; try { if (middleware.version !== import_middlewareBuilder.currentVersion) throw new Error(`Middleware version cannot be higher or lower than currently supported version (${middleware.version} != ${import_middlewareBuilder.currentVersion})`); await Promise.resolve(middleware.data.initEvent(middleware.localContext, middleware.config, this.globalContext)); } catch (error) { reject(error); stopExecution = true; break; } } ; if (stopExecution) return; if (this.globalContext.options.ssl.enabled) { try { await import_fs.promises.readFile(import_path.default.resolve(this.globalContext.options.ssl.keyFile)); await import_fs.promises.readFile(import_path.default.resolve(this.globalContext.options.ssl.certFile)); } catch { throw new Error(`Cant access your SSL Key or Cert file! (${this.globalContext.options.ssl.keyFile} / ${this.globalContext.options.ssl.certFile})`); } try { if (this.globalContext.options.ssl.caFile) await import_fs.promises.readFile(import_path.default.resolve(this.globalContext.options.ssl.caFile)); if (this.globalContext.options.ssl.dhParamFile) await import_fs.promises.readFile(import_path.default.resolve(this.globalContext.options.ssl.dhParamFile)); } catch { throw new Error(`Cant access your SSL Ca or Dhparam file! (${this.globalContext.options.ssl.caFile} / ${this.globalContext.options.ssl.dhParamFile})`); } this.server = import_uws.default.SSLApp({ key_file_name: import_path.default.resolve(this.globalContext.options.ssl.keyFile), cert_file_name: import_path.default.resolve(this.globalContext.options.ssl.certFile), ca_file_name: this.globalContext.options.ssl.caFile ? import_path.default.resolve(this.globalContext.options.ssl.caFile) : void 0, dh_params_file_name: this.globalContext.options.ssl.dhParamFile ? import_path.default.resolve(this.globalContext.options.ssl.dhParamFile) : void 0 }); } else this.server = import_uws.default.App(); this.server.any("/*", (res, req) => { (0, import_handleHTTPRequest.default)(req, res, null, "http", this.globalContext); }).ws("/*", { maxBackpressure: 512 * 1024 * 1024, sendPingsAutomatically: true, compression: this.globalContext.options.wsCompression.enabled ? import_uws.default.SHARED_COMPRESSOR : void 0, maxPayloadLength: this.globalContext.options.message.maxSize, upgrade: (res, req, connection) => { (0, import_handleHTTPRequest.default)(req, res, connection, "upgrade", this.globalContext); }, open: (ws) => { (0, import_handleWSOpen.default)(ws, this.globalContext); }, message: this.globalContext.options.message.enabled ? (ws, message) => { (0, import_handleWSMessage.default)(ws, message, this.globalContext); } : void 0, close: (ws, code, message) => { (0, import_handleWSClose.default)(ws, message, this.globalContext); } }); const routes = await this.getData(); this.globalContext.routes.normal = routes.routes; this.globalContext.routes.websocket = routes.webSockets; this.globalContext.routes.static = routes.statics; this.globalContext.contentTypes = routes.contentTypes; this.globalContext.defaults.headers = routes.defaultHeaders; this.globalContext.routes.normal.push(...externalPaths.routes); this.globalContext.routes.websocket.push(...externalPaths.webSockets); this.server.listen(this.globalContext.options.bind, this.globalContext.options.port, (listen) => { if (!listen) return reject(new Error(`Failed to start server on port ${this.globalContext.options.port}.`)); this.socket = listen; return resolve(import_uws.default.getSocketPort(listen)); }); }); } /** * Reload the Server * @example * ``` * const controller = new Server({ }) * * controller.reload() * .then((port) => { * console.log(`Server reloaded and started on port ${port}`) * }) * .catch((err) => { * console.error(err) * }) * ``` * @since 3.0.0 */ async reload() { this.globalContext.cache.files.clear(); this.globalContext.cache.routes.clear(); const routes = await this.getData(); this.globalContext.routes.normal = routes.routes; this.globalContext.routes.websocket = routes.webSockets; this.globalContext.routes.static = routes.statics; this.globalContext.contentTypes = routes.contentTypes; this.globalContext.defaults.headers = routes.defaultHeaders; const externalPaths = await this.loadExternalPaths(); this.globalContext.routes.normal.push(...externalPaths.routes); this.globalContext.routes.websocket.push(...externalPaths.webSockets); this.stop(); return this.start(); } /** * Stop the Server * @example * ``` * const controller = new Server({ }) * * controller.stop() * .then(() => { * console.log('Server stopped') * }) * .catch((err) => { * console.error(err) * }) * ``` * @since 3.0.0 */ async stop() { this.globalContext.cache.files.clear(); this.globalContext.cache.routes.clear(); import_uws.default.closeSocket(this.socket); return; } /** * Load all External Paths */ async loadExternalPaths() { const loadedRoutes = { routes: [], webSockets: [] }; for (const loadPath of (await this.getData()).loadPaths) { this.globalContext.logger.debug("Loading Route Path", loadPath); if (loadPath.type === "cjs") { for (const file of (await (0, import_rjutils_collection.getFilesRecursively)(loadPath.path, true)).filter((f) => f.endsWith("js"))) { this.globalContext.logger.debug("Loading cjs Route file", file); const route = require(file); if (!route || !(route instanceof import_file.default)) throw new Error(`Invalid Route @ ${file}`); const routeInfos = await route.getData(loadPath.prefix); for (const routeInfo of routeInfos.routes) { if (loadPath.fileBasedRouting) routeInfo.path.addSuffix(import_path.default.posix.normalize(import_path.default.posix.normalize(file).replace(import_path.default.posix.normalize(loadPath.path), "").replaceAll("\\", "/")).replace(/index|\.(js|ts|cjs|cts|mjs|mts)/g, "")); routeInfo.data.validations.push(...loadPath.validations); routeInfo.data.headers = Object.assign(routeInfo.data.headers, loadPath.headers); } for (const routeInfo of routeInfos.webSockets) { if (loadPath.fileBasedRouting) routeInfo.path.addSuffix(import_path.default.posix.normalize(import_path.default.posix.normalize(file).replace(import_path.default.posix.normalize(loadPath.path), "").replaceAll("\\", "/")).replace(/index|\.(js|ts|cjs|cts|mjs|mts)/g, "")); routeInfo.data.validations.push(...loadPath.validations); } loadedRoutes.routes.push(...routeInfos.routes); loadedRoutes.webSockets.push(...routeInfos.webSockets); } } else { for (const file of (await (0, import_rjutils_collection.getFilesRecursively)(loadPath.path, true)).filter((f) => f.endsWith("js"))) { this.globalContext.logger.debug("Loading esm Route file", file); const importFile = import_os.default.platform() === "win32" ? `file:///${file}` : file; const route = (await import(importFile)).default; if (!route || !(route instanceof import_file.default)) throw new Error(`Invalid Route @ ${file}`); const routeInfos = await route.getData(loadPath.prefix); for (const routeInfo of routeInfos.routes) { if (loadPath.fileBasedRouting) routeInfo.path.addSuffix(import_path.default.posix.normalize(import_path.default.posix.normalize(file).replace(import_path.default.posix.normalize(loadPath.path), "").replaceAll("\\", "/")).replace(/index|\.(js|ts|cjs|cts|mjs|mts)/g, "")); routeInfo.data.validations.push(...loadPath.validations); routeInfo.data.headers = Object.assign(routeInfo.data.headers, loadPath.headers); } for (const routeInfo of routeInfos.webSockets) { if (loadPath.fileBasedRouting) routeInfo.path.addSuffix(import_path.default.posix.normalize(import_path.default.posix.normalize(file).replace(import_path.default.posix.normalize(loadPath.path), "").replaceAll("\\", "/")).replace(/index|\.(js|ts|cjs|cts|mjs|mts)/g, "")); routeInfo.data.validations.push(...loadPath.validations); } loadedRoutes.routes.push(...routeInfos.routes); loadedRoutes.webSockets.push(...routeInfos.webSockets); } } } return loadedRoutes; } }