Help us understand the problem. What is going on with this article?

goでWebサーバを書くためのシンプルなライブラリchiの紹介

More than 1 year has passed since last update.

この記事は Go3 Advent Calendar 2017 の13日目の記事です。

はじめに

goでwebサーバを書く際にはいろいろやり方がありますが、ざっくり分けて以下のような感じだと思います。

  1. net/http で十分。必要に応じてルーティングに gorilla/mux 使ったりする
  2. 軽めのwebフレームワークを利用する。 gin, echo, gojiなどを使う
  3. 全部入りのrailsみたいなやつが欲しい。Revel などを使う

パフォーマンスとか書きやすさとかそれぞれ違うので、各自好きなの使えばいいと思います。ちなみに自分は、一つ前のプロジェクトでは gojiを使っていて、今はechoを使っています。
個人的にはechoよかったんですが、 GoogleAppEngineで go1.8と echoのver.3以降で使おうと思うとcontextの扱いがいまいちきれいに書けない感じになりそうなので、別の選択肢を探してました。

chiの特徴

go-chi/chi: lightweight, idiomatic and composable router for building Go HTTP services

chiは、上記の分類でいうと1に近い選択肢になるかと思います。echoに比べると機能は少ないですが、余計なことをしないのでいいという感じがします。routerとmiddlewareの機能を提供する薄いライブラリです。以下で簡単に説明します。

シンプルで薄い、速い

READMEによると
- 軽量
- 速い(benchmark)
- net/http互換
- 外部ライブラリに依存しない
ということです。

外部ライブラリに依存していないのはいろいろなライブラリのバージョンなど気にしなくてよくなるのでいいなと思いました。
また、net/httpと互換性があるので、chiを使うのをやめたくなったとしても、ルーティングの部分だけ取り除けばhandler以下はそのまま動かせるのもいいですね。

routing

基本的なルーティングは以下のように書けます。

{
  r := chi.NewRouter()

  // "articles"以下のURLをルーティング
  r.Route("/articles", func(r chi.Router) {

    r.Post("/", createArticle)                                        // POST /articles
    r.Get("/search", searchArticles)                                  // GET /articles/search

    // 正規表現を使ったURLパラメータも可能:
    r.Get("/{articleSlug:[a-z-]+}", getArticleBySlug)                // GET /articles/home-is-toronto

    // サブルータ:
    r.Route("/{articleID}", func(r chi.Router) {
      r.Get("/", getArticle)                                          // GET /articles/123
      r.Put("/", updateArticle)                                       // PUT /articles/123
      r.Delete("/", deleteArticle)                                    // DELETE /articles/123
    })
  })

  http.ListenAndServe(":3000", r)
}


URLパラメータはもちろん受けられますし、サブルーターを使って、ルーティングをグループ化することもできます。

ルーティング先となるhandlerは、net/http互換ということで、次のように書けます。

func getArticle(w http.ResponseWriter, r *http.Request) {
  articleID := chi.URLParam(r, "articleID")

  article, err := dbGetArticle(articleID)
  if err != nil {
    http.Error(w, http.StatusText(404), 404)
    return
  }

  w.Write([]byte(fmt.Sprintf("title:%s", article.Title)))
}

また、次のようにMountを使ってルータを分けることもできます。

{
  r := chi.NewRouter()
  ...

  // サブルータでマウント
  r.Mount("/admin", adminRouter())
}


// メインのルータとは独立したルータ
func adminRouter() http.Handler {
  r := chi.NewRouter()
  r.Use(AdminOnly)                // ミドルウェアで認証(後述)
  r.Get("/", adminIndex)
  r.Get("/accounts", adminListAccounts)
  return r
}

基本的なルーティングは以上です。

Middleware

chiのミドルウェアはnet/httpのミドルウェアなので、シンプルに http.Handler を受けて何か処理をして、次の http.Handlerを返すだけです。
次の例のような感じです。

// contextに値をセットするmiddlewareの例
func MyMiddleware(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ctx := context.WithValue(r.Context(), "user", "123")
    next.ServeHTTP(w, r.WithContext(ctx))
  })
}

middlewareを使うことで、認証をしたり前処理をして値を contextに入れて後段の handler に渡したり・・・など、さまざまなことが可能です。公式で提供されているmiddlewareや他の人がコミュニティに共有しているmiddlewareもいくつかあります。

middlewareを使う場合には、ルーティングの部分で次のように指定してやります。

  r := chi.NewRouter()

  // 公式提供のmiddleware
  r.Use(middleware.RequestID)
  r.Use(middleware.RealIP)
  r.Use(middleware.Logger)
  r.Use(middleware.Recoverer)

  // 独自のmiddleware
  r.Use(MyMiddleWare)

  r.Route("/admin", func(r chi.Router) {
    // 管理画面だけ認証する
   r.Use(AdminOnly)
   r.Get("/", adminIndex)
   r.Get("/accounts", adminListAccounts)
  })
}

//管理画面認証するためのMiddleware
func AdminOnly(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    ctx := r.Context()
    perm, ok := ctx.Value("acl.permission").(YourPermissionType)
    if !ok || !perm.IsAdmin() {
      http.Error(w, http.StatusText(403), 403)
      return
    }
    next.ServeHTTP(w, r)
  })
}

middlewareを使うことで、共通の前処理などがきれいに書くことができます。

まとめ

以上が簡単ですが chi の紹介です。
goでサーバを書く際に、なるべくフレームワークは使いたくないけど、きれいに書けるところは書きたい、という人にはおすすめできると思いますので、試してみてください。GAE/GO1.8 でも使えました。

tjun
Network software engineer
http://tjun.org
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした