add pathParams

This commit is contained in:
HorizonCode 2023-05-11 06:46:41 +02:00
parent 5da902fb3b
commit 129366c087
2 changed files with 147 additions and 59 deletions

View File

@ -2,12 +2,14 @@ import { Status } from "https://deno.land/std@0.186.0/http/http_status.ts";
import { HTTPServer } from "../http_server.ts"; import { HTTPServer } from "../http_server.ts";
const httpServer = new HTTPServer(); const httpServer = new HTTPServer();
httpServer.add("GET", "/", (_req, rep) => { httpServer.add("GET", "/", (req, rep) => {
rep.status(Status.Teapot) rep.status(Status.Teapot)
.header("working", "true") .header("working", "true")
.type("application/json")
.cookie("working", "true"); .cookie("working", "true");
console.log(_req.cookie("working")); console.log(req.cookie("working"));
return JSON.stringify( return JSON.stringify(
{ {
code: Status.Teapot, code: Status.Teapot,
@ -17,6 +19,19 @@ httpServer.add("GET", "/", (_req, rep) => {
2, 2,
); );
}); });
httpServer.add("GET", "/api/user/:userId", (req, rep) => {
rep.status(Status.Teapot)
.type("application/json");
return JSON.stringify(
{
code: Status.Teapot,
message: `UserID is ${req.pathParams["userId"]}`,
},
null,
2,
);
});
httpServer.listen({ httpServer.listen({
port: 8080, port: 8080,
staticLocalDir: "/static", staticLocalDir: "/static",

View File

@ -18,6 +18,10 @@ type RouteHandler = (
) => ) =>
| Promise<unknown> | Promise<unknown>
| unknown; | unknown;
export type RouteParam = {
idx: number;
paramKey: string;
};
export class HTTPServer { export class HTTPServer {
private server?: Deno.Listener; private server?: Deno.Listener;
@ -84,15 +88,16 @@ export class HTTPServer {
await requestEvent.respondWith(response); await requestEvent.respondWith(response);
return; return;
} }
const routeRequest = new RouteRequest(requestEvent.request);
const routeReply: RouteReply = new RouteReply();
const routeName = `${requestEvent.request.method}@${filepath}`; const routeName = `${requestEvent.request.method}@${filepath}`;
const route = this.routes.has(routeName) let route = this.routes.has(routeName)
? this.routes.get(routeName) ? this.routes.get(routeName)
: undefined; : undefined;
if (route) { if (route) {
const routeReply: RouteReply = new RouteReply();
const handler = await route.handler( const handler = await route.handler(
new RouteRequest(requestEvent.request), routeRequest,
routeReply, routeReply,
); );
await requestEvent.respondWith( await requestEvent.respondWith(
@ -102,7 +107,41 @@ export class HTTPServer {
statusText: STATUS_TEXT[routeReply.statusCode], statusText: STATUS_TEXT[routeReply.statusCode],
}), }),
); );
} else { continue;
}
route = Array.from(this.routes.values()).find((route) =>
routeWithParamsRouteMatcher(routeRequest, route)
);
if (route) {
const routeParamsMap: RouteParam[] = extractRouteParams(route.path);
const routeSegments: string[] = filepath.split("/");
routeRequest.pathParams = routeParamsMap.reduce(
(accum: { [key: string]: string | number }, curr: RouteParam) => {
return {
...accum,
[curr.paramKey]: routeSegments[curr.idx],
};
},
{},
);
const handler = await route.handler(
routeRequest,
routeReply,
);
await requestEvent.respondWith(
new Response(handler as string, {
status: routeReply.statusCode,
headers: routeReply.headers,
statusText: STATUS_TEXT[routeReply.statusCode],
}),
);
continue;
}
await requestEvent.respondWith( await requestEvent.respondWith(
new Response( new Response(
JSON.stringify({ JSON.stringify({
@ -116,7 +155,6 @@ export class HTTPServer {
); );
} }
} }
}
get(path: string, handler: RouteHandler) { get(path: string, handler: RouteHandler) {
this.add("GET", path, handler); this.add("GET", path, handler);
@ -145,6 +183,31 @@ export class HTTPServer {
} }
} }
export const routeWithParamsRouteMatcher = (
req: RouteRequest,
route: Route,
): boolean => {
const routeMatcherRegEx = new RegExp(`^${routeParamPattern(route.path)}$`);
return (
req.method === route.method &&
route.path.includes("/:") &&
routeMatcherRegEx.test(req.path)
);
};
export const routeParamPattern: (route: string) => string = (route) =>
route.replace(/\/\:[^/]{1,}/gi, "/[^/]{1,}").replace(/\//g, "\\/");
export const extractRouteParams: (route: string) => RouteParam[] = (route) =>
route.split("/").reduce((accum: RouteParam[], curr: string, idx: number) => {
if (/:[A-Za-z1-9]{1,}/.test(curr)) {
const paramKey: string = curr.replace(":", "");
const param: RouteParam = { idx, paramKey };
return [...accum, param];
}
return accum;
}, []);
export class Route { export class Route {
routeName: string; routeName: string;
path: string; path: string;
@ -160,10 +223,19 @@ export class Route {
} }
export class RouteRequest { export class RouteRequest {
url: string;
path: string;
headers: Headers; headers: Headers;
method: string;
pathParams: { [key: string]: string | number };
constructor(request: Request) { constructor(request: Request) {
this.url = request.url;
const urlObj = new URL(request.url);
this.path = decodeURIComponent(urlObj.pathname);
this.headers = request.headers; this.headers = request.headers;
this.method = request.method;
this.pathParams = {};
} }
header(name: string) { header(name: string) {
@ -208,7 +280,7 @@ export class RouteReply {
httpOnly?: boolean; httpOnly?: boolean;
sameSite?: "Strict" | "Lax" | "None"; sameSite?: "Strict" | "Lax" | "None";
unparsed?: string[]; unparsed?: string[];
}) { }): RouteReply {
if (!value) { if (!value) {
cookie.deleteCookie(this.headers, name, { cookie.deleteCookie(this.headers, name, {
domain: attributes?.domain, domain: attributes?.domain,
@ -228,5 +300,6 @@ export class RouteReply {
unparsed: attributes?.unparsed, unparsed: attributes?.unparsed,
}); });
} }
return this;
} }
} }