Go言語によるWebサーバー作成入門 ーuuidー

公開:2020/10/31
更新:2020/11/13
5 min読了の目安(約5300字TECH技術記事

(作成2020.10.31/修正2020.11.13)

uuid

golangのuuidにも様々なパッケージが存在しますが、ここではgithub.com/google/uuid をとりあげます。そのほかで有名なのはgithub.com/satori/go.uuid があります。どちらも使い勝手は同じように感じますが、ここではビッグネームのgoogleのものを使っていきます。気が向いたら両方説明するかもしれません。
uuidはidを自動的につけてくれるツールです。idなので他のものとかぶらないようにつけてくれます。「かぶらないように」というのは絶対かぶらないということまでは保証していないということなのですが、実務上はかぶらないと思っててよいらしいです。

パッケージインストールが必要

go modulesを使う方は、

$ go mod init

とやってgo.modファイルを作ってください。goコードのコンパイル時にパッケージがインストールされます。go modulesを使うのが嫌な方はやらなくてもよいです。
そして、

$ go get github.com/google/uuid
または
$ go get github.com/satori/go.uuid

とやってパッケージをダウンロードしてください。

uuid作成例

main.go
package main

import (
	"fmt"
	"net/http"

	"github.com/google/uuid"
)

func main() {
	http.HandleFunc("/", index)
	http.Handle("/favicon.ico", http.NotFoundHandler())
	http.ListenAndServe(":8080", nil)
}

func index(w http.ResponseWriter, req *http.Request) {
	cookie, err := req.Cookie("session")
	if err != nil {
		id := uuid.New() // satori/go.uuidの場合はid, _ := uuid.NewV4()
		cookie = &http.Cookie{
			Name:  "session",
			Value: id.String(),
			// Secure: true,
			HttpOnly: true,
			Path:     "/",
		}
		http.SetCookie(w, cookie)
	}
	fmt.Println(cookie)
}

実行したら、検証-application-Cookiesを確認してください。Valueのところに怪しい文字が並んでいると思います。これが今回作られたuuidです。


コンソールの方にも出力しておきました。

余談ですが、id := uuid.New()は

id := uuid.Must(uuid.NewRandom())

と同じ意味だそうです。

(実践)ログイン機能を作る

個を特定するIDを作成できるということで、これを活用してログイン機能的なものを作りましょう。ログインにあたって、uuidで作ってた唯一情報(セッションID)と入力されたユーザーIDを照合して会員かどうかを判定します。照合する部分の仕組みをセッションということにします。そして、ログインするとサーバーの中でユーザーIDと名前等のuser情報が管理されている、という仕組みを作ります。

コードの流れを次のようにしてみました。

main.go
// 個人情報の定義
// userDB: user ID, user情報
// sessionDB: session ID, user ID

/* ルートにアクセスしたときの制御 */
// クッキーを読み込む。なかったら作る
// もしすでにuserIDがsessionDBにあったらそのuserIDとuser情報を取ってくる
// 入力フォームから情報を受け取ったらそのデータをsessionDBとuserDBに登録する

/* 会員ページでの制御 */
// クッキーを取ってくる。なければルートにリダイレクトする
// sessionDBからuserIDを取ってくる。なければルートにリダイレクトする
// userDBからuser情報を取ってくる

では、実際にコードを書いていきます。

main.go
package main

import (
	"html/template"
	"net/http"

	"github.com/google/uuid"
)

// 個人情報の定義
type user struct {
	UserName string
	First    string
	Last     string
}

var tpl *template.Template
var dbUsers = map[string]user{}      // user ID, user infomation
var dbSessions = map[string]string{} // session ID, user ID

func init() {
	tpl = template.Must(template.ParseGlob("templates/*"))
}

func main() {
	http.HandleFunc("/", index)
	http.HandleFunc("/vip", vip)
	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 := uuid.New()
		c = &http.Cookie{
			Name:  "session",
			Value: sID.String(),
		}
		http.SetCookie(w, c)
	}

	// もしすでにuserIDがsessionDBにあったらそのuserIDとuser情報を取ってくる
	var u user
	if un, ok := dbSessions[c.Value]; ok {
		u = dbUsers[un]
	}

	// 入力フォームから情報を受け取ったらそのデータをsessionDBとuserDBに登録する
	// 入力フォームから情報を受け取ったら = RequestがPOSTだったら
	if req.Method == http.MethodPost {
		un := req.FormValue("username")
		f := req.FormValue("firstname")
		l := req.FormValue("lastname")
		u = user{un, f, l}
		dbSessions[c.Value] = un
		dbUsers[un] = u
	}

	tpl.ExecuteTemplate(w, "index.gohtml", u)
}

func vip(w http.ResponseWriter, req *http.Request) {

	// クッキーを取ってくる。なければルートにリダイレクトする
	c, err := req.Cookie("session")
	if err != nil {
		http.Redirect(w, req, "/", http.StatusSeeOther)
		return
	}
	// sessionDBからuserIDを取ってくる。なければルートにリダイレクトする
	un, ok := dbSessions[c.Value]
	if !ok {
		http.Redirect(w, req, "/", http.StatusSeeOther)
		return
	}
	// userDBからuser情報を取ってくる
	u := dbUsers[un]
	tpl.ExecuteTemplate(w, "vip.gohtml", u)
}

テンプレートをこのようにしました。

index.gohtml
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

<form method="post">

    <input type="email" name="username" placeholder="email"><br>
    <input type="text" name="firstname" placeholder="first name"><br>
    <input type="text" name="lastname" placeholder="last name"><br>
    <input type="submit">

</form>


{{if .First}}
USER NAME {{.UserName}}<br>
FIRST {{.First}}<br>
LAST {{.Last}}<br>
{{end}}

<br>
<h2>Go to <a href="/vip">VIP room</a></h2>
</body>
</html>
vip.gohtml
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>VIP</title>
</head>
<body>

<h1>VIP room</h1>

{{if .First}}
    USER NAME {{.UserName}}<br>
    ようこそ {{.Last}} {{.First}} 様!
{{end}}

</body>
</html>

初期画面。最初にクッキーが作成されています。この時点ではVIP roomのリンクをクリックしても移動しません。


入力フォームに情報を入力。


送信すると、情報が書き込まれました。


VIP roomに入ることができました。


ルートに戻ると、クッキーが新たに作成され既存セッションが破棄されます。VIP roomへの再入場はできません。