Sessions、Users
sessionの簡単な例をお見せします。
sessionsテーブルとusersテーブルを用いてユーザー情報を管理するイメージです。
sessionsテーブルのunique idをキーに、usersテーブルの情報を引き出します。
main.goです。
type user struct { // 各ユーザー情報を格納する構造体
UserName, First, Last string
}
var tpl *template.Template
var dbUsers = make(map[string]user) // 模擬usersテーブル
var dbSessions = make(map[string]string) // 模擬sessionsテーブル
func init() {
tpl = template.Must(template.ParseGlob("templates/*"))
}
func main() {
http.HandleFunc("/", index)
http.HandleFunc("/bar", bar)
http.Handle("/favicon.ico", http.NotFoundHandler())
http.ListenAndServe(":8080", nil)
}
func index(w http.ResponseWriter, req *http.Request) {
c, err := req.Cookie("session")
if err != nil {
sID, err := uuid.NewV4() // 外部パッケージを用いて、unique idを生成
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
c = &http.Cookie{
Name: "session",
Value: sID.String(), // string化
}
http.SetCookie(w, c) // Cookieをセット
}
var u user
if un, ok := dbSessions[c.Value]; ok { // mapからキーで値が取れた場合、okはtrueになる
u = dbUsers[un]
}
if req.Method == http.MethodPost { // formから値を取得
un := req.FormValue("username")
f := req.FormValue("firstname")
l := req.FormValue("lastname")
u := user{
UserName: un,
First: f,
Last: l,
}
dbSessions[c.Value] = un
dbUsers[un] = u
}
tpl.ExecuteTemplate(w, "index.gohtml", u)
}
func bar(w http.ResponseWriter, req *http.Request) {
c, err := req.Cookie("session")
if err == http.ErrNoCookie {
http.Redirect(w, req, "/", http.StatusSeeOther)
return
}
un, ok := dbSessions[c.Value] // CookieのValueをキーに、userの名前を取得
if !ok {
http.Redirect(w, req, "/", http.StatusSeeOther)
return
}
u := dbUsers[un] // userの名前をキーに、userの情報を取得
tpl.ExecuteTemplate(w, "bar.gohtml", u)
}
templateファイルの紹介は省略しますが、 index.gohtmlにて、username、firstname、lastnameのinput要素を用意します。
Sign-up
先ほどの例を発展させて、Sign-up機能を作ります。
main.go
type user struct {
UserName, Password, First, Last string // Passwordを追加
}
var tpl *template.Template
var dbUsers = make(map[string]user)
var dbSessions = make(map[string]string)
func init() {
tpl = template.Must(template.ParseGlob("templates/*"))
}
func main() {
http.HandleFunc("/", index)
http.HandleFunc("/bar", bar)
http.HandleFunc("/signup", signup)
http.Handle("/favicon.ico", http.NotFoundHandler())
http.ListenAndServe(":8080", nil)
}
func index(w http.ResponseWriter, req *http.Request) {
u := getUser(w, req)
tpl.ExecuteTemplate(w, "index.gohtml", u)
}
func bar(w http.ResponseWriter, req *http.Request) {
u := getUser(w, req)
if !alreadyLoggedIn(req) {
http.Redirect(w, req, "/", http.StatusSeeOther)
return
}
tpl.ExecuteTemplate(w, "bar.gohtml", u)
}
func signup(w http.ResponseWriter, req *http.Request) {
if alreadyLoggedIn(req) {
http.Redirect(w, req, "/", http.StatusSeeOther)
return
}
if req.Method == http.MethodPost {
un := req.FormValue("username")
p := req.FormValue("password")
f := req.FormValue("firstname")
l := req.FormValue("lastname")
if _, ok := dbUsers[un]; ok {
http.Error(w, "Username already taken", http.StatusForbidden)
return
}
c := makeSession(w)
http.SetCookie(w, c)
dbSessions[c.Value] = un // sessionsテーブル(キーはunique id)にusernameを格納
u := user{
UserName: un,
Password: p,
First: f,
Last: l,
}
dbUsers[un] = u // usersテーブル(キーはusername)にuser情報を格納
http.Redirect(w, req, "/", http.StatusSeeOther)
return
}
tpl.ExecuteTemplate(w, "signup.gohtml", nil)
}
func getUser(w http.ResponseWriter, req *http.Request) user {
c, err := req.Cookie("session")
if err != nil {
c = makeSession(w)
}
var u user
if un, ok := dbSessions[c.Value]; ok { // mapからキーで値が取れた場合、okはtrueになる
u = dbUsers[un]
}
return u
}
func alreadyLoggedIn(req *http.Request) bool {
c, err := req.Cookie("session")
if err != nil {
return false
}
un := dbSessions[c.Value]
_, ok := dbUsers[un] // mapからキーで値が取れた場合、okはtrueになる
return ok
}
func makeSession(w http.ResponseWriter) *http.Cookie {
sID, err := uuid.NewV4() // 外部パッケージを用いて、unique idを生成
if err != nil {
log.Fatal(err)
}
return &http.Cookie{
Name: "session",
Value: sID.String(),
}
}
templateファイルの紹介は省略しますが、
signup.gohtmlにて、username、firstname、lastname、そしてpasswordのinput要素を用意します。
login
サードパーティのbcryptを使用したloginの方法を下記で紹介しています。
logout
追加箇所を中心に記載します。
logout関数を追加して、http.HandleFuncを追加します。
logoutリンクはtemplate内どこにでも配置することができます。
func main() {
...
http.HandleFunc("/logout", logout)
...
http.ListenAndServe(":8080", nil)
}
func logout(w http.ResponseWriter, req *http.Request) {
if !alreadyLoggedIn(req) {
http.Redirect(w, req, "/", http.StatusSeeOther) // ログイン状態でない場合は、以降の処理が不要
return
}
c, _ := req.Cookie("session") // エラーハンドリングはalreadyLoggedIn()で行なっているため、不要
delete(dbSessions, c.Value) // sessionsテーブルから削除
c.MaxAge = -1, // 0かマイナス値を指定することで、Cookieを削除できる
http.SetCookie(w, c) // 削除を実行
http.Redirect(w, req, "/login", http.StatusSeeOther)
}