98
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Go3Advent Calendar 2017

Day 13

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

Last updated at Posted at 2017-12-12

この記事は 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 でも使えました。

98
56
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
98
56

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?