Compojure(Ring)でSessionを使う


CompojureでSessionに何かデータ保存したいと思ったらまだCompojureでSessionを使ったことがなかったので簡単に使ってみました。

Compojure(Ring)でセッションを使う場合、handlerにwrap-sessionを指定してあげるとCookieセッションが使える。デフォルトだとデータの保存先はメモリ。

で、少しSession関連のモジュールを調べてみたところ、sandbarというのがwrap-sessionのラッパーで高機能かつ、インタフェースが使いやすかったので、今回はコレを使うことにしました。

広告

Session Wrapper

sandbarはwrap-sessionのラッパーというだけでなく、セッションを使ったサイトでよく使われるような、ログインユーザの名前や権限を取得する関数等も提供されています。

brentonashworth/sandbar – GitHub

実際に組み込み後のコードはこんな感じになりました。

(ns compojuretest.core
  (:use compojure.core
        compojure.response
        ring.adapter.jetty
        ring.middleware.reload-modified
        somnium.congomongo
        [ring.util.response :only (response content-type)]
        fleet
+        (sandbar stateful-session auth))
 
... 省略 ...
 
;; =============================================
;; Routes
;; =============================================
(defroutes app-routes 
  (GET "/" {} (tpl/index {})))
 
+(def app
+  (-> app-routes
+    wrap-stateful-session))
 
;; =============================================
;; Run Server
;; =============================================
;; AutoReload Configuration
(def app (handler/site app))
(def handlerApp
    (wrap-reload-modified #'app ["src"]))
 
(defn -main []
  (let [port (Integer/parseInt (get (System/getenv) "PORT" "8080"))]
    (run-jetty handlerApp {:port port})))

これで、Sessionが使えるようになります。Sessionを取り出したり設定したりするAPIは本家のwikiを見るか、行数が少ないのでソースを直接見たほうが早いかもしれません。該当箇所は以下です。

src/sandbar/stateful_session.clj at master from brentonashworth/sandbar – GitHub

保存先

上記に書いた通り、標準のSessionの保存先はメモリとなっているので、サーバの再起動等で消えてしまいます。少し不安でもあったので、ファイルかDBに保存するモジュールが無いか調べました。

その前にwrap-sessionで指定できるSessionの保存先というのはオプションで指定でき、下記の継承したものであれば良いようです。

ring-core/src/ring/middleware/session/store.clj at master from mmcgrana/ring – GitHub

SessionもMongoDBに保存したい!

せっかく今回、MongoDBを使っているので、セッションもMongoDBに格納したい!!

clojars.orgで検索してみたところ、下記のモジュールが見つかりました。

hozumi/mongodb-session – GitHub

サンプルを見たところ

(ns hello
  (:require [ring.middleware.session :as rs]
            [somnium.congomongo :as congo]
            [hozumi.mongodb-session :as mongoss]
        ...))
 
(congo/mongo! :db "mydb" :host "127.0.0.1")
 
(defroutes my-routes ....)
 
(def app (-> my-routes
           (rs/wrap-session {:store (mongoss/mongodb-store)})))

とあるので、既に宣言しているmongo!の周り無視して、wrap-session辺りを修正するだけで大丈夫なようです。mongodb-storeを組み込んだコードはこんな感じになりました。

(ns compojuretest.core
  (:use compojure.core
        compojure.response
        ring.adapter.jetty
        ring.middleware.reload-modified
        somnium.congomongo
        [ring.util.response :only (response content-type)]
        fleet
        (sandbar stateful-session auth))
  (:require [compojure.route :as route]
            [compojure.handler :as handl(mongoss/er]
            [clj-json.core :as json]
            [clojure.contrib.string :as string]
+            [hozumi.mongodb-session :as mongoss]))
 
... 省略 ...
 
;; =============================================
;; Routes
;; =============================================
(defroutes app-routes 
  (GET "/" {} (tpl/index {})))
 
(def app
  (-> app-routes
-    wrap-stateful-session
+    (wrap-stateful-session {:store (mongoss/mongodb-store)})))
 
;; =============================================
;; Run Server
;; =============================================
;; AutoReload Configuration
(def app (handler/site app))
(def handlerApp
    (wrap-reload-modified #'app ["src"]))
 
(defn -main []
  (let [port (Integer/parseInt (get (System/getenv) "PORT" "8080"))]
    (run-jetty handlerApp {:port port})))

この状態で実際にセッションに値を格納するとMongoDBの方にring_sessionsというコレクションが出来ていると思います。ちなみに、ring_sessionsのレコードはこんな形式です。

> db.ring_sessions.find().forEach(printjson);
{
	"_id" : "c83528b0-cfce-4e0f-b70b-90adce0c21d5",
	"_date" : ISODate("2011-10-31T17:34:55.022Z"),
	"_sandbar_session" : {
		"name" : "Tatsuya Fukata"
	}
}

Cookieのオプション

wrap-sessionで生成されるCookieはデフォルトではexpires未指定でブラウザを閉じた場合に消去されるものになっています。これらのオプションを指定する場合には、wrap-sessionの引数として下記のように指定します。また、これはwrap-stateful-sessionでも同じ形式の引数で渡すことが出来ます。

(def app
  (-> app-routes
    (wrap-stateful-session
      {:cookie-attrs {:expires "Thu, 1-Jan-2030 00:00:00 GMT", :path "/"},
      :cookie-name "compojuretest-session",
      :store (mongoss/mongodb-store)})))

上記以外の詳しい指定方法については、下記のソースを参照してみてください。

ring-core/src/ring/middleware/session.clj at master from mmcgrana/ring – GitHub

関連記事