Skip to content

Commit

Permalink
Added subdomain based routing
Browse files Browse the repository at this point in the history
  • Loading branch information
rajkumardusad committed Feb 10, 2023
1 parent d3a6525 commit 7976afc
Show file tree
Hide file tree
Showing 6 changed files with 228 additions and 52 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@routejs/router",
"version": "1.0.1",
"version": "2.0.0",
"description": "Routejs is a fast and lightweight http router for nodejs",
"main": "index.js",
"directories": {
Expand Down
4 changes: 2 additions & 2 deletions src/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ function mergeRoute({ host, method, group, callbacks }) {
} else if (Array.isArray(route) || route instanceof Router) {
routes.push(mergeRoute({ host, method, group, callbacks: route }));
} else {
routes.push(setRoute({ host, method, path: group, callbacks }));
routes.push(setRoute({ host, method, group, callbacks }));
}
});
} else {
routes.push(setRoute({ host, method, path: group, callbacks }));
routes.push(setRoute({ host, method, group, callbacks }));
}
return routes;
}
Expand Down
105 changes: 73 additions & 32 deletions src/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ const supportedMethod = require("./supported-method");

class Route {
host = null;
hostRegexp = null;
method = null;
path = null;
pathRegexp = null;
group = null;
name = null;
params = null;
regexp = null;
group = null;
subdomains = null;
callbacks = null;
caseSensitive = false;

Expand All @@ -33,15 +35,17 @@ class Route {
}
return e.toUpperCase();
});
} else if (
(typeof method === "string" || method instanceof String) &&
!supportedMethod.includes(method.toUpperCase())
) {
} else if (typeof method === "string" || method instanceof String) {
if (!supportedMethod.includes(method.toUpperCase())) {
throw new TypeError(
`Error: ${method.toUpperCase()} method is not supported`
);
}
method = method.toUpperCase();
} else {
throw new TypeError(
`Error: ${method.toUpperCase()} method is not supported`
"Error: route method accepts only string or array of string as an argument"
);
} else {
method = method.toUpperCase();
}
}

Expand All @@ -58,17 +62,19 @@ class Route {
}

this.host = host;
this.hostRegexp = host ? this.#compileHostRegExp(host) : undefined;
this.method = method;
this.path = path;
this.name = name;
this.params = this.#getParams(path);
this.caseSensitive = caseSensitive ?? false;
this.regexp = path
this.pathRegexp = path
? this.#compileRouteRegExp(path)
: group
? this.#compileMiddlewareRegExp(group)
: this.#compileMiddlewareRegExp("/");
this.group = group;
this.name = name;
this.params = this.#getParams(path);
this.subdomains = this.#getParams(host);
this.caseSensitive = caseSensitive ?? false;
this.callbacks = Array.isArray(callbacks)
? callbacks.map((callback) => {
if (typeof callback !== "function") {
Expand Down Expand Up @@ -115,7 +121,7 @@ class Route {
);
}

if (this.regexp === null) {
if (this.pathRegexp === null) {
return false;
}

Expand All @@ -125,23 +131,37 @@ class Route {
path: this.path,
callbacks: this.callbacks,
params: {},
subdomains: {},
};

if (!!this.host && host !== this.host) {
return false;
if (!!this.hostRegexp) {
const match = this.hostRegexp.exec(host);
if (match === null) {
return false;
}
if (match.length > 1) {
let index = 0;
for (let i = 1; i < match.length ?? 0; i++) {
if (this.subdomains && this.subdomains.hasOwnProperty(index)) {
route.subdomains[this.subdomains[index]] = match[i];
}
index++;
}
}
}

if (!!this.method && Array.isArray(this.method)) {
if (!this.method.includes(method.toUpperCase())) {
if (!!this.method) {
if (
Array.isArray(this.method) &&
!this.method.includes(method.toUpperCase())
) {
return false;
}
} else {
if (!!this.method && method.toUpperCase() !== this.method) {
} else if (method.toUpperCase() !== this.method) {
return false;
}
}

const match = this.regexp.exec(path);
const match = this.pathRegexp.exec(path);
if (match === null) {
return false;
}
Expand All @@ -158,13 +178,32 @@ class Route {
return route;
}

#compileHostRegExp(host) {
try {
let regexp = host
? host
.replace(/\{([^\}]+)\:/g, "")
.replace(/\)\}/g, ")")
.replace(/\{(.*?)\}/g, "([^.]+?)")
: "";
if (this.caseSensitive === true) {
return regexp ? new RegExp(`^${regexp}$`) : null;
}
return regexp ? new RegExp(`^${regexp}$`, "i") : null;
} catch (err) {
throw new TypeError(`Error: ${host} invalid regular expression`);
}
}

#compileRouteRegExp(path) {
try {
let regexp = path
? path
.replace(/\/?$/, "\\/?")
.replace(/\/:([^\\/]+)\(/g, "\\/(")
.replace(/\/:([^\\/]+)/g, "/([^\\/]+?)")
.replace(/^\/?|\/?$/g, "/?")
.replace(/\/\?\/\?/, "/?")
.replace(/\{([^\}]+)\:/g, "")
.replace(/\)\}/g, ")")
.replace(/\{(.*?)\}/g, "([^\\/]+?)")
: "";
if (this.caseSensitive === true) {
return regexp ? new RegExp(`^${regexp}$`) : null;
Expand All @@ -179,25 +218,27 @@ class Route {
try {
let regexp = path
? path
.replace(/\/?$/, "\\/?")
.replace(/\/:([^\\/]+)\(/g, "\\/(")
.replace(/\/:([^\\/]+)/g, "/([^\\/]+?)")
.replace(/^\/?|\/?$/g, "/?")
.replace(/\/\?\/\?/, "/?")
.replace(/\{([^\}]+)\:/g, "")
.replace(/\)\}/g, ")")
.replace(/\{(.*?)\}/g, "([^\\/]+?)")
: "";
if (this.caseSensitive === true) {
return regexp ? new RegExp(`^${regexp}(?:[\\/].*)?$`) : null;
return regexp ? new RegExp(`^${regexp}(?:.*)?$`) : null;
}
return regexp ? new RegExp(`^${regexp}(?:[\\/].*)?$`, "i") : null;
return regexp ? new RegExp(`^${regexp}(?:.*)?$`, "i") : null;
} catch (err) {
throw new TypeError(`Error: ${path} invalid regular expression`);
}
}

#getParams(path) {
let params = path ? path.match(/\/:([^\/]*)/g) : null;
let params = path ? path.match(/(?<=\{).+?(?=\})/g) : null;
if (!params) {
return undefined;
}
return params.map((e) => e.replace("/:", "").replace(/\(.*/, ""));
return params.map((e) => e.replace(/\:\((.*)?/, ""));
}
}

Expand Down
38 changes: 29 additions & 9 deletions src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,24 +231,21 @@ class Router {
} else if (Array.isArray(route) || route instanceof Router) {
this.#mergeRoute({ host, method, group, callbacks: route });
} else {
this.#setRoute({ host, method, path: group, callbacks });
this.#setRoute({ host, method, group, callbacks });
}
});
} else {
this.#setRoute({ host, method, path: group, callbacks });
this.#setRoute({ host, method, group, callbacks });
}
}

#handle(routes, request, response) {
const requestHost = request.headers ? request.headers.host : null;
const requestMethod = request.method;
const requestUrl = request.url;
handle({ requestHost, requestMethod, requestUrl, request, response }) {
const parsedUrl = url.parse(requestUrl ? requestUrl : "");
const requestPath = parsedUrl.pathname;
const callStack = [];
let callIndex = 0;

routes.forEach((e) => {
this.routes().forEach((e) => {
const match = e.match({
host: requestHost,
method: requestMethod,
Expand All @@ -258,6 +255,7 @@ class Router {
callStack.push(match.callbacks);
if (match.method && match.params) {
request.params = match.params;
request.subdomains = match.subdomains;
}
}
});
Expand Down Expand Up @@ -343,8 +341,30 @@ class Router {
}

handler() {
return (request, response) =>
this.#handle(this.routes(), request, response);
function requestHandler(request, response) {
var requestHost = request.headers ? request.headers.host : null;
var requestMethod = request.method;
var requestUrl = request.url;

if (!requestHost && "getHeader" in request) {
requestHost = request.getHeader("host");
}
if (!requestMethod && "getMethod" in request) {
requestMethod = request.getMethod();
}
if (!requestUrl && "getUrl" in request) {
requestUrl = request.getUrl();
}

this.handle({
requestHost,
requestMethod,
requestUrl,
request,
response,
});
}
return requestHandler.bind(this);
}
}

Expand Down
71 changes: 67 additions & 4 deletions tests/middleware.test.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,85 @@
const request = require("supertest");
const { Router } = require("../index");

describe("Routing test", () => {
describe("Middleware test", () => {
const app = new Router();
const blog = new Router();

app.use(
"/multiple",
function (req, res, next) {
res.write("Ok");
next();
},
function (req, res) {
function (req, res, next) {
res.end();
}
);

test("GET /", async () => {
app.use("/home", function (req, res, next) {
res.write("Ok");
next();
});

app.get("/home", function (req, res, next) {
res.end();
});

app.use("/params/{name}", function (req, res, next) {
res.write(req.params.name);
next();
});

app.get("/params/{name}", function (req, res, next) {
res.end();
});

blog.get("/", function (req, res, next) {
res.end("Ok");
});

app.use("/blog", blog);
app.use("/urls", blog.routes());

test("GET /multiple", async () => {
await request(app.handler())
.get("/multiple")
.expect(200)
.then((res) => {
expect(res.text).toBe("Ok");
});
});

test("GET /home", async () => {
await request(app.handler())
.get("/home")
.expect(200)
.then((res) => {
expect(res.text).toBe("Ok");
});
});

test("GET /params/user", async () => {
await request(app.handler())
.get("/params/user")
.expect(200)
.then((res) => {
expect(res.text).toBe("user");
});
});

test("GET /blog", async () => {
await request(app.handler())
.get("/blog")
.expect(200)
.then((res) => {
expect(res.text).toBe("Ok");
});
});

test("GET /urls", async () => {
await request(app.handler())
.get("/")
.get("/urls")
.expect(200)
.then((res) => {
expect(res.text).toBe("Ok");
Expand Down
Loading

0 comments on commit 7976afc

Please sign in to comment.