Clack comes with separate middleware modules for session management and for storing session information in a database. I discovered the storage middleware shortly after implementing my own version.
This post describes what I learnt about these two options. Though the code to implement this feature is minimal, it took me much too long to figure out the details necessary to do so.
Writing your own storage layer has the following advantages:
- You can use storage not supported by cl-dbi, i.e. MySQL, PostgreSQL and SQLite.
- You determine the table layout.
My suggestion is to use the included version. It will save you time. I only created my own version because I did not know Clack already included the necessary module.
Using Clack’s storage middleware
This information is correct at least up to commit c4f3e09 (2015-10-27)
The class name is <clack-session-store-dbi>
located at v1-compat/src/contrib/session/store/
.
The code below creates a Clack application which handles the storage of session information completely automatically.
(clack:clackup
(builder
(<clack-middleware-session>
:store (make-instance 'clack.session.store.dbi:<clack-session-store-dbi>
:connect-args '(:postgresql
:database-name "DBNAME"
:username "ROLE"
:password "PASSWORD")))
#'app))
A hash table containing the session information is available inside request
handlers. It is obtained with (getf env :clack.session)
.
The hash table content is serialized before being stored to disk so it can be used to store any arbitrary data.
Session tracking is done using cookies which contain only a session ID. All other session information are kept server side.
Writing your own persistence middleware
The code below implements middleware for a storage layer. The actual database access is done by the model
package.
(in-package :cl-user)
(defpackage sltv.session.store
(:use :cl
:clack.session.store))
(in-package :sltv.session.store)
(cl-syntax:use-syntax :annot)
@export
(defclass <sltv-session-store> (<clack-session-store>)
())
@export
(defmethod fetch ((this <sltv-session-store>) sid)
(let* ((session (model:fetch-session-as-hashtable sid)))
(setf (gethash :id session) sid)
session))
@export
(defmethod store-session ((this <sltv-session-store>) sid session)
(model:store-session sid session))
@export
(defmethod remove-session ((this <sltv-session-store>) sid)
(model:remove-session sid))
Notes:
-
sid
is the session ID generated by Clack. - The session hash table must contain a key
ID
which is set to thesid
not the database record’s ID field. - The
session
must be defined with(make-hash-table :test 'equal)
. -
fetch
obtains an existing session from the database or creates a new entry if it doesn’t exist. -
store-session
stores the session hash table and sid in the database. -
remove-session
deletes the database record.
When an HTTP request is received the session middleware fetches the
session
for the received sid
. If the request did not include an
sid
(i.e. no cookie) or it included an expired sid
, Clack
generates a new one and fetches (which should create an new record) the
session.
The request handler has three option settings with which it can control some of the session middleware’s actions.
- :no-store
- Do not store the session object. It is still created in the database and available in the request handler but the database entry is deleted before the response is returned.
- :expire
- Expire the session so that a new one is generated next time the user visits.
- :change-id
- Create a new session and populate it with the current data.
An option can be changed with the form
(setf (gethash :change-id (getf env :clack.session.options)) t)