{"id":13776276,"url":"https://github.com/bungle/lua-resty-route","last_synced_at":"2025-03-17T03:31:35.763Z","repository":{"id":142450275,"uuid":"43500397","full_name":"bungle/lua-resty-route","owner":"bungle","description":"URL Routing Library for OpenResty Supporting Pluggable Matching Engines","archived":false,"fork":false,"pushed_at":"2018-06-26T15:50:12.000Z","size":130,"stargazers_count":99,"open_issues_count":3,"forks_count":27,"subscribers_count":11,"default_branch":"master","last_synced_at":"2024-02-12T21:52:08.405Z","etag":null,"topics":["lua","lua-resty-route","luajit","nginx","openresty","routing","routing-engine"],"latest_commit_sha":null,"homepage":"","language":"Lua","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bungle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2015-10-01T14:15:12.000Z","updated_at":"2024-02-12T21:52:08.406Z","dependencies_parsed_at":"2024-01-06T22:43:01.999Z","dependency_job_id":null,"html_url":"https://github.com/bungle/lua-resty-route","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bungle%2Flua-resty-route","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bungle%2Flua-resty-route/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bungle%2Flua-resty-route/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bungle%2Flua-resty-route/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bungle","download_url":"https://codeload.github.com/bungle/lua-resty-route/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243841207,"owners_count":20356443,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["lua","lua-resty-route","luajit","nginx","openresty","routing","routing-engine"],"created_at":"2024-08-03T18:00:21.721Z","updated_at":"2025-03-17T03:31:35.508Z","avatar_url":"https://github.com/bungle.png","language":"Lua","readme":"# lua-resty-route\n\n**lua-resty-route** is a URL routing library for OpenResty supporting\nmultiple route matchers, middleware, and HTTP and WebSockets handlers\nto mention a few of its features.\n\n## Matchers\n\n`lua-resty-route` supports multiple different matchers on routing. Right now\nwe support these:\n\n* Prefix (case-sensitive and case-insensitive)\n* Equals (case-sensitive and case-insensitive)\n* Match (using Lua's `string.match` function)\n* Regex (case-sensitive and case-insensitive)\n* Simple (case-sensitive and case-insensitive)\n\nMatcher is selected by a prefix in a route's pattern, and they do somewhat\nfollow the Nginx's `location` block prefixes:\n\nPrefix   | Matcher | Case-sensitive | Used by Default\n---------|---------|----------------|----------------\n`[none]` | Prefix  | ✓              | ✓\n`*`      | Prefix  |                |\n`=`      | Equals  | ✓              |\n`=*`     | Equals  |                |\n`#`      | Match   | ¹              |\n`~`      | Regex   | ✓              |\n`~*`     | Regex   |                |\n`@`      | Simple  | ✓              |\n`@*`     | Simple  |                |\n\n¹ Lua `string.match` can be case-sensitive or case-insensitive.\n\n### Prefix Matcher\n\nPrefix, as the name tells, matches only the prefix of the actual location.\nPrefix matcher takes only static string prefixes. If you need anything more\nfancy, take a look at regex matcher. Prefix can be matched case-insensitively\nby prefixing the prefix with `*`, :-). Let's see this in action:\n\n```lua\nroute \"/users\" (function(self) end)\n```\n\nThis route matches locations like:\n\n* `/users`\n* `/users/edit`\n* `/users_be_aware`\n\nBut it **doesn't** match location paths like:\n\n* `/Users`\n* `/USERS/EDIT`\n\nBut those can be still be matched in case-insensitive way:\n\n```lua\nroute \"*/users\" (function(self) end)\n```\n\n### Equals Matcher\n\nThis works the same as the prefix matcher, but with this\nwe match the exact location, to use this matcher, prefix\nthe route with `=`:\n\n```lua\nroute \"=/users\" {\n    get = function(self) end\n}\n```\n\nThis route matches only this location:\n\n* `/users` \n\n\nCase-insensitive variant can be used also:\n\n```lua\nroute \"=*/users\" {\n    get = function(self) end\n}\n```\n\nAnd this of course matches locations like:\n\n* `/users`\n* `/USERS`\n* `/usErs`\n\n### Match Matcher\n\nThis matcher matches patters using Lua's `string.match` function. Nice\nthing about this matcher is that it accepts patterns and also provides\ncaptures. Check Lua's documentation about possible ways to define\n[patterns](https://www.lua.org/manual/5.1/manual.html#5.4.1). Here are\nsome examples:\n\n```lua\nroute \"#/files/(%w+)[.](%w+)\" {\n    get = function(self, file, ext) end\n}\n```\n\nThis will match location paths like:\n\n* `/files/test.txt` etc.\n\nIn that case the provided function (that answers only HTTP `GET`\nrequests in this example), will be called also with these captures:\n`\"test\"` (function argument `file`) and `txt` (function argument `ext`).\n\nFor many, the regular expressions are more familiar and more powerfull. \nThat is what we will look next.\n\n### Regex Matcher\n\nRegex or regular expressions is a common way to do pattern matching.\nOpenResty has support for PCRE compatible regualar expressions, and\nthis matcher in particular, uses `ngx.re.match` function:\n\n```lua\nroute [[~^/files/(\\w+)[.](\\w+)$]] {\n    get = function(self, file, ext) end\n}\n```\n\nAs with the Match matcher example above, the end results are the same\nand the function will be called with the captures.\n\nFor Regex matcher we also have case-insensitive version:\n\n```lua\nroute [[~*^/files/(\\w+)[.](\\w+)$]] {\n    get = function(self, file, ext) end\n}\n```\n\n### Simple Matcher\n\nThis matcher is a specialized and limited version of a Regex matcher\nwith one advantage. It handles type conversions automatically, right\nnow it only supports integer conversion to Lua number. For example:\n\n```lua\nroute:get \"@/users/:number\" (function(self, id) end)\n```\n\nYou could have location path like:\n\n* `/users/45`\n\nThe function above will get `45` as a Lua `number`.\n\nSupported simple capturers are:\n\n* `:string`, that is equal to this regex `[^/]+` (one or more chars, not including `/`)\n* `:number`, that is equal to this regex `\\d+` (one or more digits that can be turned to Lua number using `tonumber` function)\n\nIn future, we may add other capture shortcuts.\n\nOf course there is a case-insensitive version for this matcher as well:\n\n```lua\nroute:get \"@*/users/:number\" (function(self, id) end)\n```\n\nThe simple matcher always matches the location from the beginning to end (partial\nmatches are not considered).\n\n## Routing\n\nThere are many different ways to define routes in `lua-resty-route`.\nIt can be said that it is somewhat a Lua DSL for defining routes.\n\nTo define routes, you first need a new instance of route. This instance\ncan be shared with different requests. You may create the routes in\n`init_by_lua*`. Here we define a new route instance:\n\n```lua\nlocal route = require \"resty.route\".new()\n```\n\nNow that we do have this `route` instance, we may continue to a next\nsection, [HTTP Routing](#http-routing).\n\n**Note:** Routes are tried in the order they are added when dispatched.\nThis differs from how Nginx itself handles the `location` blocks.\n\n### HTTP Routing\n\nHTTP routing is the most common thing to do in web related routing. That's\nwhy HTTP routing is the default way to route in `lua-resty-route`. Other\ntypes of routing include e.g. [WebSockets routing](#websockets-routing).\n\nThe most common HTTP request methods (sometimes referred to as verbs) are:\n\nMethod   | Definition\n---------|-----------\n`GET`    | Read\n`POST`   | Create\n`PUT`    | Update or Replace\n`PATCH`  | Update or Modify\n`DELETE` | Delete\n\nWhile these are the most common ones, `lua-resty-route` is not by any means\nrestricted to these. You may use whatever request methods there is just like\nthese common ones. But to keep things simple here, we will just use these in\nthe examples.\n\n#### The General Pattern in Routing\n\n```lua\nroute(...)\nroute:method(...)\n```\n\nor\n\n```lua\nroute(method, pattern, func)\nroute:method(pattern, func)\n```\n\ne.g.:\n\n```lua\nroute(\"get\", \"/\", function(self) end)\nroute:get(\"/\", function(self) end)\n```\n\nOnly the first function argument is mandatory. That's why we can\ncall these functions in a quite flexible ways. For some `methods`,\ne.g. websocket, we can pass a `table` instead of a `function` as\na route handler. Next we look at different ways to call these\nfunctions.\n\n#### Defining Routes as a Table\n\n```lua\nroute \"=/users\" {\n    get  = function(self) end,\n    post = function(self) end\n}\nlocal users = {\n    get  = function(self) end,\n    post = function(self) end\n}\nroute \"=/users\" (users)\nroute(\"=/users\", users)\n```\n\n#### Using Lua Packages for Routing\n\n```lua\nroute \"=/users\"  \"controllers.users\"\nroute(\"=/users\", \"controllers.users\")\n```\n\nThese are same as:\n\n```lua\nroute(\"=/users\", require \"controllers.users\")\n```\n\n#### Defining Multiple Methods at Once\n\n```lua\nroute { \"get\", \"head\" } \"=/users\" (function(self) end)\n```\n\n#### Defining Multiple Routes at Once\n\n```lua\nroute {\n    [\"/\"] = function(self) end,\n    [\"=/users\"] = {\n        get  = function(self) end,\n        post = function(self) end\n    }\n}\n```\n\n#### Routing all the HTTP Request Methods\n\n```lua\nroute \"/\" (function(self) end)\nroute(\"/\", function(self) end)\n```\n\n#### The Catch all Route\n\n```lua\nroute(function(self) end)\n```\n\n#### Going Crazy with Routing\n\n```lua\nroute:as \"@home\" (function(self) end)\nroute {\n    get = {\n        [\"=/\"] = \"@home\",\n        [\"=/users\"] = function(self) end\n    },\n    [\"=/help\"] = function(self) end,\n    [{ \"post\", \"put\"}] = {\n        [\"=/me\"] = function(self)\n        end\n    },\n    [\"=/you\"] = {\n        [{ \"get\", \"head\" }] = function(self) end\n    },\n    [{ \"/files\", \"/cache\" }] = {\n        -- requiring controllers.filesystem returns a function\n        [{\"get\", \"head\" }] = \"controllers.filesystem\"\n    }\n}\n```\n\nAs you may see this is pretty freaky. But it doesn't actually\nstop here. I haven't even mentioned things like callable Lua\ntables (aka tables with metamethod `__call`) or web sockets\nrouting. They are supported as well.\n\n### WebSockets Routing\n\n### File System Routing\n\nFile system routing is based on a file system tree. This could be\nconsidered as a routing by a convention. File system routing depends\non either [LuaFileSystem](https://github.com/keplerproject/luafilesystem)\nmodule or a preferred and LFS compatible\n[ljsyscall](https://github.com/justincormack/ljsyscall).\n\nAs an example, let's consider that we do have this kind of file tree:\n\n```\n/routing/\n ├─ index.lua \n ├─ users.lua\n └─ users/\n │  ├─ view@get.lua\n │  ├─ edit@post.lua\n │  └─ #/\n │     └─ index.lua\n └─ page/\n    └─ #.lua\n```\n\nThis file tree will provide you with the following routes:\n\n- `@*/` → `index.lua`\n- `@*/users` → `users.lua`\n- `@*/users/view` → `users/view@get.lua` (only GET requests are routed here)\n- `@*/users/edit` → `users/edit@post.lua` (only POST requests are routed here)\n- `@*/users/:number` → `users/#/index.lua`\n- `@*/page/:number` → `page/#.lua`\n\nThe files could look like this (just an example):\n\n`index.lua`:\n\n```lua\nreturn {\n    get  = function(self) end,\n    post = function(self) end\n}\n```\n\n`users.lua`:\n\n```lua\nreturn {\n    get    = function(self) end,\n    post   = function(self) end,\n    delete = function(self) end\n}   \n```\n\n`users/view@get.lua`:\n\n```lua\nreturn function(self) end\n```\n\n`users/edit@post.lua`:\n\n```lua\nreturn function(self) end\n```\n\n`users/#/index.lua`:\n\n```lua\nreturn {\n    get    = function(self, id) end,\n    put    = function(self, id) end,\n    post   = function(self, id) end,\n    delete = function(self, id) end\n}\n```\n\n`page/#.lua`:\n\n```lua\nreturn {\n    get    = function(self, id) end,\n    put    = function(self, id) end,\n    post   = function(self, id) end,\n    delete = function(self, id) end\n}\n```\n\nTo define routes based on file system tree you will need to call `route:fs`\nfunction:\n\n```lua\n-- Here we assume that you do have /routing directory\n-- on your file system. You may use whatever path you\n-- like, absolute or relative.\nroute:fs \"/routing\"\n```\n\nUsing file system routing you can just add new files to file system tree,\nand they will be added automatically as a routes.\n\n### Named Routes\n\nYou can define named route handlers, and then reuse them in actual routes.\n\n```lua\nroute:as \"@home\" (function(self) end)\n```\n\n(the use of `@` as a prefix for a named route is optional)\n\nAnd here we actually attach it to a route:\n\n```lua\nroute:get \"/\" \"@home\"\n```\n\nYou can also define multiple named routes in a one go:\n\n```lua\nroute:as {\n    home    = function(self) end,\n    signin  = function(self) end,\n    signout = function(self) end\n}\n```\n\nor if you want to use prefixes:\n\n```lua\nroute:as {\n    [\"@home\"]    = function(self) end,\n    [\"@signin\"]  = function(self) end,\n    [\"@signout\"] = function(self) end\n}\n```\n\nNamed routes must be defined before referencing them in routes.\nThere are or will be other uses to named routers as well. On todo\nlist there are things like reverse routing and route forwarding to\na named route.\n\n## Middleware\n\nMiddleware in `lua-resty-route` can be defined on either on per request\nor per route basis. Middleware are filters that you can add to the request\nprocessing pipeline. As `lua-resty-route` tries to be as unopionated as\npossible we don't really restrict what the filters do or how they have to\nbe written. Middleware can be inserted just flexible as routes, and they\nactually do share much of the logic. With one impotant difference. You can\nhave multiple middleware on the pipeline whereas only one matchin route\nwill be executed. The middleware can also be yielded (`coroutine.yield`),\nand that allows code to be run before and after the router (you can yield\na router as well, but that will never be resumed). If you don't yield,\nthen the middleware is considered as a before filter.\n\nThe most common type of Middleware is request level middleware:\n\n```lua\nroute:use(function(self)\n    -- This code will be run before router:\n    -- ...\n    self.yield() -- or coroutine.yield()\n    -- This code will be run after the router:\n    -- ...\nend)\n```\n\nNow, as you were already hinted, you may add filters to specific routes as well:\n\n```lua\nroute.filter \"=/\" (function(self)\n    -- this middleware will only be called on a specific route\nend)\n```\n\nYou can use the same rules as with routing there, e.g.\n\n```lua\nroute.filter:post \"middleware.csrf\"\n```\n\nOf course you can also do things like:\n\n```lua\nroute.filter:delete \"@/users/:number\" (function(self, id)\n    -- here we can say prevent deleting the user who\n    -- issued the request or something.\nend)\n```\n\nAll the matching middleware is run on every request, unless one of them\ndecides to `exit`, but we do always try to run after filters for those\nmiddleware that already did run, and yielded. But we will call them in\nreverse order:\n\n1. middleware 1 runs and yields\n3. middleware 2 runs (and finishes)\n4. middleware 3 runs and yields\n5. router runs\n6. middleware 3 resumes\n7. middleware 1 resumes\n\nThe order of middleware is by scope:\n\n1. request level middleware is executed first\n2. router level middleware is executed second\n\nIf there are multiple requet or router level middleware, then they will be\nexecuted the same order they were added to a specific scope. Yielded middleware\nis executed in reverse order. Yielded middleware will only be resumed once.\n\nInternally we do use Lua's great `coroutines`.\n\nWe are going to support a bunch of predefined middleware in a future.\n\n## Events\n\nEvents allow you to register specialized handlers for different HTTP status\ncodes or other predefined event codes. There can be only one handler for each\ncode or code group.\n\nYou can for example define `404` aka route not found handler like this:\n\n```lua\nroute:on(404, function(self) end)\n```\n\nSome groups are predefined, e.g.:\n\n* `info`, status codes 100 – 199\n* `success`, status codes 200 – 299\n* `redirect`, status codes 300 – 399\n* `client error`, status codes 400 – 499\n* `server error`, status codes 500 – 599\n* `error`, status codes 400 – 599\n\nYou may use groups like this:\n\n```lua\nroute:on \"error\" (function(self, code) end)\n```\n\nYou can also define multiple event handlers in a one go:\n\n```lua\nroute:on {\n    error   = function(self, code) end,\n    success = function(self, code) end,\n    [302]   = function(self) end\n}\n```\n\nThen there is a generic catch-all event handler:\n\n```lua\nroute:on(function(self, code) end)\n```\n\nWe will find the right event handler in this order:\n\n1. if there is a specific handler for a specific code, we will call that\n2. if there is a group handler for specific code, we will call that\n3. if there is a catch-all handler, we will call that\n\nOnly one of these is called per event.\n\nIt is possible that we will add other handlers in a future where you could\nhook on.\n\n### Router API\n\nYou may have seen in previous examples functions get as a first\nparameter a `self`. The `self` represents a `router` that contains\nmany nice functions documented below.\n\nWhile the above so called `Route API` is for defining the routes,\nthe `Router API` is actually about running the routes.\n\n#### router.context\n\nThis is really powerful concept here to share data between\ndifferent routes and functions. Many middleware will be\ninserted to context.\n\nE.g. a redis middleware could add `redis` object to `context`\nso that you could just:\n\n```lua\nlocal ok, err = self.redis:set(\"cat\", \"tommy\")\n```\n\nOpening and closing the Redis connection is something that the\nmiddleware does automatically before scenes. It means that you\ndon't need to initiate or close the connections to Redis server,\nbut this small `framework` takes care of this. As you see, this\n`self` parameter is automatically passed around different layers\nof this framework, and this context makes it easy to pass data\nbetween them.\n\n#### router.yield()\n\nIs similar to `coroutine.yield()` but as you have seen above\nin middlewares section, it is quite nice to just call `self.yield()`\ninstead to split middleware to before and after `filters`,\nit also makes us possible to add e.g. debugging / profiling code\nin a future. `self.yield()` is more self explaining what happens\nand makes code easier to read (may be subjective opinion).\n\n#### router:redirect(uri, code)\n\nSimilar to `ngx.redirect` but runs redirect event handler and\nafter filters before actually calling `ngx.redirect` with `code`\n(or `ngx.HTTP_MOVED_TEMPORARILY` if not specified) and ending\nthe handler.\n\n#### router:exit(uri, code)\n\nSimilar to `ngx.exit` but runs event handler and after filters\nbefore actually calling `ngx.exit` with `code` (or `ngx.OK`\nif not specified) and ending the handler.\n\n#### router:exec(uri, args)\n\nSimilar to `ngx.exec` but runs event handler and after filters\nbefore actually calling `ngx.exec` and ending the handler. \n\n#### router:done()\n\nSimilar to `ngx.exit` with `ngx.HTTP_OK` but runs event handler\nand after filters before actually calling `ngx.exit` and ending\nthe handler.\n\n#### router:abort()\n\nThis is reserved for `ngx.on_abort` usage (NYI). Right now only\ncalls `ngx.exit(499)` after running event handler and after\nfilters.\n\n#### router:fail(error, code)\n\nIf `error` is a string, then logs it to error log. Otherwise it\nis similar to `ngx.exit(code)` (by default the `code` is\n`ngx.HTTP_INTERNAL_SERVER_ERROR`) but runs event handler and\nafter filters before actually calling `ngx.exit`and ending\nthe handler.\n\n#### router:to(location, method)\n\nAllows you to execute another route (defined by `route`).\n\n#### router:render(content, context)\n\nWrites content to output stream. If there is a `context.template`\nthen it will call `context.template.render(content, context or self.context)`.\n\n#### router:json(data)\n\nEncodes data as JSON, adds `application/json` content-type\nheader and outputs the JSON.\n\n#### router:*\n\nA lot more can be added here to make writing code less repetive,\nbut a lot can be done with injecting into `self.context` as well.\n\n## Roadmap\n\nThis is a small collection of ideas that may or may not be implemented as\na part of `lua-resty-route`.\n\n* Add documentation\n* Add tests\n* Rewrite current middleware and add new ones\n* Rewrite current websocket handler\n* Add route statistics\n* Add an automatic route cleaning and redirecting (possibly configurable) (clean function is already written)\n* Add an automatic slash handling and redirecting (possibly configurable)\n* Add a more automated way to define redirects\n* Add a support for route caching\n* Add a support to route by host\n* Add a support to route by headers\n* Add a support for Nginx phases\n* Add a support for easy way to define Web Hooks routes\n* Add a support for easy way to define Server Sent Events routes\n* Add a support for \"provides\", e.g. renderers (?)\n* Add a support for conditions, e.g. content negotiation\n* Add a support for route grouping (already possible on Nginx at config level)\n* Add a support for reverse routing\n* Add a support for form method spoofing\n* Add a support for client connection abort event handler (`ngx.on_abort`)\n* Add a support for host (and possibly) other headers filtering\n* Add a support for basic authentication\n* Add a support for JWT / OpenID Connect authentication\n* Add bootstrapping functionality from Nginx configs\n* Add support for resources (or view sets) (a more automated REST-routing)\n* Add filesystem routing support for resources (or view sets)\n\n## See Also\n\n* [lua-resty-reqargs](https://github.com/bungle/lua-resty-reqargs) — Request arguments parser\n* [lua-resty-session](https://github.com/bungle/lua-resty-session) — Session library\n* [lua-resty-template](https://github.com/bungle/lua-resty-template) — Templating engine\n* [lua-resty-validation](https://github.com/bungle/lua-resty-validation) — Validation and filtering library\n\n## License\n\n`lua-resty-route` uses two clause BSD license.\n\n```\nCopyright (c) 2015 – 2017, Aapo Talvensaari\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n* Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n* Redistributions in binary form must reproduce the above copyright notice, this\n  list of conditions and the following disclaimer in the documentation and/or\n  other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES`\n","funding_links":[],"categories":["Libraries"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbungle%2Flua-resty-route","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbungle%2Flua-resty-route","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbungle%2Flua-resty-route/lists"}