goでwebサーバを書く時、フレームワーク的なもののデファクトがいまいちない感じですが、chiを触ってみたらよさそうだったので紹介します。
これまでのgoでのWeb開発
去年くらいに調べたときの感じでは、
という感じでした。
個人的には、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以外の外部パッケージに依存しない
net/http
に100%準拠
- APIをモジュール化できる仕組み(ミドルウェア、routeグループ、subrouter)
ということで、薄いpkgでルーティングをいい感じにしたい人にはぴったりです。
chiのレポジトリのREADMEにある以下のコードを見ると、できることがだいたい分かると思います。
import (
"context"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
)
func main() {
r := chi.NewRouter()
r.Use(middleware.RequestID)
r.Use(middleware.RealIP)
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.Timeout(60 * time.Second))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hi"))
})
r.Route("/articles", func(r chi.Router) {
r.With(paginate).Get("/", listArticles)
r.With(paginate).Get("/{month}-{day}-{year}", listArticlesByDate)
r.Post("/", createArticle)
r.Get("/search", searchArticles)
r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug)
r.Route("/{articleID}", func(r chi.Router) {
r.Use(ArticleCtx)
r.Get("/", getArticle)
r.Put("/", updateArticle)
r.Delete("/", deleteArticle)
})
})
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が好きの方が好きです。
ということで、ドキュメントを読んでちょっと触ってみた限り、とてもいい感じがするのでオススメです。