前回、前々回に引き続き、今回もGoにおけるセッション管理機構の実装を学習していきましょう。
前々回はセッション管理機構の設計について、前回はセッションマネージャの実装について解説しました。
詳しくはこちらの記事をご参照ください。
【Go実践】セッション管理を行う(1)―セッション管理機構の設計
【Go実践】セッション管理を行う(2)―セッションマネージャの実装
前回の記事では、セッション管理機構を実現するための設計を考えました。
そこにおいて、セッションマネージャに実装すべき管理機能と、個々のセッションにに紐づけられるべき変数操作などの機能を分けて洗い出しました。
今回は、後者である個々のセッションに関する機能の実装例を提示し、内容についての解説を行っていきたいと思います。
session.goファイルの記述
それでは早速、sessionsディレクトリ内にsession.goファイルを作成し、以下の内容を記述してみてください。
package sessions // セッション管理パッケージ import ( "net/http" "github.com/gin-gonic/gin" "github.com/gorilla/context" ) /* ******************* * * 定数値 * ****** */ const ( DefaultSessionName = "default-session" // デフォルトセッション名 DefaultCookieName = "default-cookie" // デフォルトCookie名 ) /* ******************* * * セッション構造体 * ****** */ type Session struct { cookieName string ID string manager *Manager request *http.Request writer http.ResponseWriter Values map[string]interface{} } /* ******************* * * 新規セッション生成 * ****** */ func NewSession(manager *Manager, cookieName string) *Session { return &Session{ cookieName: cookieName, manager: manager, Values: map[string]interface{}{}, } } /* ******************* * * セッションの開始 * ****** */ func StartSession(sessionName, cookieName string, manager *Manager) gin.HandlerFunc { return func(ctx *gin.Context) { var session *Session var err error session, err = manager.Get(ctx.Request, cookieName) if err != nil { session, err = manager.New(ctx.Request, cookieName) if err != nil { println(err.Error()) ctx.Abort() } } session.writer = ctx.Writer ctx.Set(sessionName, session) defer context.Clear(ctx.Request) ctx.Next() } } /* ******************* * * デフォルトセッションの開始 * ****** */ func StartDefaultSession(manager *Manager) gin.HandlerFunc { return StartSession(DefaultSessionName, DefaultCookieName, manager) } /* ******************* * * セッションの取得 * ****** */ func GetSession(c *gin.Context, sessionName string) *Session { return c.MustGet(sessionName).(*Session) } /* ******************* * * デフォルトセッションの取得 * ****** */ func GetDefaultSession(c *gin.Context) *Session { return GetSession(c, DefaultSessionName) } /* ******************* * * セッションの保存 * ****** */ func (s *Session) Save() error { return s.manager.Save(s.request, s.writer, s) } /* ******************* * * セッション名の取得 * ****** */ func (s *Session) Name() string { return s.cookieName } /* ******************* * * セッション変数値の取得 * ****** */ func (s *Session) Get(key string) (interface{}, bool) { ret, exists := s.Values[key] return ret, exists } /* ******************* * * セッション変数値のセット * ****** */ func (s *Session) Set(key string, val interface{}) { s.Values[key] = val } /* ******************* * * セッション変数の削除 * ****** */ func (s *Session) Delete(key string) { delete(s.Values, key) } /* ******************* * * セッションの削除 * ****** */ func (s *Session) Terminate() { s.manager.Destroy(s.ID) }
この実装により、前回作成したmanager.goファイルのエラーも解消されたかと思います。
それでは、実装内容を順にみていきましょう。
セッション構造体の定義
セッションマネージャと同様に、ファイルの先頭でセッションの構造体を定義しています。
/* ******************* * * セッション構造体 * ****** */ type Session struct { cookieName string ID string manager *Manager request *http.Request writer http.ResponseWriter Values map[string]interface{} }
cookie名、セッションID、セッションマネージャなどが構造体のフィールドに定義されています。
更に、末尾のフィールドに、キーが文字列型/要素がインターフェース型のマップValueが定義されている点に着目してください。
これは、セッションに紐づけたい任意の情報を格納するためのマップであり、いわゆるセッション変数として使用するためのフィールドです。
/* ******************* * * 新規セッション生成 * ****** */ func NewSession(manager *Manager, cookieName string) *Session { return &Session{ cookieName: cookieName, manager: manager, Values: map[string]interface{}{}, } }
上記は、セッション構造体をインスタンス化する関数です。
ここでセットしていないセッションID、HTTPリクエスト、ResponseWriter等のフィールドはセッションマネージャ側で設定します。
セッションの開始
セッション開始時に使用する関数は以下になります。
/* ******************* * * セッションの開始 * ****** */ func StartSession(sessionName, cookieName string, manager *Manager) gin.HandlerFunc { return func(ctx *gin.Context) { var session *Session var err error session, err = manager.Get(ctx.Request, cookieName) if err != nil { session, err = manager.New(ctx.Request, cookieName) if err != nil { println(err.Error()) ctx.Abort() } } session.writer = ctx.Writer ctx.Set(sessionName, session) defer context.Clear(ctx.Request) ctx.Next() } }
パラメータとして任意のセッション名、Cookie名を指定できるようになっています。
48行目において、セッションマネージャ側で定義されているGet関数でリクエストのCokkie値を取得しています。
続く49行目の分岐で、Cookie値がセットされていない場合のみ新規セッションの生成を行っています。
これにより、既存セッションの有無に関わらず使用できる関数になっています。
また、以下は定数として定義しているデフォルトセッション名およびCokkie名を使用してセッションを開始する関数です。
StartSession関数をラップし、デフォルトの定数値をパラメタに渡しているだけのシンプルなものです。
/* ******************* * * デフォルトセッションの開始 * ****** */ func StartDefaultSession(manager *Manager) gin.HandlerFunc { return StartSession(DefaultSessionName, DefaultCookieName, manager) }
セッションの保存
リクエスト間でセッション情報を共有するには、サーバサイドとクライアントサイドに保存する必要があります。
保存処理の定義はセッションマネージャに記述されていますが、それをラップした関数をセッション構造体のオプションとして定義することで、セッションマネージャ側の実装を隠蔽して使用することができます。
/* ******************* * * セッションの保存 * ****** */ func (s *Session) Save() error { return s.manager.Save(s.request, s.writer, s) }
セッション変数の取り扱い
Session構造体の機能で最も頻繁に使用されるのがセッション変数を取り扱うためのメソッドでしょう。
セッション変数のための領域はインターフェース型のValuesマップとして構造体のフィールドに定義されています。
セッション変数に関わるメソッドは、いずれもこのValuesマップに対する要素の追加/更新、参照、削除を行うだけのシンプルな実装です。
/* ******************* * * セッション変数値の取得 * ****** */ func (s *Session) Get(key string) (interface{}, bool) { ret, exists := s.Values[key] return ret, exists } /* ******************* * * セッション変数値のセット * ****** */ func (s *Session) Set(key string, val interface{}) { s.Values[key] = val } /* ******************* * * セッション変数の削除 * ****** */ func (s *Session) Delete(key string) { delete(s.Values, key) }
セッションの削除
セッションの削除も、マネージャー側の定義をラップし隠蔽するシンプルな実装です。
ログアウト処理等の明示的なセッション削除要求、およびセッションタイムアウト等の際に呼び出して実行されます。
/* ******************* * * セッションの削除 * ****** */ func (s *Session) Terminate() { s.manager.Destroy(s.ID) }
終わりに
今回は、セッション構造体とそれに紐づくメソッドを実装しました。
セッションマネージャと合わせて、セッション管理機構パッケージとして実用可能になります。
次回は、セッション管理の実用例として、ログイン機能のサンプルをご紹介します。