Gorillaのcontextとmuxを触ってみる
Gorilla, the golang web toolkit
GorillaというGolangのWeb向けツールキットについて調べ始めました。
Webアプリ用に色々なものが用意されているのですが、フレームワークではなく、あくまで必要なものを自分で取捨選択してつかう「ツールキット」です。
今回はこのGorillaの中から、contextとmuxを触ってみました。以下、公式のサンプルを参考に自分が書いてみたソースのメモ書きです。
context
context - Gorilla, the golang web toolkit
contextはリクエストの有効期間中、値を保存しておく機能です。何のこっちゃという気もしますが、ソースを見た方が手っ取り早いかと思います。
package main import ( "fmt" "log" "net/http" "time" ) const key1 Key = "key1" const key2 Key = "key2" // Key reprisents context key. type Key string // GetContext returns a value for this package from the request values. func GetContext(r *http.Request, key Key) string { if rv := context.Get(r, key); rv != nil { return rv.(string) } return "" } // SetContext sets a value for this package in the request values. func SetContext(r *http.Request, key Key, val string) { context.Set(r, key, val) } func contextHandler(w http.ResponseWriter, r *http.Request) { urlValue := r.URL.Path[1:] SetContext(r, key1, urlValue) SetContext(r, key2, time.Now().Format("2006/01/02 15:04:05")) // do something... value1 := GetContext(r, key1) value2 := GetContext(r, key2) fmt.Fprintf(w, "get key1 = %s\n", value1) fmt.Fprintf(w, "get key2 = %s\n", value2) } func main() { http.HandleFunc("/context/", contextHandler) log.Fatal(http.ListenAndServe(":8080", nil)) }
contextHandler内で、「http://localhost:8080/context/sample」のようなURLから「context/sample」の部分を抜き出し、キーを「key1」としてcontextに保存しています。
また現在日時を「key2」に保存しています。
「// do something...」で何らかの処理を行ったと仮定し、その後でcontextより値を取得して出力しています。
上記をビルドして実行し、curlを叩いてみると以下のように出力されます。(現在日時の部分は異なってきますが・・・)
$ curl http://localhost:8080/context/sample get key1 = context/sample get key2 = 2020/03/11 21:54:08
contextに設定した値が、無事取得できているようです。
mux
mux - Gorilla, the golang web toolkit
muxはザックリと書くと、URLのルーティング周りの機能を提供しています。
上記の公式が分かりやすいのですが、
- URLパラメータを含むURLのルーティング
- URLパラメータは正規表現で指定できる
- サブルーティングでグループ化も可能
- 静的ページの格納先パスを指定
- middlewareによる共通処理の実装
などです。
これらのサンプルを書いてみました。
標準的な使い方
先に書いた
- URLパラメータを含むURLのルーティング
- URLパラメータは正規表現で指定できる
- サブルーティングでグループ化も可能
を実装しています。
package main import ( "fmt" "log" "net/http" "github.com/gorilla/mux" ) func productHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) key := vars["key"] fmt.Fprintf(w, "get key = %s\n", key) } func articlesCategoryHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) category := vars["category"] sort := vars["sort"] fmt.Fprintf(w, "get category = %s, sort = %s\n", category, sort) } func articleHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] fmt.Fprintf(w, "get id = %s\n", id) } func main() { r := mux.NewRouter() r.HandleFunc("/products/{key}", productHandler).Methods("GET") s := r.PathPrefix("/articles").Subrouter() s.HandleFunc("/{category}/{sort:(?:asc|desc|new)}", articlesCategoryHandler).Methods("GET") s.HandleFunc("/{category}/{id:[0-9]+}", articleHandler).Methods("GET") http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }
「/products/」はURLパラメータを、「/category/」はURLパラメータの正規表現での定義、およびサブルーディングでグループ化してURLパラメータが「asc」「desc」「new」の場合と数値の場合とで別々のハンドラを指定しています。
ビルドしてcurlで実行すると以下のようになります。
$ curl http://localhost:8080/products/xxx get key = xxx $ curl http://localhost:8080/articles/ccc/asc get category = ccc, sort = asc $ curl http://localhost:8080/articles/ccc/10 get id = 10
静的ページ
cssや画像ファイルなど静的ページを格納することがあるかと思います。その静的ページの格納先の例となります。
今回は「static」というフォルダ内の「hello.html」という静的なHTMLを用意し、それを呼び出すサンプルを書いてみました。
package main import ( "log" "net/http" "github.com/gorilla/mux" ) const staticDir = "/static/" func main() { r := mux.NewRouter() r.PathPrefix(staticDir). Handler(http.StripPrefix(staticDir, http.FileServer(http.Dir("."+staticDir)))) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }
実行してcurlでこんな感じで呼び出すと、hello.htmlの内容が返却されるはずです。
$ curl http://localhost:8080/static/hello.html
middleware
各ハンドラに共通の処理を実装したい場合、middlewareという機能を使い、その中に共通処理を実装する方法があります。basic認証を共通処理として実装するサンプルを作ってみました。
package main import ( "fmt" "log" "net/http" "time" "github.com/gorilla/mux" ) // User ... type User struct { Name string Password string } // BasicAuthMiddleware ... type BasicAuthMiddleware struct { Users []User } // NewBasicAuthMiddleware ... func NewBasicAuthMiddleware() BasicAuthMiddleware { user1 := User{"test", "pass"} user2 := User{"hello", "world"} users := []User{user1, user2} return BasicAuthMiddleware{users} } // Authenticate ... func (mwr *BasicAuthMiddleware) Authenticate(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { username, password, _ := r.BasicAuth() log.Println("Middleware Authenticated.") // Authenticate logic exist := false for _, u := range mwr.Users { if u.Name == username && u.Password == password { exist = true break } } if exist { next.ServeHTTP(w, r) } else { http.Error(w, "Forbidden", http.StatusForbidden) } }) } func helloHandler(w http.ResponseWriter, r *http.Request) { timeStr := time.Now().Format("2006/01/02 15:04:05") fmt.Fprintf(w, "Hello, now is %s.\n", timeStr) } func main() { r := mux.NewRouter() r.HandleFunc("/hello", helloHandler) mwr := NewBasicAuthMiddleware() r.Use(mwr.Authenticate) http.Handle("/", r) log.Fatal(http.ListenAndServe(":8080", nil)) }
パスワードを平文 + べた書きで保持していますが(笑)、サンプルなので適当にやっています。本来であればデータベースなどにハッシュ化して保持するべきでしょう。。。
「helloHandler」というハンドラに対して、BasicAuthMiddleware 構造体の「Authenticate」メソッドを共通処理として事前に実行するようにしています。
実行してcurlを叩くと、ユーザとパスワードが正しいかを判定していることが分かるかと思います。
$ username=test $ password=pass $ curl -i --basic -u $username:$password http://localhost:8080/hello
ざっと書いたサンプルを上げてみましたが、以上です。