rjweb-server
Version:
Easy and Robust Way to create a Web Server with Many Easy-to-use Features in NodeJS
283 lines (282 loc) • 11.2 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultOptions = void 0;
const global_1 = require("../types/global");
const utils_1 = require("@rjweb/utils");
const GlobalContext_1 = __importDefault(require("../types/internal/classes/GlobalContext"));
const ContentTypes_1 = __importDefault(require("./router/ContentTypes"));
const File_1 = __importDefault(require("./router/File"));
const Middleware_1 = require("./Middleware");
const Validator_1 = __importDefault(require("./Validator"));
const Path_1 = __importDefault(require("./router/Path"));
const mergeClasses_1 = __importDefault(require("../functions/mergeClasses"));
const openapi3_ts_1 = require("openapi3-ts");
const http_1 = __importDefault(require("../handlers/http"));
const ws_1 = __importDefault(require("../handlers/ws"));
const RequestContext_1 = __importDefault(require("../types/internal/classes/RequestContext"));
const HttpRequestContext_1 = __importDefault(require("./request/HttpRequestContext"));
const WsOpenContext_1 = __importDefault(require("./request/WsOpenContext"));
const WsMessageContext_1 = __importDefault(require("./request/WsMessageContext"));
const WsCloseContext_1 = __importDefault(require("./request/WsCloseContext"));
exports.defaultOptions = {
port: 0,
bind: '0.0.0.0',
version: true,
compression: {
http: {
enabled: true,
preferOrder: ['brotli', 'gzip', 'deflate'],
maxSize: (0, utils_1.size)(10).mb(),
minSize: (0, utils_1.size)(1).kb()
}, ws: {
enabled: true,
maxSize: (0, utils_1.size)(1).mb()
}
}, performance: {
eTag: true,
lastModified: true
}, logging: {
debug: false,
error: true,
warn: true
}, proxy: {
enabled: false,
force: false,
compress: false,
header: 'x-forwarded-for',
ips: {
validate: false,
mode: 'whitelist',
list: [...global_1.ReverseProxyIps.LOCAL, ...global_1.ReverseProxyIps.CLOUDFLARE]
}, credentials: {
authenticate: false,
username: 'proxy',
password: 'proxy'
}
}
};
class Server {
/**
* Construct a new Server Instance
* @example
* ```
* import { Server } from "rjweb-server"
* import { Runtime } from "@rjweb/runtime-bun"
*
* const server = new Server(Runtime, {
* port: 3000
* })
* ```
* @since 3.0.0
*/ constructor(implementation, options, middlewares, context = {}) {
this.context = context;
this._status = 'stopped';
this.promises = [];
this.openAPISchemas = {};
this.Validator = Validator_1.default;
this.middlewares = middlewares ?? [];
this.options = utils_1.object.deepParse(exports.defaultOptions, options);
this.implementation = new implementation(this.options);
this.global = new GlobalContext_1.default(this.options, this.implementation, {
HttpRequest: (0, mergeClasses_1.default)(HttpRequestContext_1.default, ...this.middlewares.filter((middleware) => middleware.rjwebVersion === Middleware_1.currentVersion).map((middleware) => middleware.classContexts.HttpRequest(middleware.config, HttpRequestContext_1.default))),
WsOpen: (0, mergeClasses_1.default)(WsOpenContext_1.default, ...this.middlewares.filter((middleware) => middleware.rjwebVersion === Middleware_1.currentVersion).map((middleware) => middleware.classContexts.WsOpen(middleware.config, WsOpenContext_1.default))),
WsMessage: (0, mergeClasses_1.default)(WsMessageContext_1.default, ...this.middlewares.filter((middleware) => middleware.rjwebVersion === Middleware_1.currentVersion).map((middleware) => middleware.classContexts.WsMessage(middleware.config, WsMessageContext_1.default))),
WsClose: (0, mergeClasses_1.default)(WsCloseContext_1.default, ...this.middlewares.filter((middleware) => middleware.rjwebVersion === Middleware_1.currentVersion).map((middleware) => middleware.classContexts.WsClose(middleware.config, WsCloseContext_1.default)))
});
const outdated = this.middlewares.find((middleware) => middleware.rjwebVersion !== Middleware_1.currentVersion);
if (outdated) {
throw new Error(`Middleware Version Mismatch\n ${outdated.infos.name}: ${outdated.infos.version} (Recieved Middleware v${outdated.rjwebVersion}, expected v${Middleware_1.currentVersion})`);
}
this.global.logger.debug('Server Created!');
this.global.logger.debug('Implementation:');
this.global.logger.debug(` ${this.implementation.name()}: ${this.implementation.version()}`);
this.global.logger.debug('Middlewares:');
if (this.middlewares.length) {
for (const middleware of this.middlewares) {
this.global.logger.debug(` ${middleware.infos.name}: ${middleware.infos.version}`);
}
}
else {
this.global.logger.debug(' (None)');
}
}
/**
* Add a Content Type Mapping to override (or expand) content types
* @since 5.3.0
*/ contentTypes(callback) {
const contentTypes = new ContentTypes_1.default();
callback(contentTypes);
this.global.contentTypes.import(contentTypes['data']);
return this;
}
get FileLoader() {
const global = this.global, promises = this.promises;
return class FakeFileLoader extends File_1.default {
constructor(prefix) {
super(prefix, global, undefined, undefined, promises);
}
};
}
/**
* Listen to Error Callbacks
* @since 9.0.0
*/ error(key, callback) {
this.global.errorHandlers[key] = callback;
return this;
}
/**
* Listen to Ratelimit Callbacks
* @since 9.0.0
*/ rateLimit(key, callback) {
this.global.rateLimitHandlers[key] = callback;
return this;
}
/**
* Listen to Not Found Callbacks
* @since 9.0.0
*/ notFound(callback) {
this.global.notFoundHandler = callback;
return this;
}
/**
* Create a new Path
* @since 6.0.0
*/ path(prefix, callback) {
const path = new Path_1.default(prefix, this.global, undefined, undefined, this.promises);
callback(path);
this.global.routes.http.push(...path['routesHttp']);
this.global.routes.static.push(...path['routesStatic']);
this.global.routes.ws.push(...path['routesWS']);
return this;
}
/**
* Listen to all HTTP Requests (does not override routed ones)
* @since 9.0.0
*/ http(callback) {
this.global.httpHandler = callback;
return this;
}
/**
* Generate an OpenAPI Specification for the Server
* @since 9.0.0
*/ openAPI(name, version, server, contact) {
const builder = new openapi3_ts_1.oas31.OpenApiBuilder()
.addTitle(name)
.addVersion(version)
.addServer(server);
if (contact)
builder.addContact(contact);
const routes = {};
for (const route of this.global.routes.http) {
if (route.urlData.type === 'regexp')
continue;
if (!routes[route.urlData.value])
routes[route.urlData.value] = {};
if (route['urlMethod'] === 'CONNECT')
continue;
routes[route.urlData.value][route['urlMethod']] = route;
}
for (const path in routes) {
const pathItem = {};
for (const [method, route] of Object.entries(routes[path])) {
pathItem[method.toLowerCase()] = route['openApi'];
}
builder.addPath(path, pathItem);
}
for (const schema in this.openAPISchemas) {
builder.addSchema(schema, this.openAPISchemas[schema]);
}
return builder.rootDoc;
}
/**
* Add an OpenAPI Schema to the Server
* @since 9.0.0
*/ schema(name, schema) {
this.openAPISchemas[name] = schema;
return this;
}
/**
* Get the Server's Port that its listening on
* @since 9.0.0
*/ port() {
if (this._status === 'stopped')
return null;
return (0, utils_1.as)(this.implementation.port());
}
/**
* Get the Server's Status
* @since 9.0.0
*/ status() {
return this._status;
}
/**
* Start the Server Instance
* @example
* ```
* import { Server } from "rjweb-server"
*
* const server = new Server({})
*
* server.start().then((port) => {
* console.log(`Server Started on Port ${port}`)
* })
* ```
* @since 3.0.0
*/ start() {
if (this._status === 'listening')
throw new Error('Server is already listening');
this.implementation.handle({
http: (context) => (0, http_1.default)(new RequestContext_1.default(context, this.middlewares, this, this.global), context, this, this.middlewares, this.context),
ws: (ws) => (0, ws_1.default)(ws.data(), ws, this, this.middlewares)
});
return new Promise(async (resolve, reject) => {
try {
await this.implementation.start();
this._status = 'listening';
this.global.logger.debug('Running Router Promises ...');
const promisesStartTime = performance.now();
await Promise.all(this.promises);
this.global.logger.debug(`Running Router Promises ... Done ${(performance.now() - promisesStartTime).toFixed(2)}ms`);
this.global.logger.debug('Running Middleware Promises ...');
const middlewareStartTime = performance.now();
await Promise.all(this.middlewares.map((middleware) => middleware.callbacks.load?.(middleware.config, this, this.global)));
this.global.logger.debug(`Running Middleware Promises ... Done ${(performance.now() - middlewareStartTime).toFixed(2)}ms`);
resolve(this.implementation.port());
}
catch (err) {
this.implementation.stop();
this._status = 'stopped';
reject(err);
}
});
}
/**
* Stop the Server Instance
* @example
* ```
* import { Server } from "rjweb-server"
*
* const server = new Server({})
*
* server.start().then((port) => {
* console.log(`Server Started on Port ${port}`)
*
* setTimeout(() => {
* server.stop()
* console.log('Server Stopped after 5 seconds')
* }, 5000)
* })
* ```
* @since 3.0.0
*/ stop() {
if (this._status === 'stopped')
throw new Error('Server is already stopped');
this.implementation.stop();
this._status = 'stopped';
return this;
}
}
exports.default = Server;