add pathParams
This commit is contained in:
		| @@ -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", | ||||||
|   | |||||||
| @@ -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; | ||||||
|   } |   } | ||||||
| } | } | ||||||
		Reference in New Issue
	
	Block a user