From f556269c7759bf438d7f3008164e9dea9bb0ba99 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Thu, 1 Aug 2024 07:48:58 +1000 Subject: [PATCH 01/17] feat(http): `http/router` --- http/router.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 http/router.ts diff --git a/http/router.ts b/http/router.ts new file mode 100644 index 000000000000..dbf5e4fe8457 --- /dev/null +++ b/http/router.ts @@ -0,0 +1,16 @@ +export type Handler = ( + request: Request, + info?: Deno.ServeHandlerInfo, +) => Response | Promise; + +export interface StaticRoute { + path: string; + method: string; +} + +export interface DynamicRoute { + pattern: URLPattern; + method: string; +} + +export type Route = StaticRoute | DynamicRoute; From 0e3f1de2b25bff1402c54682396dda23e62e8af6 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Mon, 5 Aug 2024 16:52:14 +0200 Subject: [PATCH 02/17] work --- cli/parse_args.test.ts | 0 http/route.ts | 71 ++++++++++++++++++++++++++++++++++++++++++ http/route_test.ts | 44 ++++++++++++++++++++++++++ http/router.ts | 16 ---------- 4 files changed, 115 insertions(+), 16 deletions(-) create mode 100644 cli/parse_args.test.ts create mode 100644 http/route.ts create mode 100644 http/route_test.ts delete mode 100644 http/router.ts diff --git a/cli/parse_args.test.ts b/cli/parse_args.test.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/http/route.ts b/http/route.ts new file mode 100644 index 000000000000..96d4e93927ce --- /dev/null +++ b/http/route.ts @@ -0,0 +1,71 @@ +export type Handler = ( + request: Request, + params?: URLPatternResult | null, +) => Response | Promise; + +export interface Route { + path: T; + /** + * @default {"GET"} + */ + method?: string; + handler: Handler; +} + +/** + * @example Usage + * ```ts + * import { route, type Route } from "@std/http/route"; + * + * const routes: Route[] = [ + * { + * path: "/about", + * method: "GET", + * handler: (request) => new Response("About page"), + * }, + * { + * path: "/users/:id", + * method: "GET", + * handler: (request, params) => new Response(params?.pathname.groups.id), + * } + * ]; + * function defaultHandler(request: Request) { + * return new Response("Not found", { status: 404 }); + * } + * + * Deno.serve(route(routes, defaultHandler)); + * ``` + * + * @param routes + * @returns + */ +export function route( + routes: Route[], + defaultHandler: Handler, +): Handler | undefined { + const staticRoutes = new Map(); + const dynamicRoutes: Route[] = []; + + for (const route of routes) { + if (route.path instanceof URLPattern) { + dynamicRoutes.push(route as Route); + } else { + staticRoutes.set(`${route.method ?? "GET"} ${route.path}`, route.handler); + } + } + + return (request: Request) => { + const { pathname } = new URL(request.url); + const handler = staticRoutes.get(`${request.method} ${pathname}`); + if (handler !== undefined) return handler(request); + + const route = dynamicRoutes + .find((route) => + (route.method ?? "GET") === request.method && + route.path.test(request.url) + ); + + return route?.handler(request, route.path.exec(request.url)) ?? + defaultHandler(request); + }; +} diff --git a/http/route_test.ts b/http/route_test.ts new file mode 100644 index 000000000000..c63e5d502748 --- /dev/null +++ b/http/route_test.ts @@ -0,0 +1,44 @@ +import { type Route, route } from "./route.ts"; +import { assertEquals } from "../assert/equals.ts"; + +const routes: Route[] = [ + { + path: "/about", + handler: (request) => new Response(new URL(request.url).pathname), + }, + { + path: new URLPattern({ pathname: "/users/:id" }), + method: "POST", + handler: (_request, params) => new Response(params?.pathname.groups.id), + }, +]; +function defaultHandler(request: Request) { + return new Response(new URL(request.url).pathname, { status: 404 }); +} + +Deno.test("route()", async (t) => { + const handler = route(routes, defaultHandler); + + await t.step("handles static routes", async () => { + const request = new Request("http://example.com/about"); + const response = await handler!(request); + assertEquals(response?.status, 200); + assertEquals(await response?.text(), "/about"); + }); + + await t.step("handles dynamic routes", async () => { + const request = new Request("http://example.com/users/123", { + method: "POST", + }); + const response = await handler!(request); + assertEquals(await response?.text(), "123"); + assertEquals(response?.status, 200); + }); + + await t.step("handles default handler", async () => { + const request = new Request("http://example.com/not-found"); + const response = await handler!(request); + assertEquals(response?.status, 404); + assertEquals(await response?.text(), "/not-found"); + }); +}); diff --git a/http/router.ts b/http/router.ts deleted file mode 100644 index dbf5e4fe8457..000000000000 --- a/http/router.ts +++ /dev/null @@ -1,16 +0,0 @@ -export type Handler = ( - request: Request, - info?: Deno.ServeHandlerInfo, -) => Response | Promise; - -export interface StaticRoute { - path: string; - method: string; -} - -export interface DynamicRoute { - pattern: URLPattern; - method: string; -} - -export type Route = StaticRoute | DynamicRoute; From fa32cc52958cbd5a645d896c30cf2b309397a5d4 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Mon, 5 Aug 2024 16:56:50 +0200 Subject: [PATCH 03/17] work --- http/route.ts | 7 ++++--- http/route_test.ts | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/http/route.ts b/http/route.ts index 96d4e93927ce..2260bfb884dd 100644 --- a/http/route.ts +++ b/http/route.ts @@ -1,5 +1,6 @@ export type Handler = ( request: Request, + info?: Deno.ServeHandlerInfo, params?: URLPatternResult | null, ) => Response | Promise; @@ -26,7 +27,7 @@ export interface Route { * { * path: "/users/:id", * method: "GET", - * handler: (request, params) => new Response(params?.pathname.groups.id), + * handler: (request, _info, params) => new Response(params?.pathname.groups.id), * } * ]; * function defaultHandler(request: Request) { @@ -54,7 +55,7 @@ export function route( } } - return (request: Request) => { + return (request: Request, info?: Deno.ServeHandlerInfo) => { const { pathname } = new URL(request.url); const handler = staticRoutes.get(`${request.method} ${pathname}`); if (handler !== undefined) return handler(request); @@ -65,7 +66,7 @@ export function route( route.path.test(request.url) ); - return route?.handler(request, route.path.exec(request.url)) ?? + return route?.handler(request, info, route.path.exec(request.url)) ?? defaultHandler(request); }; } diff --git a/http/route_test.ts b/http/route_test.ts index c63e5d502748..f724f6c1c7ba 100644 --- a/http/route_test.ts +++ b/http/route_test.ts @@ -9,7 +9,8 @@ const routes: Route[] = [ { path: new URLPattern({ pathname: "/users/:id" }), method: "POST", - handler: (_request, params) => new Response(params?.pathname.groups.id), + handler: (_request, _info, params) => + new Response(params?.pathname.groups.id), }, ]; function defaultHandler(request: Request) { From c343adab15fc23c5f8173ee0ac0a3129699acfdb Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Mon, 5 Aug 2024 17:00:46 +0200 Subject: [PATCH 04/17] fix --- http/route.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/http/route.ts b/http/route.ts index 2260bfb884dd..767431863047 100644 --- a/http/route.ts +++ b/http/route.ts @@ -27,7 +27,7 @@ export interface Route { * { * path: "/users/:id", * method: "GET", - * handler: (request, _info, params) => new Response(params?.pathname.groups.id), + * handler: (_request, _info, params) => new Response(params?.pathname.groups.id), * } * ]; * function defaultHandler(request: Request) { @@ -44,7 +44,7 @@ export function route( routes: Route[], defaultHandler: Handler, ): Handler | undefined { - const staticRoutes = new Map(); + const staticRoutes = new Map(); const dynamicRoutes: Route[] = []; for (const route of routes) { @@ -60,11 +60,9 @@ export function route( const handler = staticRoutes.get(`${request.method} ${pathname}`); if (handler !== undefined) return handler(request); - const route = dynamicRoutes - .find((route) => - (route.method ?? "GET") === request.method && - route.path.test(request.url) - ); + const route = dynamicRoutes.find((route) => + (route.method ?? "GET") === request.method && route.path.test(request.url) + ); return route?.handler(request, info, route.path.exec(request.url)) ?? defaultHandler(request); From 1b7c74a09cf94c817993c06b3d3d22c403e1ef51 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Tue, 6 Aug 2024 09:10:44 +0200 Subject: [PATCH 05/17] work --- http/deno.json | 3 +- http/mod.ts | 28 ++++++++++++++++ http/route.ts | 83 ++++++++++++++++++++++++++++++++++++---------- http/route_test.ts | 11 +++--- 4 files changed, 101 insertions(+), 24 deletions(-) diff --git a/http/deno.json b/http/deno.json index 502def38de6e..2e7607eb7d83 100644 --- a/http/deno.json +++ b/http/deno.json @@ -10,6 +10,7 @@ "./server-sent-event-stream": "./server_sent_event_stream.ts", "./status": "./status.ts", "./signed-cookie": "./signed_cookie.ts", - "./user-agent": "./user_agent.ts" + "./user-agent": "./user_agent.ts", + "./route": "./route.ts" } } diff --git a/http/mod.ts b/http/mod.ts index a4a2e7e0b9ea..a8ca6e10027c 100644 --- a/http/mod.ts +++ b/http/mod.ts @@ -58,6 +58,33 @@ * }); * ``` * + * ### Routing + * + * {@linkcode route} provides an easy way to route requests to different + * handlers based on the request path and method. + * + * ```ts no-eval + * import { route, type Route } from "@std/http/route"; + * + * const routes: Route[] = [ + * { + * path: "/about", + * method: "GET", + * handler: (request) => new Response("About page"), + * }, + * { + * path: "/users/:id", + * method: "GET", + * handler: (_request, _info, params) => new Response(params?.pathname.groups.id), + * } + * ]; + * function defaultHandler(request: Request) { + * return new Response("Not found", { status: 404 }); + * } + * + * Deno.serve(route(routes, defaultHandler)); + * ``` + * * @module */ @@ -69,3 +96,4 @@ export * from "./signed_cookie.ts"; export * from "./server_sent_event_stream.ts"; export * from "./user_agent.ts"; export * from "./file_server.ts"; +export * from "./route.ts"; diff --git a/http/route.ts b/http/route.ts index 767431863047..5a345a7bc89e 100644 --- a/http/route.ts +++ b/http/route.ts @@ -1,28 +1,72 @@ +/** + * Request handler for {@linkcode route}. + * + * Extends {@linkcode Handler} by adding a `params` argument. + * + * @param request Request + * @param info Request info + * @param params URL pattern result + */ export type Handler = ( request: Request, info?: Deno.ServeHandlerInfo, params?: URLPatternResult | null, ) => Response | Promise; -export interface Route { - path: T; +/** Static route configuration for {@linkcode route}. */ +export interface StaticRoute { + /** + * Request path. + */ + path: string; + /** + * Request method. + * + * @default {"GET"} + */ + method?: string; + /** + * Request handler. + */ + handler: Handler; +} + +/** Dynamic configuration for {@linkcode route}. */ +export interface DynamicRoute { + /** + * Request path. + */ + path: URLPattern; /** + * Request method. + * * @default {"GET"} */ method?: string; + /** + * Request handler. + */ handler: Handler; } +function isDynamicRoute(route: Route): route is DynamicRoute { + return route.path instanceof URLPattern; +} + +/** Route configuration for {@linkcode route}. */ +export type Route = StaticRoute | DynamicRoute; + /** + * Routes requests to different handlers based on the request path and method. + * * @example Usage - * ```ts + * ```ts no-eval * import { route, type Route } from "@std/http/route"; * * const routes: Route[] = [ * { * path: "/about", - * method: "GET", - * handler: (request) => new Response("About page"), + * handler: (_request) => new Response("About page"), * }, * { * path: "/users/:id", @@ -30,6 +74,7 @@ export interface Route { * handler: (_request, _info, params) => new Response(params?.pathname.groups.id), * } * ]; + * * function defaultHandler(request: Request) { * return new Response("Not found", { status: 404 }); * } @@ -37,19 +82,21 @@ export interface Route { * Deno.serve(route(routes, defaultHandler)); * ``` * - * @param routes - * @returns + * @param routes Route configurations + * @param defaultHandler Default request handler that's returned when no route + * matches the given request. + * @returns Request handler */ export function route( - routes: Route[], + routes: Route[], defaultHandler: Handler, -): Handler | undefined { +): Handler { const staticRoutes = new Map(); - const dynamicRoutes: Route[] = []; + const dynamicRoutes: DynamicRoute[] = []; for (const route of routes) { - if (route.path instanceof URLPattern) { - dynamicRoutes.push(route as Route); + if (isDynamicRoute(route)) { + dynamicRoutes.push(route); } else { staticRoutes.set(`${route.method ?? "GET"} ${route.path}`, route.handler); } @@ -58,13 +105,13 @@ export function route( return (request: Request, info?: Deno.ServeHandlerInfo) => { const { pathname } = new URL(request.url); const handler = staticRoutes.get(`${request.method} ${pathname}`); - if (handler !== undefined) return handler(request); + if (handler !== undefined) return handler(request, info); - const route = dynamicRoutes.find((route) => - (route.method ?? "GET") === request.method && route.path.test(request.url) - ); + for (const route of dynamicRoutes) { + const match = route.path.exec(pathname); + if (match) return route.handler(request, info, match); + } - return route?.handler(request, info, route.path.exec(request.url)) ?? - defaultHandler(request); + return defaultHandler(request, info); }; } diff --git a/http/route_test.ts b/http/route_test.ts index f724f6c1c7ba..608c5ef87ff4 100644 --- a/http/route_test.ts +++ b/http/route_test.ts @@ -1,10 +1,10 @@ import { type Route, route } from "./route.ts"; import { assertEquals } from "../assert/equals.ts"; -const routes: Route[] = [ +const routes: Route[] = [ { path: "/about", - handler: (request) => new Response(new URL(request.url).pathname), + handler: (request: Request) => new Response(new URL(request.url).pathname), }, { path: new URLPattern({ pathname: "/users/:id" }), @@ -13,6 +13,7 @@ const routes: Route[] = [ new Response(params?.pathname.groups.id), }, ]; + function defaultHandler(request: Request) { return new Response(new URL(request.url).pathname, { status: 404 }); } @@ -22,7 +23,7 @@ Deno.test("route()", async (t) => { await t.step("handles static routes", async () => { const request = new Request("http://example.com/about"); - const response = await handler!(request); + const response = await handler(request); assertEquals(response?.status, 200); assertEquals(await response?.text(), "/about"); }); @@ -31,14 +32,14 @@ Deno.test("route()", async (t) => { const request = new Request("http://example.com/users/123", { method: "POST", }); - const response = await handler!(request); + const response = await handler(request); assertEquals(await response?.text(), "123"); assertEquals(response?.status, 200); }); await t.step("handles default handler", async () => { const request = new Request("http://example.com/not-found"); - const response = await handler!(request); + const response = await handler(request); assertEquals(response?.status, 404); assertEquals(await response?.text(), "/not-found"); }); From 452010c20e21fa1ee05865342d8d9eb7e669b75b Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Tue, 6 Aug 2024 10:58:43 +0200 Subject: [PATCH 06/17] fix --- http/route.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/http/route.ts b/http/route.ts index 5a345a7bc89e..f175210207dd 100644 --- a/http/route.ts +++ b/http/route.ts @@ -103,12 +103,12 @@ export function route( } return (request: Request, info?: Deno.ServeHandlerInfo) => { - const { pathname } = new URL(request.url); + const { pathname, href } = new URL(request.url); const handler = staticRoutes.get(`${request.method} ${pathname}`); if (handler !== undefined) return handler(request, info); for (const route of dynamicRoutes) { - const match = route.path.exec(pathname); + const match = route.path.exec(href); if (match) return route.handler(request, info, match); } From d4288e15bed13f2606e984f56d087b523c727fda Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Tue, 6 Aug 2024 16:15:03 +0200 Subject: [PATCH 07/17] clean --- cli/parse_args.test.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 cli/parse_args.test.ts diff --git a/cli/parse_args.test.ts b/cli/parse_args.test.ts deleted file mode 100644 index e69de29bb2d1..000000000000 From 9f71a29a60534e52b50614867e119f4a165983ae Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Tue, 6 Aug 2024 16:18:40 +0200 Subject: [PATCH 08/17] add unstable notices --- http/route.ts | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/http/route.ts b/http/route.ts index f175210207dd..7d5cf0971c07 100644 --- a/http/route.ts +++ b/http/route.ts @@ -1,6 +1,11 @@ /** * Request handler for {@linkcode route}. * + * > [!WARNING] + * > **UNSTABLE**: New API, yet to be vetted. + * + * @experimental + * * Extends {@linkcode Handler} by adding a `params` argument. * * @param request Request @@ -13,7 +18,14 @@ export type Handler = ( params?: URLPatternResult | null, ) => Response | Promise; -/** Static route configuration for {@linkcode route}. */ +/** + * Static route configuration for {@linkcode route}. + * + * > [!WARNING] + * > **UNSTABLE**: New API, yet to be vetted. + * + * @experimental + */ export interface StaticRoute { /** * Request path. @@ -31,7 +43,14 @@ export interface StaticRoute { handler: Handler; } -/** Dynamic configuration for {@linkcode route}. */ +/** + * Dynamic configuration for {@linkcode route}. + * + * > [!WARNING] + * > **UNSTABLE**: New API, yet to be vetted. + * + * @experimental + */ export interface DynamicRoute { /** * Request path. @@ -53,12 +72,24 @@ function isDynamicRoute(route: Route): route is DynamicRoute { return route.path instanceof URLPattern; } -/** Route configuration for {@linkcode route}. */ +/** + * Route configuration for {@linkcode route}. + * + * > [!WARNING] + * > **UNSTABLE**: New API, yet to be vetted. + * + * @experimental + */ export type Route = StaticRoute | DynamicRoute; /** * Routes requests to different handlers based on the request path and method. * + * > [!WARNING] + * > **UNSTABLE**: New API, yet to be vetted. + * + * @experimental + * * @example Usage * ```ts no-eval * import { route, type Route } from "@std/http/route"; From e29b7554e8ff9550bcd29286814bb563af79fdaa Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Tue, 6 Aug 2024 16:21:41 +0200 Subject: [PATCH 09/17] fix --- http/route.ts | 2 ++ http/route_test.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/http/route.ts b/http/route.ts index 7d5cf0971c07..bb7949b30f28 100644 --- a/http/route.ts +++ b/http/route.ts @@ -1,3 +1,5 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + /** * Request handler for {@linkcode route}. * diff --git a/http/route_test.ts b/http/route_test.ts index 608c5ef87ff4..24c623ab788e 100644 --- a/http/route_test.ts +++ b/http/route_test.ts @@ -1,3 +1,5 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + import { type Route, route } from "./route.ts"; import { assertEquals } from "../assert/equals.ts"; From bdab9966628422f6bbf2572adf9df780afd42714 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Wed, 7 Aug 2024 10:24:15 +0200 Subject: [PATCH 10/17] work --- http/route.ts | 6 +++--- http/route_test.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/http/route.ts b/http/route.ts index bb7949b30f28..d04447cd0e4e 100644 --- a/http/route.ts +++ b/http/route.ts @@ -57,7 +57,7 @@ export interface DynamicRoute { /** * Request path. */ - path: URLPattern; + pattern: URLPattern; /** * Request method. * @@ -71,7 +71,7 @@ export interface DynamicRoute { } function isDynamicRoute(route: Route): route is DynamicRoute { - return route.path instanceof URLPattern; + return "pattern" in route; } /** @@ -141,7 +141,7 @@ export function route( if (handler !== undefined) return handler(request, info); for (const route of dynamicRoutes) { - const match = route.path.exec(href); + const match = route.pattern.exec(href); if (match) return route.handler(request, info, match); } diff --git a/http/route_test.ts b/http/route_test.ts index 24c623ab788e..32ecd63bdebc 100644 --- a/http/route_test.ts +++ b/http/route_test.ts @@ -9,7 +9,7 @@ const routes: Route[] = [ handler: (request: Request) => new Response(new URL(request.url).pathname), }, { - path: new URLPattern({ pathname: "/users/:id" }), + pattern: new URLPattern({ pathname: "/users/:id" }), method: "POST", handler: (_request, _info, params) => new Response(params?.pathname.groups.id), From 6098f2e3826ec6150a5d55e44685fa15a2e3dc43 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Wed, 7 Aug 2024 11:11:19 +0200 Subject: [PATCH 11/17] work --- http/route.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/http/route.ts b/http/route.ts index d04447cd0e4e..3028be024ffb 100644 --- a/http/route.ts +++ b/http/route.ts @@ -8,7 +8,8 @@ * * @experimental * - * Extends {@linkcode Handler} by adding a `params` argument. + * Extends {@linkcode Deno.ServeHandlerInfo} by adding making `info` optional + * and adding a `params` argument. * * @param request Request * @param info Request info @@ -117,7 +118,8 @@ export type Route = StaticRoute | DynamicRoute; * * @param routes Route configurations * @param defaultHandler Default request handler that's returned when no route - * matches the given request. + * matches the given request. Serving HTTP 404 Not Found or 405 Method Not + * Allowed response can be done in this function. * @returns Request handler */ export function route( @@ -138,7 +140,7 @@ export function route( return (request: Request, info?: Deno.ServeHandlerInfo) => { const { pathname, href } = new URL(request.url); const handler = staticRoutes.get(`${request.method} ${pathname}`); - if (handler !== undefined) return handler(request, info); + if (handler) return handler(request, info); for (const route of dynamicRoutes) { const match = route.pattern.exec(href); From 8ce06cb755e71f1b75e98f2e755d2758aae58ab1 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Wed, 7 Aug 2024 11:44:37 +0200 Subject: [PATCH 12/17] work --- http/mod.ts | 16 ++++++++++------ http/route.ts | 47 +++++++++++++++++++++++++++++++++++------------ 2 files changed, 45 insertions(+), 18 deletions(-) diff --git a/http/mod.ts b/http/mod.ts index 3304908d04a5..45f6fb23b4c4 100644 --- a/http/mod.ts +++ b/http/mod.ts @@ -65,20 +65,24 @@ * * ```ts no-eval * import { route, type Route } from "@std/http/route"; + * import { serveDir } from "@std/http/file-server"; * * const routes: Route[] = [ * { * path: "/about", - * method: "GET", - * handler: (request) => new Response("About page"), + * handler: () => new Response("About page"), * }, * { - * path: "/users/:id", - * method: "GET", - * handler: (_request, _info, params) => new Response(params?.pathname.groups.id), + * pattern: new URLPattern({ pathname: "/users/:id" }), + * handler: (_req, _info, params) => new Response(params?.pathname.groups.id), + * }, + * { + * pattern: new URLPattern({ pathname: "/static/*" }), + * handler: (req: Request) => serveDir(req) * } * ]; - * function defaultHandler(request: Request) { + * + * function defaultHandler(_req: Request) { * return new Response("Not found", { status: 404 }); * } * diff --git a/http/route.ts b/http/route.ts index 3028be024ffb..3e33773bdfbd 100644 --- a/http/route.ts +++ b/http/route.ts @@ -1,7 +1,26 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. /** - * Request handler for {@linkcode route}. + * Request handler for {@linkcode StaticRoute}. + * + * > [!WARNING] + * > **UNSTABLE**: New API, yet to be vetted. + * + * @experimental + * + * Extends {@linkcode Deno.ServeHandlerInfo} by adding making `info` optional. + * + * @param request Request + * @param info Request info + * @param params URL pattern result + */ +export type StaticRouteHandler = ( + request: Request, + info?: Deno.ServeHandlerInfo, +) => Response | Promise; + +/** + * Request handler for {@linkcode DynamicRoute}. * * > [!WARNING] * > **UNSTABLE**: New API, yet to be vetted. @@ -15,7 +34,7 @@ * @param info Request info * @param params URL pattern result */ -export type Handler = ( +export type DynamicRouteHandler = ( request: Request, info?: Deno.ServeHandlerInfo, params?: URLPatternResult | null, @@ -43,7 +62,7 @@ export interface StaticRoute { /** * Request handler. */ - handler: Handler; + handler: StaticRouteHandler; } /** @@ -68,7 +87,7 @@ export interface DynamicRoute { /** * Request handler. */ - handler: Handler; + handler: DynamicRouteHandler; } function isDynamicRoute(route: Route): route is DynamicRoute { @@ -96,20 +115,24 @@ export type Route = StaticRoute | DynamicRoute; * @example Usage * ```ts no-eval * import { route, type Route } from "@std/http/route"; + * import { serveDir } from "@std/http/file-server"; * * const routes: Route[] = [ * { * path: "/about", - * handler: (_request) => new Response("About page"), + * handler: () => new Response("About page"), + * }, + * { + * pattern: new URLPattern({ pathname: "/users/:id" }), + * handler: (_req, _info, params) => new Response(params?.pathname.groups.id), * }, * { - * path: "/users/:id", - * method: "GET", - * handler: (_request, _info, params) => new Response(params?.pathname.groups.id), + * pattern: new URLPattern({ pathname: "/static/*" }), + * handler: (req: Request) => serveDir(req) * } * ]; * - * function defaultHandler(request: Request) { + * function defaultHandler(_req: Request) { * return new Response("Not found", { status: 404 }); * } * @@ -124,9 +147,9 @@ export type Route = StaticRoute | DynamicRoute; */ export function route( routes: Route[], - defaultHandler: Handler, -): Handler { - const staticRoutes = new Map(); + defaultHandler: StaticRouteHandler, +): StaticRouteHandler { + const staticRoutes = new Map(); const dynamicRoutes: DynamicRoute[] = []; for (const route of routes) { From 2954042289a30b9641debd0ac0499d52471498d6 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Wed, 7 Aug 2024 13:57:16 +0200 Subject: [PATCH 13/17] work --- http/mod.ts | 2 +- http/route.ts | 103 +++++++++------------------------------------ http/route_test.ts | 2 +- 3 files changed, 21 insertions(+), 86 deletions(-) diff --git a/http/mod.ts b/http/mod.ts index 45f6fb23b4c4..3f1874ddcf27 100644 --- a/http/mod.ts +++ b/http/mod.ts @@ -69,7 +69,7 @@ * * const routes: Route[] = [ * { - * path: "/about", + * pattern: new URLPattern({ pathname: "/about" }), * handler: () => new Response("About page"), * }, * { diff --git a/http/route.ts b/http/route.ts index 3e33773bdfbd..ad1ce93a0c65 100644 --- a/http/route.ts +++ b/http/route.ts @@ -1,26 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. /** - * Request handler for {@linkcode StaticRoute}. - * - * > [!WARNING] - * > **UNSTABLE**: New API, yet to be vetted. - * - * @experimental - * - * Extends {@linkcode Deno.ServeHandlerInfo} by adding making `info` optional. - * - * @param request Request - * @param info Request info - * @param params URL pattern result - */ -export type StaticRouteHandler = ( - request: Request, - info?: Deno.ServeHandlerInfo, -) => Response | Promise; - -/** - * Request handler for {@linkcode DynamicRoute}. + * Request handler for {@linkcode Route}. * * > [!WARNING] * > **UNSTABLE**: New API, yet to be vetted. @@ -34,46 +15,21 @@ export type StaticRouteHandler = ( * @param info Request info * @param params URL pattern result */ -export type DynamicRouteHandler = ( +export type Handler = ( request: Request, info?: Deno.ServeHandlerInfo, params?: URLPatternResult | null, ) => Response | Promise; /** - * Static route configuration for {@linkcode route}. - * - * > [!WARNING] - * > **UNSTABLE**: New API, yet to be vetted. - * - * @experimental - */ -export interface StaticRoute { - /** - * Request path. - */ - path: string; - /** - * Request method. - * - * @default {"GET"} - */ - method?: string; - /** - * Request handler. - */ - handler: StaticRouteHandler; -} - -/** - * Dynamic configuration for {@linkcode route}. + * Route configuration for {@linkcode route}. * * > [!WARNING] * > **UNSTABLE**: New API, yet to be vetted. * * @experimental */ -export interface DynamicRoute { +export interface Route { /** * Request path. */ @@ -87,23 +43,9 @@ export interface DynamicRoute { /** * Request handler. */ - handler: DynamicRouteHandler; + handler: Handler; } -function isDynamicRoute(route: Route): route is DynamicRoute { - return "pattern" in route; -} - -/** - * Route configuration for {@linkcode route}. - * - * > [!WARNING] - * > **UNSTABLE**: New API, yet to be vetted. - * - * @experimental - */ -export type Route = StaticRoute | DynamicRoute; - /** * Routes requests to different handlers based on the request path and method. * @@ -119,7 +61,7 @@ export type Route = StaticRoute | DynamicRoute; * * const routes: Route[] = [ * { - * path: "/about", + * pattern: new URLPattern({ pathname: "/about" }), * handler: () => new Response("About page"), * }, * { @@ -147,29 +89,22 @@ export type Route = StaticRoute | DynamicRoute; */ export function route( routes: Route[], - defaultHandler: StaticRouteHandler, -): StaticRouteHandler { - const staticRoutes = new Map(); - const dynamicRoutes: DynamicRoute[] = []; - - for (const route of routes) { - if (isDynamicRoute(route)) { - dynamicRoutes.push(route); - } else { - staticRoutes.set(`${route.method ?? "GET"} ${route.path}`, route.handler); - } - } - + // TODO(iuioiua): Replace with `Deno.ServeHandler` once `info` is optional. + defaultHandler: ( + request: Request, + info?: Deno.ServeHandlerInfo, + ) => Response | Promise, +): // TODO(iuioiua): Replace with `Deno.ServeHandler` once `info` is optional. +( + request: Request, + info?: Deno.ServeHandlerInfo, +) => Response | Promise { + // TODO(iuioiua): Use `URLPatternList` once available return (request: Request, info?: Deno.ServeHandlerInfo) => { - const { pathname, href } = new URL(request.url); - const handler = staticRoutes.get(`${request.method} ${pathname}`); - if (handler) return handler(request, info); - - for (const route of dynamicRoutes) { - const match = route.pattern.exec(href); + for (const route of routes) { + const match = route.pattern.exec(request.url); if (match) return route.handler(request, info, match); } - return defaultHandler(request, info); }; } diff --git a/http/route_test.ts b/http/route_test.ts index 32ecd63bdebc..64049cb863a5 100644 --- a/http/route_test.ts +++ b/http/route_test.ts @@ -5,7 +5,7 @@ import { assertEquals } from "../assert/equals.ts"; const routes: Route[] = [ { - path: "/about", + pattern: new URLPattern({ pathname: "/about" }), handler: (request: Request) => new Response(new URL(request.url).pathname), }, { From a14a95a514302a29bcb4b1607010b1b4a8e8f4c9 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Wed, 7 Aug 2024 13:58:14 +0200 Subject: [PATCH 14/17] fix --- http/route.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/http/route.ts b/http/route.ts index ad1ce93a0c65..64ea800d6d2c 100644 --- a/http/route.ts +++ b/http/route.ts @@ -31,7 +31,7 @@ export type Handler = ( */ export interface Route { /** - * Request path. + * Request URL pattern. */ pattern: URLPattern; /** From 93b5543b1289f01b60b96b9d353b22796e02fb01 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Wed, 7 Aug 2024 13:59:37 +0200 Subject: [PATCH 15/17] fix --- http/route.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/http/route.ts b/http/route.ts index 64ea800d6d2c..6fc0d778e564 100644 --- a/http/route.ts +++ b/http/route.ts @@ -8,15 +8,15 @@ * * @experimental * - * Extends {@linkcode Deno.ServeHandlerInfo} by adding making `info` optional - * and adding a `params` argument. + * Extends {@linkcode Deno.ServeHandlerInfo} by adding making `request` and + * `info` optional and adding a `params` argument. * * @param request Request * @param info Request info * @param params URL pattern result */ export type Handler = ( - request: Request, + request?: Request, info?: Deno.ServeHandlerInfo, params?: URLPatternResult | null, ) => Response | Promise; From 51f48e4df934ec3f7a44d99bd831e544f88dd124 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Wed, 7 Aug 2024 14:01:52 +0200 Subject: [PATCH 16/17] fix --- http/route.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/http/route.ts b/http/route.ts index 6fc0d778e564..64ea800d6d2c 100644 --- a/http/route.ts +++ b/http/route.ts @@ -8,15 +8,15 @@ * * @experimental * - * Extends {@linkcode Deno.ServeHandlerInfo} by adding making `request` and - * `info` optional and adding a `params` argument. + * Extends {@linkcode Deno.ServeHandlerInfo} by adding making `info` optional + * and adding a `params` argument. * * @param request Request * @param info Request info * @param params URL pattern result */ export type Handler = ( - request?: Request, + request: Request, info?: Deno.ServeHandlerInfo, params?: URLPatternResult | null, ) => Response | Promise; From e9836b27ea3b127d31ccd25e896b20ab14263c10 Mon Sep 17 00:00:00 2001 From: Asher Gomez Date: Wed, 7 Aug 2024 14:32:07 +0200 Subject: [PATCH 17/17] work --- http/route.ts | 21 ++++++--------------- http/route_test.ts | 15 ++++++++++++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/http/route.ts b/http/route.ts index 64ea800d6d2c..5daaaaf2e652 100644 --- a/http/route.ts +++ b/http/route.ts @@ -8,8 +8,7 @@ * * @experimental * - * Extends {@linkcode Deno.ServeHandlerInfo} by adding making `info` optional - * and adding a `params` argument. + * Extends {@linkcode Deno.ServeHandlerInfo} by adding adding a `params` argument. * * @param request Request * @param info Request info @@ -17,7 +16,7 @@ */ export type Handler = ( request: Request, - info?: Deno.ServeHandlerInfo, + info: Deno.ServeHandlerInfo, params?: URLPatternResult | null, ) => Response | Promise; @@ -89,18 +88,10 @@ export interface Route { */ export function route( routes: Route[], - // TODO(iuioiua): Replace with `Deno.ServeHandler` once `info` is optional. - defaultHandler: ( - request: Request, - info?: Deno.ServeHandlerInfo, - ) => Response | Promise, -): // TODO(iuioiua): Replace with `Deno.ServeHandler` once `info` is optional. -( - request: Request, - info?: Deno.ServeHandlerInfo, -) => Response | Promise { - // TODO(iuioiua): Use `URLPatternList` once available - return (request: Request, info?: Deno.ServeHandlerInfo) => { + defaultHandler: Deno.ServeHandler, +): Deno.ServeHandler { + // TODO(iuioiua): Use `URLPatternList` once available (https://github.com/whatwg/urlpattern/pull/166) + return (request: Request, info: Deno.ServeHandlerInfo) => { for (const route of routes) { const match = route.pattern.exec(request.url); if (match) return route.handler(request, info, match); diff --git a/http/route_test.ts b/http/route_test.ts index 64049cb863a5..af9218844a52 100644 --- a/http/route_test.ts +++ b/http/route_test.ts @@ -20,12 +20,21 @@ function defaultHandler(request: Request) { return new Response(new URL(request.url).pathname, { status: 404 }); } +const info: Deno.ServeHandlerInfo = { + remoteAddr: { + transport: "tcp", + hostname: "example.com", + port: 80, + }, + completed: Promise.resolve(), +}; + Deno.test("route()", async (t) => { const handler = route(routes, defaultHandler); await t.step("handles static routes", async () => { const request = new Request("http://example.com/about"); - const response = await handler(request); + const response = await handler(request, info); assertEquals(response?.status, 200); assertEquals(await response?.text(), "/about"); }); @@ -34,14 +43,14 @@ Deno.test("route()", async (t) => { const request = new Request("http://example.com/users/123", { method: "POST", }); - const response = await handler(request); + const response = await handler(request, info); assertEquals(await response?.text(), "123"); assertEquals(response?.status, 200); }); await t.step("handles default handler", async () => { const request = new Request("http://example.com/not-found"); - const response = await handler(request); + const response = await handler(request, info); assertEquals(response?.status, 404); assertEquals(await response?.text(), "/not-found"); });