Last active
February 15, 2025 14:28
-
-
Save ZoomTen/2c11a63a2fe736c02a5e6502c84bf6fa to your computer and use it in GitHub Desktop.
a router
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
proc route(reqType: string, path: string): void = | |
proc notFound( | |
reqType: string, pathOnly: string, getParams: string, pathParams: StringTableRef | |
): void = | |
stderr.writeLine "default" | |
proc methodNotAllowed( | |
reqType: string, pathOnly: string, getParams: string, pathParams: StringTableRef | |
): void = | |
stderr.writeLine "method not allowed" | |
var pathParams = newStringTable() | |
## Calculate where the path should split | |
var startPos_536874292 = -1 | |
for i in 0 ..< len(path): | |
case path[i] | |
of '?', '&': | |
startPos_536874292 = i | |
break | |
else: | |
discard | |
let pathOnly = block: | |
var p = | |
if startPos_536874292 > -1: | |
path[0 ..< startPos_536874292] | |
else: | |
path | |
## Clean up trailing slash | |
if len(p) > 1 and p[^1] == '/': | |
p[0 ..^ 2] | |
else: | |
p | |
let getParams = block: | |
if startPos_536874292 > -1: | |
path[(startPos_536874292 + 1) ..^ 1] | |
else: | |
"" | |
case pathOnly | |
of "/": | |
case reqType | |
of "GET": | |
stderr.writeLine "root" | |
else: | |
methodNotAllowed(reqType, pathOnly, getParams, pathParams) | |
of "/static/something": | |
case reqType | |
of "GET": | |
stderr.writeLine "static" | |
else: | |
methodNotAllowed(reqType, pathOnly, getParams, pathParams) | |
else: | |
## dynamic path fallback | |
const rexp_536874285 = | |
re2"(?P<Route0>/uploads/(?P<Route0_id>[^/]+?))|(?P<Route1>/(?P<Route1_id>[^/]+?)/(?P<Route1_test>[^/]+?))" | |
var m_536874286: RegexMatch2 | |
if match(pathOnly, rexp_536874285, m_536874286): | |
let gn_536874287 = groupNames(m_536874286) | |
var matchedRoute_536874288 = "" | |
for i_536874289 in 0 ..< 2: | |
let routeNameCandidate_536874290 = "Route" & $i_536874289 | |
if m_536874286.group(routeNameCandidate_536874290) != reNonCapture: | |
matchedRoute_536874288 = routeNameCandidate_536874290 | |
break | |
if len(matchedRoute_536874288) > 1: | |
for i_536874291 in gn_536874287: | |
if i_536874291.startsWith(matchedRoute_536874288) and | |
len(i_536874291) > len(matchedRoute_536874288): | |
pathParams[i_536874291[(len(matchedRoute_536874288) + 1) ..^ 1]] = | |
pathOnly[m_536874286.group(i_536874291)] | |
case matchedRoute_536874288 | |
of "Route0": | |
case reqType | |
of "GET": | |
stderr.writeLine "id => " & pathParams.getOrDefault("id") | |
else: | |
## invalid method | |
methodNotAllowed(reqType, pathOnly, getParams, pathParams) | |
of "Route1": | |
case reqType | |
of "GET": | |
stderr.writeLine "id => " & pathParams.getOrDefault("id") | |
stderr.writeLine "test => " & pathParams.getOrDefault("test") | |
else: | |
## invalid method | |
methodNotAllowed(reqType, pathOnly, getParams, pathParams) | |
else: | |
## something went wrong | |
notFound(reqType, pathOnly, getParams, pathParams) | |
else: | |
## nothing REALLY matched | |
notFound(reqType, pathOnly, getParams, pathParams) | |
else: | |
## nothing matched | |
notFound(reqType, pathOnly, getParams, pathParams) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
requires "nim >= 2.0.10" | |
requires "regex == 0.26.1" | |
# benchmarking | |
requires "benchy == 0.0.1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import std/macros | |
import std/tables | |
import std/strtabs | |
export strtabs | |
import std/strutils | |
export strutils | |
import regex | |
export regex | |
type RouterHttpMethods = enum | |
Get = "GET" | |
Head = "HEAD" | |
Post = "POST" | |
Put = "PUT" | |
Delete = "DELETE" | |
Options = "OPTIONS" | |
Patch = "PATCH" | |
macro makeRouter*(name: string, body: untyped) = | |
##[ | |
Creates a router proc of the following form: | |
```nim | |
proc routerProcName(reqType: string; path: string): void | |
``` | |
Where `name` will replace `routerProcName`. The `reqType` that | |
this new proc will accept is the following (all **uppercase**): | |
1. GET | |
2. HEAD | |
3. POST | |
4. PUT | |
5. DELETE | |
6. OPTIONS | |
7. PATCH | |
When defining a router, you will define a path for the reqType | |
using **lowercase**, e.g. to define `GET` for `/` and `PUT` for `/obj`, you do: | |
```nim | |
makeRouter("test"): | |
get "/": | |
# your stuff here | |
discard | |
put "/obj": | |
# ditto | |
discard | |
default: | |
# ... | |
discard | |
``` | |
You must define a `default` path, when the route for a URL is not | |
found. | |
You may optionally define a `methodNotAllowed` path, for when | |
the URL is accessed not through one of the defined methods. | |
You can also define "dynamic" paths that capture URL parameters | |
surrounded in braces. The URL parameters will be made available | |
via the `pathParams` variable as a string, like this: | |
```nim | |
makeRouter("test2"): | |
get "/posts/{postId}": | |
let id = pathParams.getOrDefault("postId") | |
echo "got post ID: " & id | |
``` | |
Other variables made available to you include `pathOnly` and `getParams`. | |
When the path is `/test/abc?id=10&def=qwerty`: | |
* `pathOnly` = `/test/abc` | |
* `getParams` = `id=10&def=qwerty` | |
]## | |
runnableExamples: | |
var output: string = "" | |
makeRouter("routeThisPhrhrht"): | |
get "/": | |
output = "got /" | |
get "/upload": | |
output = "loaded /upload" | |
post "/upload": | |
output = | |
"uploaded something" & ( | |
if len(getParams) > 0: | |
" with params: " & getParams | |
else: | |
"" | |
) | |
default: | |
output = "unknown" | |
methodNotAllowed: | |
output = "whoops!" | |
routeThisPhrhrht("GET", "/") | |
assert output == "got /" | |
routeThisPhrhrht("GET", "/upload") | |
assert output == "loaded /upload" | |
routeThisPhrhrht("POST", "/upload") | |
assert output == "uploaded something" | |
routeThisPhrhrht("POST", "/upload?aaaaaa=1231223&&&&11rrrdsaop3r") | |
assert output == "uploaded something with params: aaaaaa=1231223&&&&11rrrdsaop3r" | |
routeThisPhrhrht("GET", "/eogijoergjioerajgieog") | |
assert output == "unknown" | |
routeThisPhrhrht("PUT", "/upload") | |
assert output == "whoops!" | |
var staticRoutes: Table[string, seq[(RouterHttpMethods, NimNode)]] | |
var dynamicRoutes: Table[string, seq[(RouterHttpMethods, NimNode)]] | |
var defaultRoutine = newEmptyNode() | |
var methodNotAllowedRoutine = newEmptyNode() | |
# Perform AST analysis | |
for b in body: | |
case b.kind | |
of nnkCommand: # command(ident<method>, strlit, stmtlist) | |
#[ | |
Statements of the form: | |
get "/": | |
<statements> | |
post "/upload": | |
<statements> | |
]# | |
let | |
methodName = b[0] | |
url = b[1] | |
list = b[2] | |
newTup = (parseEnum[RouterHttpMethods](methodName.strVal.toUpperAscii()), list) | |
var isDynamicRoute = false | |
for i in url.strVal: | |
if i == '{': | |
isDynamicRoute = true | |
break | |
if isDynamicRoute: | |
if not (url.strVal in staticRoutes): | |
dynamicRoutes[url.strVal] = @[newTup] | |
else: | |
dynamicRoutes[url.strVal].add(newTup) | |
else: # is static route | |
if not (url.strVal in staticRoutes): | |
staticRoutes[url.strVal] = @[newTup] | |
else: | |
staticRoutes[url.strVal].add(newTup) | |
of nnkCall: # call(ident"others", stmtlist) | |
#[ | |
Statements of the form: | |
default: | |
<statements> | |
]# | |
case b[0].strVal | |
of "default": | |
defaultRoutine = b[1] | |
of "methodNotAllowed": | |
methodNotAllowedRoutine = b[1] | |
else: | |
discard | |
when defined(routerMacroDbg): | |
debugEcho "============= STATIC ROUTES =============" | |
for k, v in pairs(staticRoutes): | |
debugEcho k & ":" | |
for m in v: | |
let (mtd, node) = m | |
debugEcho " " & $mtd & " => " & node.repr.repr.replace("\n", "") | |
debugEcho "============= DYNAMIC ROUTES =============" | |
for k, v in pairs(dynamicRoutes): | |
debugEcho k & ":" | |
for m in v: | |
let (mtd, node) = m | |
debugEcho " " & $mtd & " => " & node.repr.repr.replace("\n", "") | |
# Generate a regex pattern from all the dynamic routes | |
var genRegex = "" | |
if len(dynamicRoutes) > 0: | |
var counter = 0 | |
const paramConverter = re2"""\{(\w+)\}""" | |
for url in keys(dynamicRoutes): | |
# Add a case for the dynamic route | |
genRegex &= "(?P<Route" | |
genRegex &= $counter | |
genRegex &= ">" | |
genRegex &= | |
url.replace( | |
paramConverter, | |
( | |
proc(m: RegexMatch2, s: string): string = | |
# turn "{id}" into "(?P<RouteXX_id>[^/]+?)" | |
"(?P<Route" & $counter & "_" & s[m.group(0)] & ">[^/]+?)" | |
), | |
) | |
genRegex &= ")|" | |
inc(counter) | |
# cut the final OR symbol | |
genRegex = genRegex[0 ..< (len(genRegex) - 1)] | |
when defined(routerMacroDbg): | |
debugEcho "============= GENERATED REGEX PATTERN =============" | |
debugEcho genRegex | |
# Generate default handler proc | |
var defaultProc = newProc( | |
ident("notFound"), | |
[ | |
ident("void"), | |
newIdentDefs(ident("reqType"), ident("string")), | |
newIdentDefs(ident("pathOnly"), ident("string")), | |
newIdentDefs(ident("getParams"), ident("string")), | |
newIdentDefs(ident("pathParams"), ident("StringTableRef")), | |
], | |
defaultRoutine, | |
) | |
let defaultProcCall = newCall( | |
ident("notFound"), | |
ident("reqType"), | |
ident("pathOnly"), | |
ident("getParams"), | |
ident("pathParams"), | |
) | |
# Generate invalid method proc, if any | |
var methodNotAllowedProc = block: | |
if methodNotAllowedRoutine.kind != nnkEmpty: | |
newProc( | |
ident("methodNotAllowed"), | |
[ | |
ident("void"), | |
newIdentDefs(ident("reqType"), ident("string")), | |
newIdentDefs(ident("pathOnly"), ident("string")), | |
newIdentDefs(ident("getParams"), ident("string")), | |
newIdentDefs(ident("pathParams"), ident("StringTableRef")), | |
], | |
methodNotAllowedRoutine, | |
) | |
else: | |
newEmptyNode() | |
let methodNotAllowedCall = newCall( | |
ident("methodNotAllowed"), | |
ident("reqType"), | |
ident("pathOnly"), | |
ident("getParams"), | |
ident("pathParams"), | |
) | |
# Build switch statement for static routes | |
var routeSwitch = nnkCaseStmt.newTree(ident("pathOnly")) | |
for routeName, routeBody in pairs(staticRoutes): | |
var rqSwitch = nnkCaseStmt.newTree(ident("reqType")) | |
for i in routeBody: | |
let (rqKind, rqRoutine) = i | |
rqSwitch.add(nnkOfBranch.newTree(newStrLitNode($rqKind), rqRoutine)) | |
rqSwitch.add( | |
nnkElse.newTree( | |
if methodNotAllowedRoutine.kind != nnkEmpty: | |
methodNotAllowedCall | |
else: | |
defaultProcCall | |
) | |
) | |
routeSwitch.add( | |
nnkOfBranch.newTree(newStrLitNode(routeName), newStmtList(rqSwitch)) | |
) | |
var dynamicRouteRoutine = newStmtList(newCommentStmtNode("dynamic path fallback")) | |
if len(genRegex) > 0: | |
# Dynamic route code path | |
let regexConstName = genSym(nskConst, "rexp") | |
let regexMatchHolderName = genSym(nskVar, "m") | |
# Add the big regex to be compiled | |
dynamicRouteRoutine.add( | |
nnkConstSection.newTree( | |
nnkConstDef.newTree( | |
regexConstName, | |
newEmptyNode(), | |
nnkCallStrLit.newTree(ident("re2"), newLit(genRegex)), | |
) | |
) | |
) | |
# Prepare regex match holder variable | |
dynamicRouteRoutine.add( | |
nnkVarSection.newTree(newIdentDefs(regexMatchHolderName, ident("RegexMatch2"))) | |
) | |
# Prepare the "something matched" arm | |
var caseOfMatchContents = newStmtList() | |
let caseOfMatch = nnkElifBranch.newTree( | |
newCall(ident("match"), ident("pathOnly"), regexConstName, regexMatchHolderName), | |
caseOfMatchContents, | |
) | |
block somethingMatched: | |
let groupNameHolderName = genSym(nskLet, "gn") | |
let matchedRouteHolderName = genSym(nskVar, "matchedRoute") | |
caseOfMatchContents.add: | |
nnkLetSection.newTree( | |
nnkIdentDefs.newTree( | |
groupNameHolderName, | |
newEmptyNode(), | |
nnkCall.newTree(newIdentNode("groupNames"), regexMatchHolderName), | |
) | |
) | |
caseOfMatchContents.add: | |
nnkVarSection.newTree( | |
nnkIdentDefs.newTree(matchedRouteHolderName, newEmptyNode(), newLit("")) | |
) | |
let tmpIterName1 = genSym(nskForVar, "i") | |
let routeNameCandidateName = genSym(nskLet, "routeNameCandidate") | |
caseOfMatchContents.add: | |
nnkForStmt.newTree( | |
tmpIterName1, | |
nnkInfix.newTree(newIdentNode("..<"), newLit(0), newLit(len(dynamicRoutes))), | |
nnkStmtList.newTree( | |
nnkLetSection.newTree( | |
nnkIdentDefs.newTree( | |
routeNameCandidateName, | |
newEmptyNode(), | |
nnkInfix.newTree( | |
newIdentNode("&"), | |
newLit("Route"), | |
nnkPrefix.newTree(newIdentNode("$"), tmpIterName1), | |
), | |
) | |
), | |
nnkIfStmt.newTree( | |
nnkElifBranch.newTree( | |
nnkInfix.newTree( | |
newIdentNode("!="), | |
nnkCall.newTree( | |
nnkDotExpr.newTree(regexMatchHolderName, newIdentNode("group")), | |
routeNameCandidateName, | |
), | |
newIdentNode("reNonCapture"), | |
), | |
nnkStmtList.newTree( | |
nnkAsgn.newTree(matchedRouteHolderName, routeNameCandidateName), | |
nnkBreakStmt.newTree(newEmptyNode()), | |
), | |
) | |
), | |
), | |
) | |
var matchFoundStatements = newStmtList() | |
caseOfMatchContents.add: | |
nnkIfStmt.newTree( | |
nnkElifBranch.newTree( | |
nnkInfix.newTree( | |
newIdentNode(">"), | |
nnkCall.newTree(newIdentNode("len"), matchedRouteHolderName), | |
newLit(1), | |
), | |
matchFoundStatements, | |
), | |
nnkElse.newTree( | |
nnkStmtList.newTree( | |
newCommentStmtNode("nothing REALLY matched"), defaultProcCall | |
) | |
), | |
) | |
# Inject captured parameters | |
let tmpIterName2 = genSym(nskForVar, "i") | |
matchFoundStatements.add: | |
nnkForStmt.newTree( | |
tmpIterName2, | |
groupNameHolderName, | |
nnkStmtList.newTree( | |
nnkIfStmt.newTree( | |
nnkElifBranch.newTree( | |
nnkInfix.newTree( | |
newIdentNode("and"), | |
nnkCall.newTree( | |
nnkDotExpr.newTree(tmpIterName2, newIdentNode("startsWith")), | |
matchedRouteHolderName, | |
), | |
nnkInfix.newTree( | |
newIdentNode(">"), | |
nnkCall.newTree(newIdentNode("len"), tmpIterName2), | |
nnkCall.newTree(newIdentNode("len"), matchedRouteHolderName), | |
), | |
), | |
nnkStmtList.newTree( | |
nnkAsgn.newTree( | |
nnkBracketExpr.newTree( | |
newIdentNode("pathParams"), | |
nnkBracketExpr.newTree( | |
tmpIterName2, | |
nnkInfix.newTree( | |
newIdentNode("..^"), | |
nnkPar.newTree( | |
nnkInfix.newTree( | |
newIdentNode("+"), | |
nnkCall.newTree( | |
newIdentNode("len"), matchedRouteHolderName | |
), | |
newLit(1), | |
) | |
), | |
newLit(1), | |
), | |
), | |
), | |
nnkBracketExpr.newTree( | |
newIdentNode("pathOnly"), | |
nnkCall.newTree( | |
nnkDotExpr.newTree(regexMatchHolderName, newIdentNode("group")), | |
tmpIterName2, | |
), | |
), | |
) | |
), | |
) | |
) | |
), | |
) | |
let matchFoundCaseStmts = nnkCaseStmt.newTree(matchedRouteHolderName) | |
var counter = 0 | |
for k, v in pairs(dynamicRoutes): | |
var matchFoundMtdCases = nnkCaseStmt.newTree(ident("reqType")) | |
matchFoundCaseStmts.add: | |
nnkOfBranch.newTree(newLit("Route" & $counter), matchFoundMtdCases) | |
for endpoint in v: | |
let (rq, content) = endpoint | |
matchFoundMtdCases.add: | |
nnkOfBranch.newTree(newLit($rq), content) | |
matchFoundMtdCases.add: | |
nnkElse.newTree( | |
newStmtList( | |
newCommentStmtNode("invalid method"), | |
( | |
if methodNotAllowedRoutine.kind != nnkEmpty: | |
methodNotAllowedCall | |
else: | |
defaultProcCall | |
), | |
) | |
) | |
inc(counter) | |
matchFoundCaseStmts.add: | |
nnkElse.newTree( | |
newStmtList(newCommentStmtNode("something went wrong"), defaultProcCall) | |
) | |
matchFoundStatements.add(matchFoundCaseStmts) | |
# Prepare the "nothing matched" arm | |
let caseElseMatch = nnkElse.newTree( | |
newStmtList(newCommentStmtNode("nothing matched"), defaultProcCall) | |
) | |
dynamicRouteRoutine.add(nnkIfStmt.newTree(caseOfMatch, caseElseMatch)) | |
else: | |
dynamicRouteRoutine.add(defaultProcCall) | |
routeSwitch.add(nnkElse.newTree(dynamicRouteRoutine)) | |
# build the part where the path gets split | |
let splitPath = block: | |
let startPosName = genSym(nskVar, "startPos") | |
nnkStmtList.newTree( | |
newCommentStmtNode("Calculate where the path should split"), | |
nnkVarSection.newTree( | |
nnkIdentDefs.newTree(startPosName, newEmptyNode(), newLit(-1)) | |
), | |
nnkForStmt.newTree( | |
newIdentNode("i"), | |
nnkInfix.newTree( | |
newIdentNode("..<"), | |
newLit(0), | |
nnkCall.newTree(newIdentNode("len"), newIdentNode("path")), | |
), | |
nnkStmtList.newTree( | |
nnkCaseStmt.newTree( | |
nnkBracketExpr.newTree(newIdentNode("path"), newIdentNode("i")), | |
nnkOfBranch.newTree( | |
newLit('?'), | |
newLit('&'), | |
nnkStmtList.newTree( | |
nnkAsgn.newTree(startPosName, newIdentNode("i")), | |
nnkBreakStmt.newTree(newEmptyNode()), | |
), | |
), | |
nnkElse.newTree(nnkStmtList.newTree(nnkDiscardStmt.newTree(newEmptyNode()))), | |
) | |
), | |
), | |
nnkLetSection.newTree( | |
nnkIdentDefs.newTree( | |
newIdentNode("pathOnly"), | |
newEmptyNode(), | |
nnkBlockStmt.newTree( | |
newEmptyNode(), | |
nnkStmtList.newTree( | |
nnkVarSection.newTree( | |
nnkIdentDefs.newTree( | |
newIdentNode("p"), | |
newEmptyNode(), | |
nnkIfExpr.newTree( | |
nnkElifExpr.newTree( | |
nnkInfix.newTree(newIdentNode(">"), startPosName, newLit(-1)), | |
nnkStmtList.newTree( | |
nnkBracketExpr.newTree( | |
newIdentNode("path"), | |
nnkInfix.newTree(newIdentNode("..<"), newLit(0), startPosName), | |
) | |
), | |
), | |
nnkElseExpr.newTree(nnkStmtList.newTree(newIdentNode("path"))), | |
), | |
) | |
), | |
newCommentStmtNode("Clean up trailing slash"), | |
nnkIfStmt.newTree( | |
nnkElifBranch.newTree( | |
nnkInfix.newTree( | |
newIdentNode("and"), | |
nnkInfix.newTree( | |
newIdentNode(">"), | |
nnkCall.newTree(newIdentNode("len"), newIdentNode("p")), | |
newLit(1), | |
), | |
nnkInfix.newTree( | |
newIdentNode("=="), | |
nnkBracketExpr.newTree( | |
newIdentNode("p"), | |
nnkPrefix.newTree(newIdentNode("^"), newLit(1)), | |
), | |
newLit('/'), | |
), | |
), | |
nnkStmtList.newTree( | |
nnkBracketExpr.newTree( | |
newIdentNode("p"), | |
nnkInfix.newTree(newIdentNode("..^"), newLit(0), newLit(2)), | |
) | |
), | |
), | |
nnkElse.newTree(nnkStmtList.newTree(newIdentNode("p"))), | |
), | |
), | |
), | |
) | |
), | |
nnkLetSection.newTree( | |
nnkIdentDefs.newTree( | |
newIdentNode("getParams"), | |
newEmptyNode(), | |
nnkBlockStmt.newTree( | |
newEmptyNode(), | |
nnkStmtList.newTree( | |
nnkIfStmt.newTree( | |
nnkElifBranch.newTree( | |
nnkInfix.newTree(newIdentNode(">"), startPosName, newLit(-1)), | |
nnkStmtList.newTree( | |
nnkBracketExpr.newTree( | |
newIdentNode("path"), | |
nnkInfix.newTree( | |
newIdentNode("..^"), | |
nnkPar.newTree( | |
nnkInfix.newTree(newIdentNode("+"), startPosName, newLit(1)) | |
), | |
newLit(1), | |
), | |
) | |
), | |
), | |
nnkElse.newTree(nnkStmtList.newTree(newLit(""))), | |
) | |
), | |
), | |
) | |
), | |
) | |
let pathParamsDefine = nnkVarSection.newTree( | |
nnkIdentDefs.newTree( | |
ident("pathParams"), newEmptyNode(), newCall(ident("newStringTable")) | |
) | |
) | |
# step 4. build the final router proc | |
let builtBody = newStmtList( | |
newProc( | |
ident(name.strVal), | |
[ | |
ident("void"), | |
newIdentDefs(ident("reqType"), ident("string")), | |
newIdentDefs(ident("path"), ident("string")), | |
], | |
newStmtList( | |
defaultProc, methodNotAllowedProc, pathParamsDefine, splitPath, routeSwitch | |
), | |
) | |
) | |
when defined(routerMacroDbg): | |
debugEcho "======BUILT=========" | |
debugEcho builtBody.repr | |
builtBody | |
when isMainModule: | |
import benchy | |
makeRouter "route": | |
get "/": | |
stderr.writeLine "root" | |
get "/static/something": | |
stderr.writeLine "static" | |
get "/{id}/{test}": | |
stderr.writeLine "id => " & pathParams.getOrDefault("id") | |
stderr.writeLine "test => " & pathParams.getOrDefault("test") | |
get "/uploads/{id}": | |
stderr.writeLine "id => " & pathParams.getOrDefault("id") | |
default: | |
stderr.writeLine "default" | |
methodNotAllowed: | |
stderr.writeLine "method not allowed" | |
timeIt "1": | |
route("GET", "/") | |
timeIt "2": | |
route("GET", "/wejfpoewjfpewhr23") | |
timeIt "3": | |
route("GET", "/194/1333") | |
timeIt "4": | |
route("GET", "/uploads/1111") | |
timeIt "5": | |
route("GET", "/uploads/ajndoiq?ekioffiqf&&&klengkleg=&&&Y") | |
# dumpTree: | |
# var capturedPathParams = newStringTable() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment