23
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

Goのhttpルーター「Chi」の紹介

少し遅れてしまいましたが、Origami Advent Calendar 2016の21日目の記事になります。

OrigamiではGo言語をメインの言語として、APIなどのHTTPアプリケーションサーバを作製しています。

golangでHTTPアプリケーションを作成するときには
フレームワークを特に導入しなくとも、
net/httpとそれに準拠したライブラリを組み合わせれば十分だと考えています。

基本的にはnet/httpの機能の組み合わせで大抵の機能は十分表現できます。
ただし、ルーティングについては標準のhttp.ServeMuxだとちょっとつらいことが多いと思います。
標準のAPIに準拠しつつ、実アプリケーションでも十分な機能を備えたルーティングライブラリはいくつかあります。

Origamiのいくつかのアプリケーションサーバでは、そのうちのchiを利用しています。

chi

chinet/httphttp.Handlerインターフェースに準拠したルーティング・ライブラリです。

標準のhttp.ServeMuxと比較して以下のような機能があります。

  • ワイルドカード付きパターンのサポート
  • 名前付きパラメータのサポート
  • ミドルウェアのサポート

ルーティング・ライブラリとしての特色は他に以下があります。

  • Go 1.7 で追加されたcontextパッケージに準拠
  • net/httpのインターフェースに準拠
  • Go 1.7とnet/httpにのみ依存
  • パトリシア木による高速マッチ(httprouterと同じ)

golangの標準インターフェースに準拠しており、かつ、ある程度高速で柔軟なマッチングのできるルータがほしいなら、chiはかなりおすすめできるライブラリです。

実際、chiの採用事例としては以下のような企業があります。

  • CloudFlare
  • Heroku
  • Origami

chiの利用

chiの使い方は単純で、ルータを作成してnet/httpのサーバにマウントするだけです。

package main

import (
    "net/http"
    "github.com/pressly/chi"
)

func main() {
    r := chi.NewRouter()
    http.ListenAndServe(":80", r)
}

ルータへのハンドラの追加は以下のように行います。

// 標準のHandlerインターフェースを受け取ります.
var handler http.Handler = someHandler
r.Handle("/foo", handler)

// HandlerFunc型で受けることもできます.
var handlerF http.HandlerFunc = someHandler.ServeHTTP
r.HandleFunc("/bar", handlerF)

// 特定のメソッドのみ受け取るメソッドも用意されています.
// HTTPメソッドでのルーティングは、HandlerFuncを指定することに注意してください.
r.Get("/baz", handlerF)

ワイルドカードや名前付きパラメータを受け取ることもできます。

r.Get("/users/:id/*", func(w http.ResponseWriter, r *http.Request) {
  userID := chi.URLParam(r, "id")
  // ...
})

サブルータを作ることもできます。

subr := chi.NewRouter()
subr.Get("/bar", FooBarHandler)
r.Mount("/foo", subr)
// => /foo/bar へのリクエストは FooBarHandler に送られます.

// サブルータは以下のように作成することもできます.
r.Route("/foo", func(subr chi.Router){
  subr.Get("/bar", FooBarHandler)
})

Middleware

chiはmiddlewareパターンによるミドルウェア(プラグイン)を利用することができます。
middlewareパターンとは以下のようにhttp.Handlerをラップする関数を指します。

// http.Handlerを受け取って、前後に何らかの処理を足し、
// ラップした新しいhttp.Handlerを返すような関数を
// middlewareと呼びます.
func middleware(next http.Handler) http.Handler {
  // ※http.HandlerFunc型の関数はhttp.Handlerインターフェースを持ちます.
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    // 前処理
    next(w, r)
    // 後処理
  })
}

middleware関数を通すことで様々な共通処理をハンドラに追加することができます。

var newHandler1 = middleware(originalHandler1)
var newHandler2 = middleware(originalHandler2)
// ラップしたハンドラをマウント
// newHandler1, newHandler2はそれぞれmiddlewareに実装された処理を共通して持つ.
r.Get(pattern1, newHandler1)
r.Get(pattern2, newHandler2)

chiではこのようなmiddleware関数の適用を自動で行ってくれます。

// middleware1は全体に適用されます
r.Use(middleware1)
r.Get("/foo", handler1)

// サブルータを作成してミドルウェアを追加することもできます.
// サブルータに適用されたミドルウェアはサブルータのみ有効です.
r.Route("/bar", func(subr chi.Router) {
  subr.Use(middleware2)
  subr.Get("/baz", handler2)
})

// 適用されるミドルウェア
// /foo => middleware1
// /bar/baz => middleware1, middleware2

chiでは上記のmiddlewareパターンを適用したミドルウェアがいくつか用意されいて、
chi/middlewareサブパッケージにまとめられています。

例えばmiddleware.RequestIDはRequestIDをそれぞれのリクエストにセットします(context経由で取り出せます)。
middleware.Timeoutはタイムアウト機能を提供します。
他にも便利なパッケージがあるので自分で実装する前にこれらのパッケージを利用してもよいでしょう。

おわりに

chiはシンプルですが非常に有用なルーティングライブラリです。

Goでは巨大なライブラリやフレームワークを使うより、
こういったシンプルなコンポーネントを組み合わせていく方が、
より「Goらしい」スタイルではないかと思っています。

その意味でnet/httpと組み合わせやすいchiは非常に取り回しがよく、
採用して安心感が持てました。

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
23
Help us understand the problem. What are the problem?