chi のミドルウェアを使う:ログ・認証・CORS の設定

Go のルーターライブラリ chi には、よく使うミドルウェアがあらかじめ用意されています。ログ出力、リクエストの復旧処理、CORS 設定など、自前で実装すると手間のかかる機能をインポートするだけで使えます。

chi のミドルウェアを登録する

chi では r.Use でミドルウェアを登録します。登録した順番にリクエストが通過していく仕組みです。

package main

import (
	"net/http"

	"github.com/go-chi/chi/v5"
	"github.com/go-chi/chi/v5/middleware"
)

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

	r.Use(middleware.Logger)
	r.Use(middleware.Recoverer)

	r.Get("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("Hello!"))
	})

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

middleware.Logger はリクエストのメソッド・パス・ステータスコード・処理時間をターミナルに出力してくれます。middleware.Recoverer はハンドラ内でパニックが発生した場合にサーバーのクラッシュを防ぎ、500 エラーを返す役割を担います。

この 2 つはほぼすべての chi プロジェクトで使われる定番の組み合わせです。特に Recoverer がないと、1 つのリクエストでパニックが起きただけでサーバー全体が落ちてしまうので注意しましょう。

Logger の出力を読む

middleware.Logger が出力するログは以下のような形式になります。

"GET http://localhost:8080/ HTTP/1.1" from 127.0.0.1:52344 - 200 6B in 42.125µs

メソッド、URL、リモートアドレス、ステータスコード、レスポンスサイズ、処理時間が一行にまとまっています。開発中のデバッグには十分な情報量でしょう。

ただし、本番環境では構造化ログ(JSON 形式)のほうが扱いやすいケースが多いです。その場合は middleware.Logger の代わりに、slogzerolog と組み合わせた独自のロギングミドルウェアを作ることになります。

認証ミドルウェアを作る

chi に認証用のミドルウェアは同梱されていません。認証ロジックはプロジェクトごとに異なるため、自分で書くのが基本です。

func authMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token := r.Header.Get("Authorization")
		if token == "" {
			http.Error(w, "認証トークンがありません", http.StatusUnauthorized)
			return
		}

		if !strings.HasPrefix(token, "Bearer ") {
			http.Error(w, "トークン形式が不正です", http.StatusUnauthorized)
			return
		}

		// トークンの検証処理(JWT デコードなど)
		next.ServeHTTP(w, r)
	})
}

chi のミドルウェアは標準ライブラリの func(http.Handler) http.Handler シグネチャそのままなので、前回の記事で紹介したミドルウェアパターンがそのまま使えます。

特定のルートグループだけに認証を適用したい場合は、r.Groupr.Route の中で Use を呼びます。

r.Route("/api", func(r chi.Router) {
	r.Use(authMiddleware)

	r.Get("/users", listUsers)
	r.Post("/users", createUser)
})

// /public は認証不要
r.Get("/public", publicHandler)

/api 配下のルートだけに認証がかかり、/public には認証なしでアクセスできます。このグループ単位でのミドルウェア適用は chi の大きな強みです。

CORS を設定する

フロントエンドとバックエンドを別オリジンで動かす場合、CORS(Cross-Origin Resource Sharing)の設定が必要になります。chi には公式の CORS ミドルウェアパッケージがあります。

import "github.com/go-chi/cors"

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

	r.Use(cors.Handler(cors.Options{
		AllowedOrigins:   []string{"https://example.com"},
		AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE"},
		AllowedHeaders:   []string{"Accept", "Authorization", "Content-Type"},
		ExposedHeaders:   []string{"Link"},
		AllowCredentials: true,
		MaxAge:           300,
	}))

	r.Get("/api/data", dataHandler)
	http.ListenAndServe(":8080", r)
}
AllowedOrigins許可するオリジン(ドメイン)
AllowedMethods許可する HTTP メソッド
AllowedHeaders許可するリクエストヘッダー
AllowCredentialsCookie の送信を許可するか
MaxAgeプリフライトリクエストのキャッシュ秒数

開発中は AllowedOrigins: []string{"*"} で全オリジンを許可すると楽ですが、本番では必ず具体的なオリジンを指定してください。ワイルドカードのまま本番に出すと、どのサイトからでも API を叩けてしまいます。

ミドルウェアの適用順序

ミドルウェアは登録順に実行されます。たとえば Logger → Recoverer → Auth の順で登録すると、まずログが記録され、次にパニック復旧が有効になり、最後に認証チェックが走ります。

Logger(リクエストをログに記録)

Recoverer(パニックをキャッチ)

Auth(認証チェック)

ハンドラ実行

Recoverer を Logger より前に置くと、パニック時のリクエストがログに残らなくなる可能性があります。ログ→復旧→認証という順番を基本にしておくとトラブルが少ないでしょう。