少し遅れてしまいましたが、Origami Advent Calendar 2016の21日目の記事になります。
OrigamiではGo言語をメインの言語として、APIなどのHTTPアプリケーションサーバを作製しています。
golangでHTTPアプリケーションを作成するときには
フレームワークを特に導入しなくとも、
net/http
とそれに準拠したライブラリを組み合わせれば十分だと考えています。
基本的にはnet/http
の機能の組み合わせで大抵の機能は十分表現できます。
ただし、ルーティングについては標準のhttp.ServeMux
だとちょっとつらいことが多いと思います。
標準のAPIに準拠しつつ、実アプリケーションでも十分な機能を備えたルーティングライブラリはいくつかあります。
Origamiのいくつかのアプリケーションサーバでは、そのうちのchiを利用しています。
chi
chiはnet/http
のhttp.Handler
インターフェースに準拠したルーティング・ライブラリです。
標準のhttp.ServeMux
と比較して以下のような機能があります。
- ワイルドカード付きパターンのサポート
- 名前付きパラメータのサポート
- ミドルウェアのサポート
ルーティング・ライブラリとしての特色は他に以下があります。
- Go 1.7 で追加された
context
パッケージに準拠 -
net/http
のインターフェースに準拠 - Go 1.7と
net/http
にのみ依存 - パトリシア木による高速マッチ(httprouterと同じ)
golangの標準インターフェースに準拠しており、かつ、ある程度高速で柔軟なマッチングのできるルータがほしいなら、chiはかなりおすすめできるライブラリです。
実際、chiの採用事例としては以下のような企業があります。
- CloudFlare
- Heroku
- Origami
chiの利用
chiの使い方は単純で、ルータを作成してnet/http
のサーバにマウントするだけです。
package main
import (
"net/http"
"github.com/pressly/chi"
)
func main() {
r := chi.NewRouter()
http.ListenAndServe(":80", r)
}
ルータへのハンドラの追加は以下のように行います。
// 標準のHandlerインターフェースを受け取ります.
var handler http.Handler = someHandler
r.Handle("/foo", handler)
// HandlerFunc型で受けることもできます.
var handlerF http.HandlerFunc = someHandler.ServeHTTP
r.HandleFunc("/bar", handlerF)
// 特定のメソッドのみ受け取るメソッドも用意されています.
// HTTPメソッドでのルーティングは、HandlerFuncを指定することに注意してください.
r.Get("/baz", handlerF)
ワイルドカードや名前付きパラメータを受け取ることもできます。
r.Get("/users/:id/*", func(w http.ResponseWriter, r *http.Request) {
userID := chi.URLParam(r, "id")
// ...
})
サブルータを作ることもできます。
subr := chi.NewRouter()
subr.Get("/bar", FooBarHandler)
r.Mount("/foo", subr)
// => /foo/bar へのリクエストは FooBarHandler に送られます.
// サブルータは以下のように作成することもできます.
r.Route("/foo", func(subr chi.Router){
subr.Get("/bar", FooBarHandler)
})
Middleware
chiはmiddleware
パターンによるミドルウェア(プラグイン)を利用することができます。
middleware
パターンとは以下のようにhttp.Handler
をラップする関数を指します。
// http.Handlerを受け取って、前後に何らかの処理を足し、
// ラップした新しいhttp.Handlerを返すような関数を
// middlewareと呼びます.
func middleware(next http.Handler) http.Handler {
// ※http.HandlerFunc型の関数はhttp.Handlerインターフェースを持ちます.
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 前処理
next(w, r)
// 後処理
})
}
middleware
関数を通すことで様々な共通処理をハンドラに追加することができます。
var newHandler1 = middleware(originalHandler1)
var newHandler2 = middleware(originalHandler2)
// ラップしたハンドラをマウント
// newHandler1, newHandler2はそれぞれmiddlewareに実装された処理を共通して持つ.
r.Get(pattern1, newHandler1)
r.Get(pattern2, newHandler2)
chiではこのようなmiddleware
関数の適用を自動で行ってくれます。
// middleware1は全体に適用されます
r.Use(middleware1)
r.Get("/foo", handler1)
// サブルータを作成してミドルウェアを追加することもできます.
// サブルータに適用されたミドルウェアはサブルータのみ有効です.
r.Route("/bar", func(subr chi.Router) {
subr.Use(middleware2)
subr.Get("/baz", handler2)
})
// 適用されるミドルウェア
// /foo => middleware1
// /bar/baz => middleware1, middleware2
chiでは上記のmiddleware
パターンを適用したミドルウェアがいくつか用意されいて、
chi/middleware
サブパッケージにまとめられています。
例えばmiddleware.RequestID
はRequestIDをそれぞれのリクエストにセットします(context
経由で取り出せます)。
middleware.Timeout
はタイムアウト機能を提供します。
他にも便利なパッケージがあるので自分で実装する前にこれらのパッケージを利用してもよいでしょう。
おわりに
chi
はシンプルですが非常に有用なルーティングライブラリです。
Goでは巨大なライブラリやフレームワークを使うより、
こういったシンプルなコンポーネントを組み合わせていく方が、
より「Goらしい」スタイルではないかと思っています。
その意味でnet/http
と組み合わせやすいchi
は非常に取り回しがよく、
採用して安心感が持てました。