{"id":13712480,"url":"https://github.com/go101/tinyrouter","last_synced_at":"2025-04-14T11:22:27.292Z","repository":{"id":57481132,"uuid":"140955884","full_name":"go101/tinyrouter","owner":"go101","description":"An ~O(2k) time complexity http request router in Go","archived":false,"fork":false,"pushed_at":"2019-09-11T16:04:14.000Z","size":43,"stargazers_count":44,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"v1","last_synced_at":"2025-03-28T00:41:31.359Z","etag":null,"topics":["go","golang","http-router"],"latest_commit_sha":null,"homepage":"https://godoc.org/go101.org/tinyrouter","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/go101.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}},"created_at":"2018-07-14T15:14:35.000Z","updated_at":"2024-06-09T20:39:18.000Z","dependencies_parsed_at":"2022-09-26T17:50:39.916Z","dependency_job_id":null,"html_url":"https://github.com/go101/tinyrouter","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go101%2Ftinyrouter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go101%2Ftinyrouter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go101%2Ftinyrouter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/go101%2Ftinyrouter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/go101","download_url":"https://codeload.github.com/go101/tinyrouter/tar.gz/refs/heads/v1","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248869112,"owners_count":21174819,"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":["go","golang","http-router"],"created_at":"2024-08-02T23:01:18.960Z","updated_at":"2025-04-14T11:22:27.267Z","avatar_url":"https://github.com/go101.png","language":"Go","funding_links":[],"categories":["Repositories"],"sub_categories":[],"readme":"\n**NOTE**: if your project supports Go modules, then the import path of this package is `go101.org/tinyrouter`,\n\n### What?\n\nTineyRouter is a tiny Go http router supporting custom parameters in paths\n(500 lines of code).\n\nThe Go package implements an **_O(2k)_** complexity algorithm (usual case) to route HTTP requests.\nwhere **_k_** is the length of a HTTP request path.\n\n### Why?\n\nFor a long time, Julien Schmidt's [HttpRouter](https://github.com/julienschmidt/HttpRouter)\nis my favorite http router and is used in my many Go projects.\nFor most cases, HttpRouter works very well.\nHowever, sometimes HttpRouter is some frustrating for [lacking of flexibity](https://github.com/julienschmidt/HttpRouter/search?q=conflicts\u0026type=Issues).\nFor example, the path patterns in each of the following groups are conflicted with each other in HttpRouter.\n\n```\n\t// 1\n\t/organizations/:param1/members/:param2\n\t/organizations/:abc/projects/:param2\n\n\t// 2\n\t/v1/user/selection\n\t/v1/:name/selection\n\n\t// 3\n\t/v2/:user/info\n\t/v2/:user/:group\n\n\t// 4\n\t/v3/user/selection\n\t/v3/:name\n\n\t// 5\n\t/sub/:group/:item\n\t/sub/:id\n\t\n\t// 6\n\t/a/b/:c\n\t/a/:b/c\n\t/a/:b/:c\n\t/:a/b/c\n\t/:a/:b/:c\n```\n\nTinyRouter is router implementation between HttpRouter and [gorilla/mux](https://github.com/gorilla/mux),\nfrom both performance and flexibility views.\nIn practice, for most general cases, TinyRouter is pretty fast.\nAnd, the above routes which don't work in HttpRouter all work fine in TinyRouter.\n\nLike many other router packages, a token in path patterns starting a `:`\nis viewed as a parameter. Regexp patterns are not supported by TinyRouter.\n\nAn example by using TinyRouter:\n\n```golang\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\n\ttiny \"github.com/go101/tinyrouter\"\n\t// tiny \"go101.org/tinyrouter\" // If your project supports Go modules, please use this line instead.\n)\n\nfunc main() {\n\troutes := []tiny.Route{\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPattern: \"/design/:user/:slot\",\n\t\t\tHandleFunc: func(w http.ResponseWriter, req *http.Request) {\n\t\t\t\tparams := tiny.PathParams(req)\n\t\t\t\tfmt.Fprintln(w, \"/design/:user/:slot\", \"user:\", params.Value(\"user\"), \"slot:\", params.Value(\"slot\"))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPattern: \"/design/:user/:slot/settings/show\",\n\t\t\tHandleFunc: func(w http.ResponseWriter, req *http.Request) {\n\t\t\t\tparams := tiny.PathParams(req)\n\t\t\t\tfmt.Fprintln(w, \"/design/:user/:slot/settings/show\", \"user:\", params.Value(\"user\"), \"slot:\", params.Value(\"slot\"))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tMethod: \"POST\",\n\t\t\tPattern: \"/design/:user/:slot/settings/update\",\n\t\t\tHandleFunc: func(w http.ResponseWriter, req *http.Request) {\n\t\t\t\tparams := tiny.PathParams(req)\n\t\t\t\tfmt.Fprintln(w, \"/design/:user/:slot/settings/update\", \"user:\", params.Value(\"user\"), \"slot:\", params.Value(\"slot\"))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tMethod: \"POST\",\n\t\t\tPattern: \"/design/:user/:slot/delete\",\n\t\t\tHandleFunc: func(w http.ResponseWriter, req *http.Request) {\n\t\t\t\tparams := tiny.PathParams(req)\n\t\t\t\tfmt.Fprintln(w, \"/design/:user/:slot/delete\", \"user:\", params.Value(\"user\"), \"slot:\", params.Value(\"slot\"))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPattern: \"/design/:uuid\",\n\t\t\tHandleFunc: func(w http.ResponseWriter, req *http.Request) {\n\t\t\t\tparams := tiny.PathParams(req)\n\t\t\t\tfmt.Fprintln(w, \"/design/:uuid\", \"uuid =\", params.Value(\"uuid\"))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPattern: \"/design/:uuid/stat\",\n\t\t\tHandleFunc: func(w http.ResponseWriter, req *http.Request) {\n\t\t\t\tparams := tiny.PathParams(req)\n\t\t\t\tfmt.Fprintln(w, \"/design/:uuid/stat\", \"uuid =\", params.Value(\"uuid\"))\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPattern: \"/\",\n\t\t\tHandleFunc: func(w http.ResponseWriter, req *http.Request) {\n\t\t\t\tfmt.Fprintln(w, \"/\")\n\t\t\t},\n\t\t},\n\t\t{\n\t\t\tMethod: \"GET\",\n\t\t\tPattern: \"/:sitepage\",\n\t\t\tHandleFunc: func(w http.ResponseWriter, req *http.Request) {\n\t\t\t\tparams := tiny.PathParams(req)\n\t\t\t\tfmt.Fprintln(w, \"/\", \"sitepage =\", params.Value(\"sitepage\"))\n\t\t\t},\n\t\t},\n\t}\n\t\n\tconfig := tiny.Config{\n\t\tRoutes:           routes,\n\t\tOthersHandleFunc: func(w http.ResponseWriter, req *http.Request) {\n\t\t\tfmt.Fprintln(w, \"other pages\")\n\t\t},\n\t}\n\t\n\trouter := tiny.New(config)\n\n\tlog.Println(\"Starting service ...\")\n\tlog.Fatal(http.ListenAndServe(\":8080\", router))\n}\n```\n\nFixed tokens in patterns have higher precedences than parameterized ones.\nLeft tokens have higher precedences than right ones.\nThe following patterns are shown by their precedence:\n```\n1: /a/b/:c\n2: /a/:b/c\n4: /a/:b/:c\n3: /:a/b/c\n5: /:a/:b/:c\n```\nSo,\n* `/a/b/c` will match the 1st one\n* `a/x/c` will match the 2nd one,\n* `a/x/y` will match the 2nd one,\n* `/x/b/c` will match the 4th one.\n* `/y/x/z` will match the 5th one.\n\nThe match rules and results are the same as Gorilla/Mux.\n\n### How?\n\nThe TinyRouter implementation groups routes:\n1. first by request methods.\n1. then by number of tokens (or called segments) in path patterns.\n1. then (for the 1st segment in patterns), by wildcard (parameterized) or not. Non-wildcard segments are called fixed segments.\n1. then for the segments in the fixed group in the last step, group them by their length.\n1. for each group with the same token length, sort the segments in it.\n\n(Repeat the last two steps for 2nd, 3rd, ..., segments.)\n\nWhen a request comes, its URL path will be parsed into tokens (one **k** in **_O(2k + N)_**).\n1. The route group with the exact reqest method will be selected.\n1. Then the route sub-group (by number of tokens) with the exact number of tokens will be selected.\n1. Then, for the 1st token, find the start segment with the same length in the fixed groups\n   and start comparing the token with the same-length segments.\n   Most `len(token)` bytes will be compared in this comparision.\n   If a fixed match is found, then try to find the match for the next token.\n   If no matches are found, then try to find the match for next token in the wildcard group.\n\n(Repeat the last step, until a match is found or return without any matches.\nAnother **k** and the **N** happen in the process.\nSome micro-optimizations, by using some one-time built information,\nin the process make the usual time complexity become to **_O(2k + N/m)_**.)\n\nFor a project with 20 routes per method with a certain number of segments in path,\n**_N/m_** would be about 5, whcih is much smaller than **k**, which is about 16-64.\nSo the usual time complexity of this algorithm is about two times of a radix implementation\n(see [the benchmarks](benchmarks/benchmark.md) for details).\nThe benefit is there are less limits for the route patterns.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo101%2Ftinyrouter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgo101%2Ftinyrouter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgo101%2Ftinyrouter/lists"}