Go 言語で gorilla/mux, gorilla/context, gorilla/sessions を使ったユーザーログイン機能のサンプル
はじめに
Go 言語練習のため gorilla/mux, gorilla/context, gorilla/sessions を使ったユーザーログイン機能を実装してみました。
ユーザーの管理はメモリ上の map 型変数にユーザー名とパスワードを入れているだけなので、ここをDBに変えるのが次の課題のつもりです。
package main import ( "net/http" "html/template" "github.com/gorilla/mux" "github.com/gorilla/context" "github.com/gorilla/securecookie" "github.com/gorilla/sessions" "errors" ) var ( // セッションストアの初期化 store *sessions.CookieStore = sessions.NewCookieStore(securecookie.GenerateRandomKey(64)) // 登録ユーザーをメモリ上で管理 users map[string]string = make(map[string]string) ) const ( SessionName = "session-name" ContextSessionKey = "session" ) func main() { r := mux.NewRouter() handleFunc(r, "/", rootHandler) handleFunc(r, "/register", registerGetHandler).Methods("GET") handleFunc(r, "/register", registerPostHandler).Methods("POST") handleFunc(r, "/login", loginGetHandler).Methods("GET") handleFunc(r, "/login", loginPostHandler).Methods("POST") handleFunc(r, "/logout", logoutGetHandler).Methods("GET") handleFunc(r, "/logout", logoutPostHandler).Methods("POST") http.ListenAndServe(":8080", r) } // アプリケーション共通処理を常に呼び出すための糖衣構文 func handleFunc(r *mux.Router, path string, fn http.HandlerFunc) *mux.Route { return r.HandleFunc(path, applicationHandler(fn)) } // アプリケーション共通処理 func applicationHandler(fn http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // セッションの取得 session, err := store.Get(r, SessionName) if err != nil { // 不正なセッションだった場合は作り直す session, err = store.New(r, SessionName) checkError(err) } context.Set(r, ContextSessionKey, session) // 個別のハンドラー呼び出し fn(w, r) } } // トップページ func rootHandler(w http.ResponseWriter, r *http.Request) { type page struct { Username string } p := &page{} session, err := getSession(r) checkError(err) if v, ok := session.Values["username"]; ok { p.Username = v.(string) } executeTemplate(w, "template/index.html", p) } // 登録ページ func registerGetHandler(w http.ResponseWriter, r *http.Request) { executeTemplate(w, "template/register.html", nil) } // 登録処理 func registerPostHandler(w http.ResponseWriter, r *http.Request) { // フォームのパース r.ParseForm() // フォームの値の取得 username, password := r.Form["username"][0], r.Form["password"][0] // 空なら登録ページへ if username == "" || password == "" { http.Redirect(w, r, "/register", http.StatusFound) return } // ユーザー登録 users[username] = password // 現在のユーザーをセッションで管理 session, err := getSession(r) checkError(err) session.Values["username"] = username session.Save(r, w) // トップページへ http.Redirect(w, r, "/", http.StatusSeeOther) } // ログインページ func loginGetHandler(w http.ResponseWriter, r *http.Request) { executeTemplate(w, "template/login.html", nil) } // ログイン処理 func loginPostHandler(w http.ResponseWriter, r *http.Request) { // フォームのパース r.ParseForm() // フォームの値の取得 username, password := r.Form["username"][0], r.Form["password"][0] // 空ならログイン画面へ if username == "" && password == "" { http.Redirect(w, r, "/login", http.StatusFound) return } // ユーザーが登録済みでありパスワードが一致したらログイン状態に if v, ok := users[username]; ok && v == password { session, err := getSession(r) checkError(err) session.Values["username"] = username session.Save(r, w) http.Redirect(w, r, "/", http.StatusSeeOther) } else { http.Redirect(w, r, "/login", http.StatusSeeOther) } } // ログアウトページ func logoutGetHandler(w http.ResponseWriter, r *http.Request) { executeTemplate(w, "template/logout.html", nil) } // ログアウト処理 func logoutPostHandler(w http.ResponseWriter, r *http.Request) { session, err := getSession(r) checkError(err) delete(session.Values, "username") session.Save(r, w) http.Redirect(w, r, "/", http.StatusSeeOther) } // セッションの取得 func getSession(r *http.Request) (*sessions.Session, error) { if v := context.Get(r, ContextSessionKey); v != nil { return v.(*sessions.Session), nil } return nil, errors.New("failed to get session") } // テンプレートの実行 func executeTemplate(w http.ResponseWriter, name string, data interface{}) { t, err := template.ParseFiles(name) checkError(err) err = t.Execute(w, data) checkError(err) } // エラーチェック func checkError(err error) { if err != nil { panic(err) } }
おわりに
net/http のハンドラーをチェインさせて共通処理を切り出すパターンは「Go言語によるWebアプリケーション開発」読んだらちゃんと書いてあったのでちゃんと読もうと思いました。
学習目的で gorilla/mux で実装してみてて実際勉強になってますが、普通に web アプリ作るならたぶん goji とか WAF を使った方がいい気がしてきてます。