技術向上

プログラミングの学び、気になるテクノロジーやビジネストレンドを発信

Session【Go】

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の方法を下記で紹介しています。

bcryptでパスワードをhash化【Go】 - 技術向上


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)
}


Unique IDの生成【Go】 - 技術向上