add pathParams
This commit is contained in:
parent
5da902fb3b
commit
129366c087
|
@ -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",
|
||||||
|
|
187
http_server.ts
187
http_server.ts
|
@ -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;
|
||||||
|
@ -47,75 +51,109 @@ export class HTTPServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleHttp(conn: Deno.Conn){
|
private async handleHttp(conn: Deno.Conn) {
|
||||||
const httpConn = Deno.serveHttp(conn);
|
const httpConn = Deno.serveHttp(conn);
|
||||||
for await (const requestEvent of httpConn) {
|
for await (const requestEvent of httpConn) {
|
||||||
const url = new URL(requestEvent.request.url);
|
const url = new URL(requestEvent.request.url);
|
||||||
const filepath = decodeURIComponent(url.pathname);
|
const filepath = decodeURIComponent(url.pathname);
|
||||||
|
|
||||||
if (this.staticServePath && filepath.startsWith(this.staticServePath)) {
|
if (this.staticServePath && filepath.startsWith(this.staticServePath)) {
|
||||||
const fileDir = filepath.split("/").slice(2).join("/");
|
const fileDir = filepath.split("/").slice(2).join("/");
|
||||||
const pathLoc = path.join(
|
const pathLoc = path.join(
|
||||||
Deno.cwd(),
|
Deno.cwd(),
|
||||||
this.staticLocalDir as string,
|
this.staticLocalDir as string,
|
||||||
fileDir,
|
fileDir,
|
||||||
);
|
);
|
||||||
let file;
|
let file;
|
||||||
try {
|
try {
|
||||||
file = await Deno.open(pathLoc, { read: true });
|
file = await Deno.open(pathLoc, { read: true });
|
||||||
} catch {
|
} catch {
|
||||||
// If the file cannot be opened, return a "404 Not Found" response
|
// If the file cannot be opened, return a "404 Not Found" response
|
||||||
await requestEvent.respondWith(
|
|
||||||
new Response(
|
|
||||||
JSON.stringify({
|
|
||||||
code: 404,
|
|
||||||
message: `File ${filepath} not found!`,
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
status: Status.NotFound,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const readableStream = file.readable;
|
|
||||||
const response = new Response(readableStream);
|
|
||||||
await requestEvent.respondWith(response);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const routeName = `${requestEvent.request.method}@${filepath}`;
|
|
||||||
const route = this.routes.has(routeName)
|
|
||||||
? this.routes.get(routeName)
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
if (route) {
|
|
||||||
const routeReply: RouteReply = new RouteReply();
|
|
||||||
const handler = await route.handler(
|
|
||||||
new RouteRequest(requestEvent.request),
|
|
||||||
routeReply,
|
|
||||||
);
|
|
||||||
await requestEvent.respondWith(
|
|
||||||
new Response(handler as string, {
|
|
||||||
status: routeReply.statusCode,
|
|
||||||
headers: routeReply.headers,
|
|
||||||
statusText: STATUS_TEXT[routeReply.statusCode],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await requestEvent.respondWith(
|
await requestEvent.respondWith(
|
||||||
new Response(
|
new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
code: 404,
|
code: 404,
|
||||||
message: `Route ${routeName} not found!`,
|
message: `File ${filepath} not found!`,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
status: Status.NotFound,
|
status: Status.NotFound,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const readableStream = file.readable;
|
||||||
|
const response = new Response(readableStream);
|
||||||
|
await requestEvent.respondWith(response);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
const routeRequest = new RouteRequest(requestEvent.request);
|
||||||
|
const routeReply: RouteReply = new RouteReply();
|
||||||
|
const routeName = `${requestEvent.request.method}@${filepath}`;
|
||||||
|
let route = this.routes.has(routeName)
|
||||||
|
? this.routes.get(routeName)
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (route) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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(
|
||||||
|
new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
code: 404,
|
||||||
|
message: `Route ${routeName} not found!`,
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
status: Status.NotFound,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get(path: string, handler: RouteHandler) {
|
get(path: string, handler: RouteHandler) {
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Reference in New Issue
Block a user