goでwebサーバを書く時、フレームワーク的なもののデファクトがいまいちない感じですが、chiを触ってみたらよさそうだったので紹介します。
これまでのgoでのWeb開発
去年くらいに調べたときの感じでは、
- 標準の
net/http
でいいでしょ + routerに gorilla/muxみたいな薄いライブラリを入れる - 比較的軽めのframeworkで、 echo, Gin, goji など
- Railsみたいなのが欲しい人はrevelとか beegoとか?
という感じでした。
個人的には、goで書くならあまり重いフレームワークは使いたくないけど net/http
はしんどそう、ということで今までは echo使ってました。結構よかったです。context
を引き回しておけば、そこから必要なものが取得できていい感じに書けました。
echoよかったけど・・・
echoよかったんですが、今から使おうと思うと自分の場合以下の点が気になりました。
- contextの取り扱いが echo ver.2 から ver.3で変わっていて、AppEngine+go1.8で使おうと思うといまいちだった
- echo作ってるチームがarmorというのを後から始めていて、echo今後もやっていくのか少し不安がある
という感じで、AppEngine+Go1.8で使うならあまりオススメできません。
chiよさそう
そこでechoの代わりに使えるものを探してみて、chiを知りました。
go-chi/chi: lightweight, idiomatic and composable router for building Go HTTP services
READMEにある説明によると
ということで、薄いpkgでルーティングをいい感じにしたい人にはぴったりです。
chiのレポジトリのREADMEにある以下のコードを見ると、できることがだいたい分かると思います。
import ( //... "context" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" ) func main() { r := chi.NewRouter() // A good base middleware stack r.Use(middleware.RequestID) r.Use(middleware.RealIP) r.Use(middleware.Logger) r.Use(middleware.Recoverer) // Set a timeout value on the request context (ctx), that will signal // through ctx.Done() that the request has timed out and further // processing should be stopped. r.Use(middleware.Timeout(60 * time.Second)) r.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hi")) }) // RESTy routes for "articles" resource r.Route("/articles", func(r chi.Router) { r.With(paginate).Get("/", listArticles) // GET /articles r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate) // GET /articles/01-16-2017 r.Post("/", createArticle) // POST /articles r.Get("/search", searchArticles) // GET /articles/search // Regexp url parameters: r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug) // GET /articles/home-is-toronto // Subrouters: r.Route("/{articleID}", func(r chi.Router) { r.Use(ArticleCtx) r.Get("/", getArticle) // GET /articles/123 r.Put("/", updateArticle) // PUT /articles/123 r.Delete("/", deleteArticle) // DELETE /articles/123 }) }) // Mount the admin sub-router r.Mount("/admin", adminRouter()) http.ListenAndServe(":3333", r) }
ルーティングにmiddleware入れる仕組みがあるのがいいですね。
例えば、ベーシック認証を行うmiddlewareは次のように書けます。
var userPasswords = map[string]string{ "user": "PassW0rd", } func basicAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { usr, pw, ok := r.BasicAuth() if !ok { w.Header().Set("WWW-Authenticate", "Basic") w.WriteHeader(http.StatusUnauthorized) http.Error(w, "auth required", http.StatusUnauthorized) return } if userPasswords[usr] != pw { http.Error(w, "incorrect auth info", http.StatusUnauthorized) return } next.ServeHTTP(w, r) }) }
で、ベーシック認証かけたいところで
r.With(basicAuth).Get("/internal", secretPage)
という感じで使うことが可能です。
gorilla/muxを使ったことはないけど、READMEを読む限り、書き方的にはchiが好きの方が好きです。
ということで、ドキュメントを読んでちょっと触ってみた限り、とてもいい感じがするのでオススメです。