🦍
gorilla/muxを使って特定のURLパスにだけミドルウェアを機能させる方法
Go言語のHTTPサーバーのルーティングライブラリgorilla/muxを使って特定のURLパスにミドルウェアを効かせる方法
困ったこと
http.Handle("/", router)
// http://www.gorillatoolkit.org/pkg/mux
router.HandleFunc("/anime/v1/master/cours", coursHandler).Methods("GET")
router.HandleFunc("/anime/v1/master/{year_num:[0-9]{4}}", yearTitleHandler).Methods("GET")
router.HandleFunc("/anime/v1/master/{year_num:[0-9]{4}}/{cours:[1-4]}", animeAPIReadHandler).Methods("GET")
// 管理者だけが実行できるようにしたい、キャッシュ全クリア 環境変数 認証キーあり
router.HandleFunc("/clear", cacheClear).Methods("POST")
// 管理者だけが実行できるようにしたい、キャッシュ全再取得 環境変数 認証キーあり
router.HandleFunc("/refresh", cacheRefresh).Methods("POST")
// middlewareAdminAuthAPIでAPIキーで管理者認証する
router.Use(middlewareAdminAuthAPI)
5つのルーティングのうち/clearと/refreshだけは管理者だけが実行できるようにしたいのでmiddlewareAdminAuthAPIというミドルウェアを作りルーティングに追加する。
ただこれだと上の3つの通常のルーティングも巻き込まれてしまう。
gorillaのgithubのTOPページではミドルウェアでログを出力するサンプル(goのhttpサーバーミドルウェアのスタンダードな例)しかなく、パスグループごとにミドルウェアを効かせる方法が書いていなかった。
middlewareAdminAuthAPIの中身はこんな感じ
func middlewareAdminAuthAPI(next http.Handler) http.Handler {
const APIKEY_HEADER_NAME = "X-API-KEY"
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
rApiKey := r.Header.Get(APIKEY_HEADER_NAME)
# apikeyはenvやconfで設定
if apikey != "" && rApiKey == apikey {
next.ServeHTTP(w, r)
} else {
//nolint:errcheck
w.Write([]byte("authentication error\n"))
}
})
}
解決方法 Subrouterを使う
http.Handle("/", router)
// http://www.gorillatoolkit.org/pkg/mux
router.HandleFunc("/anime/v1/master/cours", coursHandler).Methods("GET")
router.HandleFunc("/anime/v1/master/{year_num:[0-9]{4}}", yearTitleHandler).Methods("GET")
router.HandleFunc("/anime/v1/master/{year_num:[0-9]{4}}/{cours:[1-4]}", animeAPIReadHandler).Methods("GET")
// https://github.com/gorilla/mux/issues/445
admin := router.PathPrefix("/anime/v1/master/cache").Subrouter()
// キャッシュ全クリア 環境変数 認証キーあり
admin.HandleFunc("/clear", cacheClear).Methods("POST")
// キャッシュ全再取得 環境変数 認証キーあり
admin.HandleFunc("/refresh", cacheRefresh).Methods("POST")
admin.Use(middlewareAdminAuthAPI)
Subrouterを使うと新たにmux.Routerを親ルータの設定を元にコピーして生成してくれる。
そのサブルーターにのみミドルウェアを設定することで実現できる。
Subrouterの実装
func (r *Route) Subrouter() *Router {
// initialize a subrouter with a copy of the parent route's configuration
router := &Router{routeConf: copyRouteConf(r.routeConf), namedRoutes: r.namedRoutes}
r.addMatcher(router)
return router
}
一応まったく新しいルーターを生成しても実現できたがサブルーターを追加するほうがいいようだ。
結果的に管理者だけがキャッシュクリアできるAPIパスの実装が実現できた
curl -XPOST --header 'X-API-KEY:abcde' http://localhost:8080/anime/v1/master/cache/cler
[OK] Clear Cache
パス側の実装、ミドルウェア側で認証をするのでパス側の関数では認証コードは必要なくなる
// APメモリキャッシュやmemcached、redisなどのキャッシュを管理者のみがクリアする処理
func cacheClear(w http.ResponseWriter, r *http.Request) {
cacheBases = make(map[int][]byte)
cacheBasesWithOgp = make(map[int][]byte)
w.Write([]byte("[OK] Clear Cache\n"))
}
Discussion