はじめに
goのWebアプリケーションフレームワークを触りながらメモしてみた。網羅的に学びたい場合は公式のドキュメントをみた方が良いと思います。
準備
goをインストールする。windowsやmacだったら公式サイトからインストーラをダウンロードして実行、インストーラの指示に従っていけば特に迷うことなくセットアップできるはず。
エディタはVisual Studio Codeがオススメだけれど、好みに応じて選択すれば良いのではないかと思う。
続いて、開発用のフォルダを準備する。まず適当にフォルダを準備する。
mkdir GinStudy
そして初期化。
cd GinStudy
go mod init GinStudy
以降、全てこのフォルダ配下で作業する。
とりあえず空っぽのサーバ
公式のquick startに倣って、以下のようなソースをmain.goなどのファイル名で保存
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{"message": "pong"})
})
r.Run()
}
go getしたのちに実行
go get
go run ./main.go
なお、プロキシの配下などでgo getがうまく動かない時はプロキシの設定をする必要があるかもしれない。
もちろん、ビルドしてから実行しても良い
go build
./GinStudy
ブラウザでhttp://localhost:8080/にアクセスすると{"message":"pong"}という文字列が表示されることでしょう。
リクエストを受け付けるアドレスを変更する
以下の箇所でどのアドレスとバインドするかを指定することができる。何も指定しないとデフォルト値として"0.0.0.0:8080"が使われている。
r.Run()
ポートを指定する場合はこんな感じ。
r.Run(":5000")
さらにアドレスも指定する場合はこんな感じ。
r.Run("127.0.0.1:5000")
テンプレート、POST、リダイレクト
HTMLを返す際に、テンプレートを使うことでHTMLに変数を埋め込むことができる。
テンプレートファイルを./templates/*.htmlに置くこととして、./templates/index.htmlとして以下を保存。
<h1>ログインしてね</h1>
<a href="/login">ログイン</a>
ログイン画面も作ってみる
<form action="/login" method="POST">
<input name="account" required />
<input type="password" name="password" required />
<button type="submit">ログイン</button>
<form>
次にgoのソース。まずはテンプレートをロードする。ここではロードしたいテンプレートファイルが./templates/*.htmlだとして、これをロードする関数をハンドラの設定より前に実行、そしてハンドラの中でテンプレートを指定しながらレスポンスを返すように指示する。
package main
import "github.com/gin-gonic/gin"
func main() {
r := gin.Default()
r.LoadHTMLGlob("./templates/*.html") // ロードしてから、、
// トップページ
r.GET("/", func(c *gin.Context) {
c.HTML(200, // ステータスコード
"index.html", // 読み込んだテンプレートファイルを指定
gin.H{}) // テンプレートに渡す変数を指定(ここでは空っぽにしている)
})
// ログイン画面
r.GET("/login", func(c *gin.Context) {
c.HTML(200, "login.html", gin.H{})
})
// ログイン処理
r.POST("/login", func(c *gin.Context) {
account := c.PostForm("account") // これでformでPOSTされたデータを抽出できる
password := c.PostForm("password") // これでformでPOSTされたデータを抽出できる
// 本当はここで正しい情報が投入されているかをチェックする
r.Redirect(302, "/") // これで指定したURLにリダイレクトすることができる
})
r.Run("127.0.0.1:5000")
}
cookie、csrf対策、ミドルウェア、テンプレートにデータ
ログイン画面を書き換える。CSRF対策用のトークンを埋め込むように変更。
<form action="login?redirectTo={{ .redirectTo }}" method="POST">
<input name="account" />
<input name="password" type="password" />
<input type="hidden" name="_csrf" value="{{ .csrfToken }}" />
<button type="submit">ログイン</button>
</form>
CSRFトークンがないPOSTが来た時に表示するエラー画面を準備。わるいやつなので冷たくあしらう。
<h1>ざんねんっ!</h1>
index.htmlは、ログインしている時としていない時で表示が変わるようにしてみる。
{{ if .loginFlag }}
<h1>ログインしてますね</h1>
<a href="/logout">ログアウト</a>
{{ else }}
<h1>ログインしてね</h1>
<a href="/login">ログイン</a>
{{ end }}
goのソースも変更、GETの時にトークンをわたし、POSTの時にトークンを受け取る。
ついでに(今は意味がないが)GETの時にリダイレクト先を受けとるようにして、POSTでログインに成功したらそのリダイレクト先にジャンプする。将来的に、ログインが必要なページにログインしていない状態でアクセスした時に、ログイン用画面にリダイレクトして、ログインに成功したら元のページに戻すことができる。
package main
import (
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
csrf "github.com/utrack/gin-csrf"
"log"
"net/url"
)
func main() {
r := gin.Default()
// クッキー
store := cookie.NewStore([]byte("himitsu"))
r.Use(sessions.Sessions("session", store))
// csrf
r.Use(csrf.Middleware(csrf.Options{
Secret: "naisho",
ErrorFunc: func(c *gin.Context) {
c.HTML(400, "error.html", gin.H{})
},
}))
// テンプレートのロード
r.LoadHTMLGlob("./templates/*.html")
// トップページ
r.GET("/", func(c *gin.Context) {
loginFlag := false
sess := sessions.Default(c)
userIDStr := sess.Get("userID")
log.Println(userIDStr)
if userIDStr != nil {
userID := userIDStr.(int)
log.Println(userID)
loginFlag = true
}
c.HTML(200, "index.html", gin.H{"loginFlag": loginFlag})
})
// ログイン画面
r.GET("/login", func(c *gin.Context) {
redirectTo, err := url.QueryUnescape(c.Query("redirectTo"))
if err != nil {
redirectTo = "/"
}
if redirectTo == "" {
redirectTo = "/"
}
csrfToken := csrf.GetToken(c)
c.HTML(200, "login.html", gin.H{"csrfToken": csrfToken, "redirectTo": redirectTo})
})
// ログイン処理
r.POST("/login", func(c *gin.Context) {
redirectTo, _ := url.QueryUnescape(c.Query("redirectTo"))
account := c.PostForm("account") // これでformでPOSTされたデータを抽出できる
password := c.PostForm("password") // これでformでPOSTされたデータを抽出できる
// 本当はここで正しい情報が投入されているかをチェックする
log.Println(account, password)
// クッキーに保存する
sess := sessions.Default(c)
sess.Set("userID", 1)
sess.Save()
c.Redirect(302, redirectTo) // これで指定したURLにリダイレクトすることができる
})
r.GET("/logout", func(c *gin.Context){
sess := sessions.Default(c)
sess.Set("userID", nil)
// sess.Clear() // 全消しの場合はこっちで
sess.Save()
c.Redirect(302, "/")
})
r.Run("127.0.0.1:5000")
}
おわりに
1ステップずつ書いていくつもりだったけれど、面倒になって途中からまとめまくってしまった。
気が向いたら説明を丁寧にしたり上記以外の説明も書きたいと思っております。