Slide 1

Slide 1 text

GoでRouter⾃作 実装寄りな話 @bmf_san 2021.06.23 社内LT

Slide 2

Slide 2 text

なんの話 • net/httpを拡張したrouterのライブラリ • net/httpの”⾜りないところ”を補う • /user/:idのような動的なルーティングができな い

Slide 3

Slide 3 text

ϒϩάͱ͔-5ͱ͔Ͱ࢖͍·Θͨ͠ωλ

Slide 4

Slide 4 text

Github https://github.com/bmf-san/goblin

Slide 5

Slide 5 text

Goblin仕様 • net/http準拠 • 動的なルーティング • 名前付きのパラメータ(パスパラーメーター)をサポート/ users/:id • 正規表現も使える /users/:id[^\d+$] • ミドルウェアをサポート • net/httpのhttp.Handlerインターフェースに従った関数をミドル ウェアとしてルーティングの処理に組み込める

Slide 6

Slide 6 text

Goblin例 IUUQTHJUIVCDPNCNGTBOHPCMJOCMPCNBTUFS@FYBNQMFTNBJOHPΛࢀর

Slide 7

Slide 7 text

有名どころ • ライブラリ • https://github.com/fasthttp/router • https://github.com/julienschmidt/httprouter • http://github.com/go-chi/chi • FW • https://github.com/gin-gonic/gin • https://github.com/labstack/echo • https://github.com/gorilla/mux

Slide 8

Slide 8 text

Routerの役⽬ NJEEMFXBSF͸IBOEMFSͷखલͰॲཧ͞ΕΔΠϝʔδ

Slide 9

Slide 9 text

Routerの役⽬ • リクエストされたURLやHTTP Methodに応じて、 処理の実⾏を制御するアプリケーション • URLとアプリケーションの処理を結びつける

Slide 10

Slide 10 text

データ構造を考える ・Router≒⽂字列マッチング ・URLの階層構造と相性が良いデータ構造 →⽊構造

Slide 11

Slide 11 text

⽊構造

Slide 12

Slide 12 text

トライ⽊(プレフィックス⽊) ・⽂字列の集合を扱う⽊構造の⼀種 ・ざっくりいうと、⽂字列を探索しやすいように⽊に 格納したやつ ・ex. サジェスト、IPアドレス探索、形態素解析とか ⽂字列を扱う系のやつのベースだったりするぽい

Slide 13

Slide 13 text

もっと良い⽊ • 時間的計算量(処理時間)、空間的計算量(メモ リ)の効率を追求するなら他に選択肢がある • ex. Radix(Prefix) tree https://www.cs.usfca.edu/ ~galles/visualization/RadixTree.html • 実装できなかった。ムズい。 • strings.Replacerは内部でRadix tree 実装してい る

Slide 14

Slide 14 text

トライ⽊を知る • https://www.cs.usfca.edu/~galles/visualization/ Trie.html • アルゴリズムのビジュアライズ • https://github.com/bmf-san/road-to-algorithm- master/tree/master/data_structures/tree/trie • 参照実装

Slide 15

Slide 15 text

オレオレトライ⽊

Slide 16

Slide 16 text

オレオレトライ⽊(旧) • 最初のリリースで実装していた⽊ • HTTPメソッドをノードに⼊れてしまうのはアンチパターンだった • ルーティングの処理にHTTPメソッドの⼀致を前提としてしまう • HandlerがHTTPメソッドに依存するような実装になってしまう • middlewareの対応で不都合に • cf. https://bmf-tech.com/posts/⾃作ルーティングをアップデートした

Slide 17

Slide 17 text

主な構造体 NBQͱMJOLFEMJTUͷ߹ମͰ໦Λදݱͨ͠Α͏ͳΠϝʔδʁ

Slide 18

Slide 18 text

Goでrouterを実装する上で知っておきたいこと • net/httpの概観 • どんな構造体があるか、どこを拡張したら良いか、どんなイン ターフェースに従うべきか • cf. https://golang.org/pkg/net/http/ • HTTPサーバーの処理 • http.ListenAndServeから内部を⾒る • cf. https://bmf-tech.com/posts/GolangのHTTPサーバーのコード リーディング

Slide 19

Slide 19 text

HTTPサーバーのコードにdeep dive • 良く⾒るGoのHTTPサーバーのコード(⾊々省略さ れている)

Slide 20

Slide 20 text

HTTPサーバーのコードにdeep dive • 省略しないで書いたパターン

Slide 21

Slide 21 text

HTTPサーバーのコードにdeep dive • ServeHTTPは関数型のaliasであるHandlerFuncに置き換えることができる • cf. • func (f HandlerfFunc) ServeHTTP(w ResponseWriter, r *Request) • https://golang.org/src/net/http/server.go?s=64180:64240#L2058

Slide 22

Slide 22 text

HTTPサーバーのコードにdeep dive • Mux(http.NewServeMux( ))は作らなくても良い。DefaultServeMuxを使うことができ る。  • DefaultServeMuxはServeMux型の構造体を持っており、HandlerFuncというmuxにルー ティングを登録する関数を実装している。 • cf. • DefaultServeMux • https://golang.org/src/net/http/server.go?s=77627:77714#L2269 • func (mux *ServeMux) HandlerFunc(pattern string, handler func(ResponseWriter, *Request))

Slide 23

Slide 23 text

HTTPサーバーのコードにdeep dive • Server構造体(http.Server{})もわざわざ作らなくても良い。net/httpには ListenAndServe( )が⽤意されている。 • cf. • func (*Server) ListenAndServe • https://golang.org/pkg/net/http/#ListenAndServe • func ListenAndServe(addr string, handler Handler) error

Slide 24

Slide 24 text

Routerを実装するには • →http.Handlerインターフェース を意識して、muxを作れば良い

Slide 25

Slide 25 text

ルーティングを登録する部分の実装 • cf. https://github.com/bmf-san/goblin/blob/master/router.go • メソッドチェーンでrouteを登録する処理 • ServeHTTPの実装(≒http.Handlerインターフェースの実装)した構造体を作る • ServeHTTP内では、ルーティング、ミドルウェア・ハンドラの実⾏を順番にやる • 作った構造体はListenAndServeに渡される想定 • cf. https://github.com/bmf-san/goblin/blob/master/_examples/main.go

Slide 26

Slide 26 text

middleware対応のための実装 • cf. https://github.com/bmf-san/goblin/blob/master/ middleware.go • Sliceに保持したミドルウェアを順番に処理していく • 処理をラップしていくように実⾏する • cf. https://github.com/bmf-san/goblin/blob/ master/_examples/main.go • いわゆるDecorator patternってやつだと思う

Slide 27

Slide 27 text

muxにあたる部分の実装 • cf. https://github.com/bmf-san/goblin/blob/master/trie.go • オレオレトライ⽊を頑張ってかく • 単純なトライ⽊を書いて、それを発展させていく • データ構造を考えきった時点で6~7割くらい完成ではある • テストとデバッガを有効につかう • テストがないと前に進めないくらいテストを有⽤ • ⾊々なパターンのルート定義を考慮する必要があり、テストケースが結構悩ましい • デバッガは⽊の⽣成が上⼿くできているか確認したり、脳内デバッグで間に合わないときに有⽤ • 正規表現の利⽤ • 正規表現を使ったルーティングのケースに対応するためにregexpを使っている • strings.Replacerで代⽤できるならそっちが良い • Goでは正規表現はコストがかかる処理なのでキャッシュして再利⽤性を⾼めると良い • 後で対応しようと思っていたらPRもらえた • https://github.com/bmf-san/goblin/pull/17

Slide 28

Slide 28 text

ベンチマーク • ϕϯνϚʔΫςετ • cf. https://github.com/julienschmidt/go-http-routing-benchmark • go test -bench=“Goblin|Gin|GorillaMux|HttpRouter|Chi|Beego|Echo” • ϕϯνϚʔΫ݁Ռ • cf. https://github.com/bmf-san/goblin/issues/ 34#issuecomment-864978325 • Routerબఆͷ؍఺͸ύϑΥʔϚϯε͚ͩͰ͸ͳ͘ɺػೳ໘΍net/ httpͷΠϯλʔϑΣʔεΛҙ͍ࣝͯ͠Δ͔Ͳ͏͔ͷόϥϯε͕େࣄ ͦ͏ • cf. https://github.com/julienschmidt/go-http-routing- benchmark#conclusions

Slide 29

Slide 29 text

ベンチマーク • cf. https://github.com/julienschmidt/go-http-routing- benchmark • Goblin௥Ճ͠·ͨ͠ରઓΑΖ͓͘͠Ͷ͕͍͠·͢PR • https://github.com/julienschmidt/go-http-routing- benchmark/pull/97 • READMEߋ৽͍ͯ͠ͳ͍͔ΒϦετݟ௚ͨͧ͠PR • https://github.com/julienschmidt/go-http-routing- benchmark/pull/96