enh(Routing) Multidomain support!

This commit is contained in:
gh0sTedBuddy
2024-04-01 22:46:52 +01:00
parent 8d34a9cb7c
commit 5e6e66dceb
2 changed files with 203 additions and 64 deletions

View File

@@ -1,24 +1,52 @@
import RequestParser from '../Utils/RequestParser';
import BaseRouter from './BaseRouter';
import Logger from '../Utils/Logger';
import LayResponse from '../IO/LayResponse';
/**
* Router class that extends BaseRouter.
* @extends BaseRouter
*/
export default class Router extends BaseRouter {
domain = undefined;
server = undefined;
routes = new Map();
static allMethodKey = '$all';
static methods = ['all', 'get', 'post', 'put', 'patch', 'head', 'options'];
headerList = new Map();
constructor(server) {
#domain = undefined;
#server = undefined;
#routes = new Map();
#headerList = new Map();
/**
* Constructor for the Router class.
* @param {Server} server - The server instance.
*/
constructor(server, options = {}) {
super();
this.server = server;
this.#server = server;
this.#domain = options.domain || undefined;
}
/**
* Get the server instance.
* @returns {Server} The server instance.
*/
getServer() {
return this.server;
return this.#server;
}
/**
* Get the domain of the router
* @returns {String} The base domain for the router.
*/
getDomain() {
return this.#domain;
}
/**
* Handle the incoming request.
* @param {RequestContext} reqContext - The request context.
* @returns {Promise<Response>} The response promise.
*/
async handle(reqContext) {
let method = (reqContext.request.method || '').toString().toLowerCase();
const methodKey = `$${method.toLowerCase()}`;
@@ -32,7 +60,7 @@ export default class Router extends BaseRouter {
if (path.startsWith('/')) path = path.substring(1);
if (path.endsWith('/')) path = path.substring(0, path.length - 1);
let currentNode = this.routes;
let currentNode = this.#routes;
let fallbackNode = currentNode.has('*') ? currentNode.get('*') : undefined;
let paramsList = [];
@@ -65,7 +93,7 @@ export default class Router extends BaseRouter {
(['get', 'head'].indexOf(method) < 0 &&
!currentNode.has(methodKey) &&
!currentNode.has(Router.allMethodKey))
) {
) {LayResponse
return new Response(`[${method}] Method Not Allowed`, {
status: 405,
});
@@ -102,13 +130,26 @@ export default class Router extends BaseRouter {
try {
await reqContext.request.parseBody();
let retVal = await handle.callback(
let retVal = await this.executeMiddleware(
handle.middleware,
reqContext.request,
reqContext.response,
async () => {
return await handle.callback(reqContext.request, reqContext.response);
},
5000 // Add a timeout of 5 seconds for middleware execution
);
await reqContext.request.parseBody();
retVal = await handle.callback(
reqContext.request,
reqContext.response
);
if (typeof retVal != 'undefined') {
if (retVal instanceof Promise) {
retVal = await retVal;
} else if (retVal instanceof LayResponse) {
return await retVal.build();
} else if (retVal instanceof Response) {
return retVal;
} else if (typeof retVal == 'string') {
@@ -118,9 +159,9 @@ export default class Router extends BaseRouter {
}
}
} catch (err) {
Logger(err);
console.log(err);
return new Response('Error', { status: 500 });
console.error(err)
Logger.error(err);
return new Response('Something went wrong!', { status: 500 });
}
if (reqContext.response.redirect()) {
@@ -133,18 +174,27 @@ export default class Router extends BaseRouter {
return await reqContext.response.build();
}
/**
* Add a route to the router.
* @param {string} route - The route path.
* @param {function} callback - The route callback function.
* @param {Object} options - The route options.
* @param {function[]} [middleware] - The middleware functions.
*/
add(route, callback) {
let options = arguments[2];
let middleware = arguments[3];
if (typeof callback != 'function')
throw new Error('`callback` is not a function');
if (typeof route != 'string')
throw new Error('`route` is not a string');
if (typeof callback !== 'function')
throw new TypeError('`callback` must be a function');
if (typeof route !== 'string')
throw new TypeError('`route` must be a string');
if (Router.methods.indexOf(options.method) < 0)
throw new Error(
`invalid \`method\` (${options.method}) given`,
Router.methods
);
if (!Array.isArray(middleware)) {
middleware = typeof middleware == 'function' ? [middleware] : null;
}
@@ -155,11 +205,11 @@ export default class Router extends BaseRouter {
let params = [];
let segments = route.split('/');
let currentNode = this.routes;
let currentNode = this.#routes;
if (route == '') {
if (currentNode.get(methodKey)) {
if (!this.server.option('allowRouteOverwrite')) {
if (!this.#server.option('allowRouteOverwrite')) {
Logger(
`overwriting of routes is prohibited: [${options.method}] \`${route}\``
);
@@ -196,7 +246,7 @@ export default class Router extends BaseRouter {
}
if (currentNode.has(methodKey)) {
if (!this.server.option('allowRouteOverwrite')) {
if (!this.#server.option('allowRouteOverwrite')) {
Logger(
`overwriting of routes is prohibited: [${method}] \`${route}\``
);
@@ -209,12 +259,19 @@ export default class Router extends BaseRouter {
currentNode.set(methodKey, { callback, middleware, params });
}
/**
* Remove a route from the router.
* @param {string} route - The route path.
* @param {string} method - The HTTP method.
* @param {function} [cb=null] - The callback function to remove.
* @returns {boolean} True if the route was removed, false otherwise.
*/
remove(route, method, cb = null) {
const methodKey = `$${method.toLowerCase()}`;
if (route.startsWith('/')) route = route.substring(1);
if (route.endsWith('/')) route = route.substring(0, route.length - 1);
let currentNode = this.routes;
let currentNode = this.#routes;
if (route != '') {
let segments = route.toString().split('/');
@@ -247,4 +304,45 @@ export default class Router extends BaseRouter {
return currentNode.delete(methodKey);
}
/**
* Execute the middleware chain.
* @param {function[]} middleware - The middleware functions.
* @param {Request} req - The request object.
* @param {Response} res - The response object.
* @param {function} next - The next function to call.
* @returns {Promise<void>} A promise that resolves when the middleware chain is completed.
*/
async executeMiddleware(middleware, req, res, next) {
if (!Array.isArray(middleware) || middleware.length === 0) {
return await next();
}
const executeNext = async (index) => {
if (index >= middleware.length) {
return await next();
}
const currentMiddleware = middleware[index];
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => {
reject(new Error('Middleware execution timed out'));
}, timeout);
});
try {
await Promise.race([
currentMiddleware(req, res, async () => {
return await executeNext(index + 1);
}),
timeoutPromise,
]);
} catch (err) {
throw err;
}
};
return await executeNext(0);
}
}