{"id":13786714,"url":"https://github.com/bmf-san/goblin","last_synced_at":"2025-04-06T10:13:21.377Z","repository":{"id":41469830,"uuid":"194352396","full_name":"bmf-san/goblin","owner":"bmf-san","description":"A golang http router based on trie tree.","archived":false,"fork":false,"pushed_at":"2024-12-26T10:28:50.000Z","size":10123,"stargazers_count":79,"open_issues_count":2,"forks_count":6,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-30T09:07:29.961Z","etag":null,"topics":["go","golang","http","http-router","httprouter","middleware","nethttp","router","routing","trie","trie-tree","url-router"],"latest_commit_sha":null,"homepage":"","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/bmf-san.png","metadata":{"files":{"readme":"README-ja.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["bmf-san"]}},"created_at":"2019-06-29T01:44:20.000Z","updated_at":"2024-12-26T10:36:03.000Z","dependencies_parsed_at":"2024-06-18T20:13:40.349Z","dependency_job_id":"3c6d2885-3e22-4be3-af2f-e895423a4858","html_url":"https://github.com/bmf-san/goblin","commit_stats":null,"previous_names":[],"tags_count":55,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmf-san%2Fgoblin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmf-san%2Fgoblin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmf-san%2Fgoblin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bmf-san%2Fgoblin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bmf-san","download_url":"https://codeload.github.com/bmf-san/goblin/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247464222,"owners_count":20942970,"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","http-router","httprouter","middleware","nethttp","router","routing","trie","trie-tree","url-router"],"created_at":"2024-08-03T19:01:30.491Z","updated_at":"2025-04-06T10:13:21.351Z","avatar_url":"https://github.com/bmf-san.png","language":"Go","funding_links":["https://github.com/sponsors/bmf-san"],"categories":["Web框架","Routers","Web Frameworks","Go"],"sub_categories":["路由器","HTTP Clients","Routers"],"readme":"[English](https://github.com/bmf-san/goblin) [日本語](https://github.com/bmf-san/goblin/blob/master/README-ja.md)\n\n# goblin\n[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go)\n[![GitHub release](https://img.shields.io/github/release/bmf-san/goblin.svg)](https://github.com/bmf-san/goblin/releases)\n[![CircleCI](https://circleci.com/gh/bmf-san/goblin/tree/master.svg?style=svg)](https://circleci.com/gh/bmf-san/goblin/tree/master)\n[![Go Report Card](https://goreportcard.com/badge/github.com/bmf-san/goblin)](https://goreportcard.com/report/github.com/bmf-san/goblin)\n[![codecov](https://codecov.io/gh/bmf-san/goblin/branch/master/graph/badge.svg?token=ZLOLQKUD39)](https://codecov.io/gh/bmf-san/goblin)\n[![GitHub license](https://img.shields.io/github/license/bmf-san/goblin)](https://github.com/bmf-san/goblin/blob/master/LICENSE)\n[![Go Reference](https://pkg.go.dev/badge/github.com/bmf-san/goblin.svg)](https://pkg.go.dev/github.com/bmf-san/goblin)\n[![Sourcegraph](https://sourcegraph.com/github.com/bmf-san/goblin/-/badge.svg)](https://sourcegraph.com/github.com/bmf-san/goblin?badge)\n\nトライ木をベースにしたGo製のHTTP Routerです。\n\n\u003cimg src=\"https://storage.googleapis.com/gopherizeme.appspot.com/gophers/d654ddf2b81c2b4123684f93071af0cf559eb0b5.png\" alt=\"goblin\" title=\"goblin\" width=\"250px\"\u003e\n\nこのロゴは[gopherize.me](https://gopherize.me/gopher/d654ddf2b81c2b4123684f93071af0cf559eb0b5)で作成しました。\n\n# 目次\n- [goblin](#goblin)\n- [目次](#目次)\n- [特徴](#特徴)\n- [インストール](#インストール)\n- [例](#例)\n- [使い方](#使い方)\n  - [メソッドベースのルーティング](#メソッドベースのルーティング)\n  - [名前付きパラメータのルーティング](#名前付きパラメータのルーティング)\n  - [正規表現を使ったルーティング](#正規表現を使ったルーティング)\n  - [ミドルウェア](#ミドルウェア)\n  - [カスタム可能なエラーハンドラー](#カスタム可能なエラーハンドラー)\n  - [デフォルトOPTIONSハンドラー](#デフォルトoptionsハンドラー)\n- [ベンチマークテスト](#ベンチマークテスト)\n- [設計](#設計)\n- [Wiki](#wiki)\n- [コントリビューション](#コントリビューション)\n- [スポンサー](#スポンサー)\n- [Stargazers](#stargazers)\n- [Forkers](#forkers)\n- [ライセンス](#ライセンス)\n  - [作者](#作者)\n\n# 特徴\n- Go1.20 \u003e= 1.16\n- トライ木をベースとしたシンプルなデータ構造\n- 軽量\n  - Lines of codes:2428\n  - Package size: 140K\n- 標準パッケージ以外の依存性なし\n- net/httpとの互換性\n- net/httpの[Servemux](https://pkg.go.dev/net/http#ServeMux)よりも高機能\n  - メソッドベースのルーティング\n  - 名前付きパラメータのルーティング\n  - 正規表現を使ったルーティング\n  - ミドルウェア\n  - カスタム可能なエラーハンドラー\n  - デフォルトOPTIONSハンドラー\n- 0allocs\n  - 静的なルーティングにおいて0allocsを達成\n  - 名前付きルーティングについては3allocs程度\n    - パラメータのslice生成やパラメータをcontextに格納する部分でヒープ割当が発生\n\n# インストール\n```sh\ngo get -u github.com/bmf-san/goblin\n```\n\n# 例\nサンプルの実装を用意しています。\n\n[example_goblin_test.go](https://github.com/bmf-san/goblin/blob/master/example_goblin_test.go)をご参照ください。\n\n# 使い方\n## メソッドベースのルーティング\n任意のHTTPメソッドに基づいてルーティングを定義することができます。\n\n以下のHTTPメソッドをサポートしています。\n`GET/POST/PUT/PATCH/DELETE/OPTIONS`\n\n```go\nr := goblin.NewRouter()\n\nr.Methods(http.MethodGet).Handler(`/`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprintf(w, \"/\")\n}))\n\nr.Methods(http.MethodGet, http.MethodPost).Handler(`/methods`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    if r.Method == http.MethodGet {\n        fmt.Fprintf(w, \"GET\")\n    }\n    if r.Method == http.MethodPost {\n        fmt.Fprintf(w, \"POST\")\n    }\n}))\n\nhttp.ListenAndServe(\":9999\", r)\n```\n\n## 名前付きパラメータのルーティング\n名前付きパラメータ(`:paramName`)を使ったルーティングを定義することができます。\n\n```go\nr := goblin.NewRouter()\n\nr.Methods(http.MethodGet).Handler(`/foo/:id`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    id := goblin.GetParam(r.Context(), \"id\")\n    fmt.Fprintf(w, \"/foo/%v\", id)\n}))\n\nr.Methods(http.MethodGet).Handler(`/foo/:name`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    name := goblin.GetParam(r.Context(), \"name\")\n    fmt.Fprintf(w, \"/foo/%v\", name)\n}))\n\nhttp.ListenAndServe(\":9999\", r)\n```\n\n## 正規表現を使ったルーティング\n名前付きパラメータに正規表現を使うこと(`:paramName[pattern]`)で正規表現を使ったルーティングを定義することができます。\n\n```go\nr.Methods(http.MethodGet).Handler(`/foo/:id[^\\d+$]`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    id := goblin.GetParam(r.Context(), \"id\")\n    fmt.Fprintf(w, \"/foo/%v\", id)\n}))\n```\n\n## ミドルウェア\nリクエストの前処理、レスポンスの後処理に役立つミドルウェアをサポートしています。\n\n任意のルーティングに対してミドルウェアを定義することができます。\n\nグローバルにミドルウェアを設定することもできます。グローバルにミドルウェアを設定すると、すべてのルーティングにミドルウェアが適用されるようになります。\n\nミドルウェアは1つ以上設定することができます。\n\nミドルウェアはhttp.Handlerを返す関数として定義する必要があります。\n\n```go\n// http.Handlerを返す関数としてミドルウェアを実装\nfunc global(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"global: before\\n\")\n\t\tnext.ServeHTTP(w, r)\n\t\tfmt.Fprintf(w, \"global: after\\n\")\n\t})\n}\n\nfunc first(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"first: before\\n\")\n\t\tnext.ServeHTTP(w, r)\n\t\tfmt.Fprintf(w, \"first: after\\n\")\n\t})\n}\n\nfunc second(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"second: before\\n\")\n\t\tnext.ServeHTTP(w, r)\n\t\tfmt.Fprintf(w, \"second: after\\n\")\n\t})\n}\n\nfunc third(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"third: before\\n\")\n\t\tnext.ServeHTTP(w, r)\n\t\tfmt.Fprintf(w, \"third: after\\n\")\n\t})\n}\n\nr := goblin.NewRouter()\n\n// グローバルにミドルウェアを設定\nr.UseGlobal(global)\nr.Methods(http.MethodGet).Handler(`/globalmiddleware`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprintf(w, \"/globalmiddleware\\n\")\n}))\n\n// Useメソッドを使用することでミドルウェアを適用できます\nr.Methods(http.MethodGet).Use(first).Handler(`/middleware`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprintf(w, \"middleware\\n\")\n}))\n\n// ミドルウェアは複数設定することができます\nr.Methods(http.MethodGet).Use(second, third).Handler(`/middlewares`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n    fmt.Fprintf(w, \"middlewares\\n\")\n}))\n\nhttp.ListenAndServe(\":9999\", r)\n```\n\n`/globalmiddleware`にリクエストすると、次のような結果が得られます。\n\n```\nglobal: before\n/globalmiddleware\nglobal: after\n```\n\n`/middleware`にリクエストすると、次のような結果が得られます。\n\n```\nglobal: before\nfirst: before\nmiddleware\nfirst: after\nglobal: after\n```\n\n`/middlewares`にリクエストすると、次のような結果が得られます。\n\n```\nglobal: before\nsecond: before\nthird: before\nmiddlewares\nthird: after\nsecond: after\nglobal: after\n```\n\n## カスタム可能なエラーハンドラー\n独自のエラーハンドラーを定義することができます。\n\n定義可能なエラーハンドラは以下の2種類です。\n\n- NotFoundHandler\n  - ルーティングにマッチする結果が得られなかったときに実行されるハンドラです\n- MethodNotAllowedHandler\n  - マッチするメソッドがなかった場合に実行されるハンドラです\n\n```go\nfunc customMethodNotFound() http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"customMethodNotFound\")\n\t})\n}\n\nfunc customMethodAllowed() http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tfmt.Fprintf(w, \"customMethodNotAllowed\")\n\t})\n}\n\nr := goblin.NewRouter()\nr.NotFoundHandler = customMethodNotFound()\nr.MethodNotAllowedHandler = customMethodAllowed()\n\nhttp.ListenAndServe(\":9999\", r)\n```\n\n## デフォルトOPTIONSハンドラー\nOPTIONSメソッドでのリクエストの際に実行されるデフォルトのハンドラを定義することができます。\n\n```go\nfunc DefaultOPTIONSHandler(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n        w.WriteHeader(http.StatusNoContent)\n\t})\n}\n\nr := goblin.NewRouter()\nr.DefaultOPTIONSHandler = DefaultOPTIONSHandler()\n\nhttp.ListenAndServe(\":9999\", r)\n```\n\nデフォルトOPTIONSハンドラーは例えば、CORSのOPTIONSリクエスト（preflight request）の対応などに役立ちます。\n\n# ベンチマークテスト\ngoblinのベンチマークテストを実行するコマンドを用意しています。\n\n[Makefile](https://github.com/bmf-san/goblin/blob/master/Makefile)をご参照ください。\n\n他のHTTP Routerとのベンチマーク比較結果が気になりますか？\n\nこちらをご覧ください！\n[bmf-san/go-router-benchmark](https://github.com/bmf-san/go-router-benchmark)\n\n# 設計\ngoblinの内部的なデータ構造について解説します。\n\nパフォーマンスが最適化されたHTTP Routerにおいては、[基数木](https://ja.wikipedia.org/wiki/%E5%9F%BA%E6%95%B0%E6%9C%A8)が採用されていることが多いですが、goblinは[トライ木](https://ja.wikipedia.org/wiki/%E3%83%88%E3%83%A9%E3%82%A4_(%E3%83%87%E3%83%BC%E3%82%BF%E6%A7%8B%E9%80%A0))をベースとしたデータ構造を採用しています。\n\n基数木と比較すると、トライ木はメモリ使用量に劣る為、パフォーマンス面では不利です。しかしアルゴリズムの単純さ、理解しやすさは圧倒的にトライ木に軍配が上がるでしょう。\n\nHTTP Routerは一見単純な仕様を持つアプリケーションに思えるかもしれませんが、意外と複雑です。これはテストケースを見て頂ければわかるかと思います。\n（もっと良い感じのテストケースの実装アイデアがあればぜひ教えてください。）\n\n単純なアルゴリズムを採用していることのメリットとしては、コードのメンテナビリティに貢献するという点です。（基数木の実装の難しさに対する言い訳とも聞こえるかもしれません・・実際のところ基数木をベースにしたHTTP Routerの実装の難しさには一度挫折しました・・）\n\n[_examples](https://github.com/bmf-san/goblin/blob/master/_examples)のソースコードを例に、goblinの内部的なデータ構造について説明します。\n\nルーティングの定義を表で表すと、次のようになります。\n\n| Method | Path | Handler | Middleware |\n| -- | -- | -- | -- |\n| GET | / | RootHandler | N/A |\n| GET | /foo | FooHandler | CORS |\n| POST | /foo | FooHandler | CORS |\n| GET | /foo/bar | FooBarHandler | N/A |\n| GET | /foo/bar/:name | FooBarNameHandler | N/A |\n| POST | /foo/:name | FooNameHandler | N/A|\n| GET | /baz | BazHandler | CORS |\n\ngobinではこのようなルーティングは次のような木構造として表現されます。\n\n```\n凡例：\u003cHTTP Method\u003e,[Node]\n\n\u003cGET\u003e\n    ├── [/]\n    |\n    ├── [/foo]\n    |        |\n    |        └── [/bar]\n    |                 |\n    |                 └── [/:name]\n    |\n    └── [/baz]\n\n\u003cPOST\u003e\n    └── [/foo]\n             |\n             └── [/:name]\n```\n\nHTTPメソッドごとに木を構築するようになっています。\n\n各ノードはハンドラーやミドルウェアの定義をデータとして持っています。\n\nここでは説明を簡素にするため、名前付きルーティングのデータや、グローバルミドルウェアのデータなどを省略しています。\n\n内部で構築される木には他にも色々なデータが保持されます。\n\n詳しく知りたい場合はデバッカーを使って内部構造を覗いてみてください。\n\n改善のアイデアがあればぜひ教えてください！\n\n# Wiki\n参考資料の一覧は[wiki](https://github.com/bmf-san/goblin/wiki)に記載しています。\n\n# コントリビューション\nIssueやPull Requestはいつでもお待ちしています。\n\n気軽にコントリビュートしてもらえると嬉しいです。\n\nコントリビュートする際は、以下の資料を事前にご確認ください。\n\n[CODE_OF_CONDUCT](https://github.com/bmf-san/goblin/blob/master/.github/CODE_OF_CONDUCT.md)\n[CONTRIBUTING](https://github.com/bmf-san/goblin/blob/master/.github/CONTRIBUTING.md)\n\n# スポンサー\nもし気に入って頂けたのならスポンサーしてもらえると嬉しいです！\n[GitHub Sponsors - bmf-san](https://github.com/sponsors/bmf-san)\n\nあるいはstarを貰えると嬉しいです！\n\n継続的にメンテナンスしていく上でのモチベーションになります :D\n\n# Stargazers\n[![Stargazers repo roster for @bmf-san/goblin](https://reporoster.com/stars/bmf-san/goblin)](https://github.com/bmf-san/goblin/stargazers)\n\n# Forkers\n[![Forkers repo roster for @bmf-san/goblin](https://reporoster.com/forks/bmf-san/goblin)](https://github.com/bmf-san/goblin/network/members)\n\n# ライセンス\nMITライセンスに基づいています。\n\n[LICENSE](https://github.com/bmf-san/goblin/blob/master/LICENSE)\n\n## 作者\n[bmf-san](https://github.com/bmf-san)\n\n- Email\n  - bmf.infomation@gmail.com\n- Blog\n  - [bmf-tech.com](http://bmf-tech.com)\n- Twitter\n  - [bmf-san](https://twitter.com/bmf-san)\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbmf-san%2Fgoblin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbmf-san%2Fgoblin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbmf-san%2Fgoblin/lists"}