準備
- info
- Goのコードは全て一つのmainファイルにまとめることもできたんですが、記事の中でいくつかに分けて埋め込みたかったので分割しました。
- 過剰に分割されているかもしれませんが、スルーでお願いします。
今回の環境は以下の docker-compose.ymlで用意します。
以降の操作はすべて gintest
コンテナ内で行う想定です。
プロンプトは慣例的に \$
に置き換えていますが、コンテナ内の操作ユーザは root
になるので適宜読み替えてください。
パッケージをインストールします。
$ go get -u github.com/gin-gonic/gin $ go get -u github.com/gin-contrib/sessions
今回はセッションも扱いたいので gin-gonic/contrib
も入れています。
ginでセッション管理するパッケージは以下の2種類があるようです。
GitHub - gin-gonic/contrib: Collection of middlewares created by the communityCollection of middlewares created by the community - GitHub - gin-gonic/contrib: Collection of middlewares created by the communityhttps://github.com/gin-gonic/contrib GitHub - gin-contrib/sessions: Gin middleware for session managementGin middleware for session management. Contribute to gin-contrib/sessions development by creating an account on GitHub.https://github.com/gin-contrib/sessions
前者はあんまりメンテされないみたいなので今回は後者の gin-contrib/sessions
を使います。
最終的に go.mod
はこんな感じになりました
以降、この(go.modが置かれている)ディレクトリは gintest
というパッケージとして扱います。
Usage
使い方はシンプルです。
gin.Default()
か gin.New()
で作った
*Engine
に対して、以下のメソッドを使って パス
と
ハンドラ関数(ビュー)
の紐付けを行って Run()
メソッドでサーバを起動するように記述するだけです。
本来これらは RouterGroup
のメソッドですが、Engine に埋め込まれているため、Engineからも呼び出すことができます。
少し(後述する)無駄な設定が混ざってますが、だいたいこんな感じです
あとは、 main.go
を実行すれば..
$ go run main.go [GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. [GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. - using env: export GIN_MODE=release - using code: gin.SetMode(gin.ReleaseMode) [GIN-debug] Loaded HTML Templates (7): - - debug.tmpl - header.tmpl - header - index.tmpl - login.tmpl - mypage.tmpl [GIN-debug] GET / --> gintest/users.Index (4 handlers) [GIN-debug] GET /debug --> gintest/debug.ParamDebug (4 handlers) [GIN-debug] POST /debug --> gintest/debug.ParamDebug (4 handlers) [GIN-debug] GET /login --> gintest/users.LoginForm (4 handlers) [GIN-debug] POST /login --> gintest/users.Login (4 handlers) [GIN-debug] GET /logout --> gintest/users.Logout (4 handlers) [GIN-debug] GET /mypage --> gintest/users.Mypage (5 handlers) [GIN-debug] Listening and serving HTTP on 0.0.0.0:8080
サーバが起動しました。あとは 起動したホストにアクセスするだけです。
ここからは各機能について掘り下げていきます。
Handler function
ハンドラ関数は Context のポインタを引数にとり、 HTML や JSON メソッドを使ってレスポンスを返却します。
以下はパラメータを表示する関数です。
メソッドの第一引数には HTTPステータスコード を指定します。
Context
はパラメータを解析するためのメソッドを持ちます。
- Query
- クエリストリングから指定された特定のパラメータ値を文字列で取得する
- 同じパラメータがあった場合は戦闘のパラメータが返却される
- パラメータがなかった場合、空文字が返却される
- DefaultQuery
- Queryと概ね同じ挙動
- パラメータがなかった場合、第2引数に指定した文字列が返却される
- パラメータに空文字が指定された場合、空文字が返却される
- QueryArray
- クエリストリングから指定されたパラメータ値をすべて文字列のスライスで取得する。
- パラメータがなかった場合、空のスライスが返却される
- PostForm
- リクエストボディから指定されたパラメータ値を文字列で取得する。
- DefaultPostForm
- PostForm と概ね同じ挙動
- パラメータがなかった場合、第2引数に指定した文字列が返却される
- パラメータに空文字が明示された場合、空文字が返却される
- PostFormArray
- リクエストボディから指定されたパラメータ値をすべて文字列のスライスで取得する。
- パラメータがなかった場合、空のスライスが返却される
これらのメソッドを使ってパラメータをJSONで表示すると以下のような感じになります。
Template
Context.HTML
で描画する
テンプレートは特定のディレクトリに配置し
r.LoadHTMLGlob("templates/*.tmpl")
のように登録します。
先程の画面は以下のテンプレートを描画しています。
テンプレートの可変部分は {{}}
(波括弧2つ)で囲みます。
数値はそのまま評価され、 .
で始まる場合はコンテキストとして評価されます。
- info
テンプレートのコンテキストは gin.H というマップを使って渡します。
c.HTML(http.StatusOK, "debug.tmpl", gin.H{ "json": string(j), })
それ以外は関数呼び出しになります。 関数はコンテキストとして渡すことはできず SetFuncMap でを登録する必要があります。(今回はやりません)
今回使っている関数は予め登録されている define
と
template
です。 {{ define テンプレート名 }}
で命名し、
{{ template テンプレート名 }}
で取り込みます。
お気付きの通り、関数やメソッドの実行は丸括弧で囲まず、スペースで引数を区切ります。
これらのテンプレート記法は Gin オリジナルというわけではなく、標準パッケージの template に従うので詳しく知りたい場合はそちらを参照してください。
Session
操作中のユーザを特定し、情報を持たせるためにセッション管理をします。
今回は以下のような User
構造体を定義します
- info
- この記事でやることをこれ以上増やしたくないので データストアとして RDBMS は使わず、以下のような JSON ファイルをDB代わりに使います。
gin-contrib/session
はセッション管理機能を提供してくれます。
以下のセッションストアを利用できます。
- cookie-based
- セッションに入れたデータすべてをCookieにいれます。
- 入れるデータが大きくなればなるほどCookieが肥大化して通信が圧迫されるので、何でもかんでもCookieに入れるのは考えものです。
- Redis
- Memcached
- MongoDB
ここでは Redis
をセッションストアとして利用します。
README に従って store
を作成し、以下のように与えてあげるだけで利用可能になります。
なお、ここで与えている接続先は記事の先頭に載せた
docker-compose
で作成しています。
store, _ := redis.NewStore(10, "tcp", "redis:6379", "", []byte("secret")) r.Use(sessions.Sessions("session", store)) // ここに指定した名前のCookieが作られる
- info
Key "github.com/gin-contrib/sessions" does not exist
と言われる場合、セッションミドルウェアを登録し忘れていないか確認してみましょう。// code from README r := gin.Default() store := sessions.NewCookieStore([]byte("secret")) r.Use(sessions.Sessions("mysession", store)) // your middleware r.Use(utils.AuthMiddleware())
セッションを利用する各関数で session := sessions.Default(c)
としてユーザに紐づく session
を取得。
sesion.Get(key string)
:
- キーに紐づく値を取得
sesion.Set(key string, value []byte)
:
- キーに値を値を保存
session.Clear()
:
- すべての値を削除
session.Delete(key string)
:
- キーに一致する値を削除
この session
に対して上記の各種操作を行った後に
session.Save()
でセッションストアに反映します。
セッションに紐づく「ユーザを取得」さらにそれを使い「未ログインユーザを弾く」ユーティリティ関数は以下のように書けます。
// マイページから未ログインユーザを弾く例 a.Use(users.AuthRequired) { a.GET("/mypage", users.Mypage) }
最後にセッションを使った基本的な画面を書いてみました。前で定義した
GetUser
関数も使っています。
ログイン後に Redis を覗いてみると登録できていますね。よかった。
# redis-cli 127.0.0.1:6379> keys * 1) "session_BP3G7FZGYKDPXM7XKUJYO3Q2BN2ZD75ZLWNS4RCNRTQCUCJLS54Q" 127.0.0.1:6379> get session_BP3G7FZGYKDPXM7XKUJYO3Q2BN2ZD75ZLWNS4RCNRTQCUCJLS54Q "\x0e\xff\x81\x04\x01\x02\xff\x82\x00\x01\x10\x01\x10\x00\x00g\xff\x82\x00\x01\x06string\x0c\x06\x00\x04user\a[]uint8\nJ\x00H{\"ID\":2,\"Password\":\"password2\",\"Name\":\"user name2\",\"Memo\":\"second user\"}"
今回のようにデータベースに入っているような恒久的なデータをセッションに入れるときは、更新時にセッションデータも更新することを忘れないようにしましょう。
ひとまずDBアクセスを除き最低限の機能は触れたと思います。
シンプルで好感が持てるフレームワークですね。 全部入りで何でもかんでもやってくれるフレームワークよりも学習コストは低いと思います。
またなにかあったら記事にします。
参考