Upgrade to Pro — share decks privately, control downloads, hide ads and more …

GoでRouter自作実装寄りな話

 GoでRouter自作実装寄りな話

0c9a69560f916778b569086d792680bb?s=128

bmf_san

June 23, 2021
Tweet

Transcript

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

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

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

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

  5. Goblin仕様 • net/http準拠 • 動的なルーティング • 名前付きのパラメータ(パスパラーメーター)をサポート/ users/:id • 正規表現も使える

    /users/:id[^\d+$] • ミドルウェアをサポート • net/httpのhttp.Handlerインターフェースに従った関数をミドル ウェアとしてルーティングの処理に組み込める
  6. Goblin例 IUUQTHJUIVCDPNCNGTBOHPCMJOCMPCNBTUFS@FYBNQMFTNBJOHPΛࢀর

  7. 有名どころ • ライブラリ • 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
  8. Routerの役⽬ NJEEMFXBSF͸IBOEMFSͷखલͰॲཧ͞ΕΔΠϝʔδ

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

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

  11. ⽊構造

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

  13. もっと良い⽊ • 時間的計算量(処理時間)、空間的計算量(メモ リ)の効率を追求するなら他に選択肢がある • ex. Radix(Prefix) tree https://www.cs.usfca.edu/ ~galles/visualization/RadixTree.html

    • 実装できなかった。ムズい。 • strings.Replacerは内部でRadix tree 実装してい る
  14. トライ⽊を知る • https://www.cs.usfca.edu/~galles/visualization/ Trie.html • アルゴリズムのビジュアライズ • https://github.com/bmf-san/road-to-algorithm- master/tree/master/data_structures/tree/trie •

    参照実装
  15. オレオレトライ⽊

  16. オレオレトライ⽊(旧) • 最初のリリースで実装していた⽊ • HTTPメソッドをノードに⼊れてしまうのはアンチパターンだった • ルーティングの処理にHTTPメソッドの⼀致を前提としてしまう • HandlerがHTTPメソッドに依存するような実装になってしまう •

    middlewareの対応で不都合に • cf. https://bmf-tech.com/posts/⾃作ルーティングをアップデートした
  17. 主な構造体 NBQͱMJOLFEMJTUͷ߹ମͰ໦Λදݱͨ͠Α͏ͳΠϝʔδʁ

  18. Goでrouterを実装する上で知っておきたいこと • net/httpの概観 • どんな構造体があるか、どこを拡張したら良いか、どんなイン ターフェースに従うべきか • cf. https://golang.org/pkg/net/http/ •

    HTTPサーバーの処理 • http.ListenAndServeから内部を⾒る • cf. https://bmf-tech.com/posts/GolangのHTTPサーバーのコード リーディング
  19. HTTPサーバーのコードにdeep dive • 良く⾒るGoのHTTPサーバーのコード(⾊々省略さ れている)

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

  21. 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
  22. 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))
  23. 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
  24. Routerを実装するには • →http.Handlerインターフェース を意識して、muxを作れば良い

  25. ルーティングを登録する部分の実装 • 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
  26. 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ってやつだと思う
  27. 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
  28. ベンチマーク • ϕϯνϚʔΫςετ • 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
  29. ベンチマーク • 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