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

この記事は [Go3 Advent Calendar 2017](https://qiita.com/advent-calendar/2017/go3) の13日目の記事です。

## はじめに

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

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

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


## chiの特徴

[go\-chi/chi: lightweight, idiomatic and composable router for building Go HTTP services](https://github.com/go-chi/chi)

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

### シンプルで薄い、速い

READMEによると
- 軽量
- 速い([benchmark](https://gist.github.com/pkieltyka/c089f309abeb179cfc4deaa519956d8c))
- net/http互換
- 外部ライブラリに依存しない
ということです。

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

### routing

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

```go
{
  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`互換ということで、次のように書けます。

```go
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を使ってルータを分けることもできます。

```go
{
  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`を返すだけです。
次の例のような感じです。

```go
// 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もいくつかあります。

- [coremiddlewares / go-chi](https://github.com/go-chi/chi#core-middlewares)
- [Auxiliary middlewares & packages | go-chi](https://github.com/go-chi/chi#auxiliary-middlewares--packages)

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

```go
  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 でも使えました。