Sunday, November 15, 2009

BZLIB/SESSION.plt - a Session Store via BZLIB/DBI.plt

I originally planned to write a series on the development of a session store, but it turned out that there aren't that many things to write about, so I am just going to release the code via planet. As usual, this is released under LGPL.


To download the planet package:

(require (planet bzlib/session)) 

The package comes with three separate database installation scripts (one each for sqlite, mysql, and postgresql). You can call them via the following:

;; installing to a sqlite database
(require (planet bzlib/session/setup/jsqlite)) 
(setup-session-store/jsqlite! <path-to-sqlite-db>) 

;; installing to a mysql database 
(require (planet bzlib/session/setup/jazmysql)) 
(setup-session-store/jazmysql! <host> <port> <user>  
                               <password> <schema>)

;; installing to a postgresql database 
(require (planet bzlib/session/setup/spgsql)) 
(setup-session-store/spgsql! <host> <port> <user>
                             <password> <database>) 
Known Issue: the script currently can only be run once and it assumes the table session_t does not exist in the target database. This will be rectified in the future.

Session ID

The session store uses uuid for session IDs. bzlib/base provides API for manipulation of uuids.

To create an uuid, just run (make-uuid). It can optionally takes in a parameter that are either an uuid in string, and then create the corresponding uuid structure (it can also takes in another uuid structure and make an equivalent uuid structure).

> (require (planet bzlib/base))
> (make-uuid)
> (make-uuid "4ba52eac-a0b4-415a-88f5-57d1fadd1aba")
> (make-uuid (make-uuid "4ba52eac-a0b4-415a-88f5-57d1fadd1aba"))

bzlib/session.plt does not handle parsing cookies into uuids.

Creating a Session Object

In order to create a session object, you first need to make a dbi handle with either dbd-spgsql, dbd-jazmysql, or dbd-jsqlite driver to where you have setup the session_t table, and you need to pass in the corresponding query script so the prepared statements ('make-session!, 'load-session, 'save-session!, and destroy-session!) can be loaded:

(require (planet bzlib/session) (planet bzlib/dbi)) 
;; loading 'jsqlite 
(require (planet bzlib/dbd-jsqlite)) 
(define h (connect 'jsqlite <path-to-db> 
                   '#:load (session-query-path/sqlite))) 
;; loading 'spgsql 
(require (planet bzlib/dbd-spgsql)) 
(define h (connect 'spgsql <spgsql-parameters> ... 
                   '#:load (session-query-path/postgres)))
;; loading 'jazmysql
(require (planet bzlib/dbd-jazmysql)) 
(define h (connect 'jazmysql <jazmysql-parameters> ...
                   '#:load (session-query-path/mysql))) 
Then you can pass the handle to build-session to create the session object.

;; create a new session with a new uuid 
(define s (build-session h)) 
;; create a new session with a known uuid 
(define s (build-session h <uuid>)) 
As soon as build-session is called, you'll find a corresponding session record in session_t.

Accessing Session Key/Value Pairs

session-ref, session-set!, session-del! modifies the values of session key/value pairs:

(session-ref <session> <key> <optional-default-value>) 

(session-set! <session> <key> <value>) 

(session-del! <session> <key>) 

Writing Out Sessions

save-session! saves the sessions out to the database:

(save-session! <session>)
refresh-session! will reload the session from the database:

(refresh-session! <session>) 
And destroy-session! will delete the session record from session_t:

(destroy-session! <session>) 
call-with-session and with-session will help you manage the call to save-session! so you do not have to write it with every request:

(call-with-session (build-session h) 
  (lambda (session) 
    <... do something with session ...>))

(with-session (build-session h) 
  (lambda () 
    <... do something with session via (current-session) ...>)) 
with-session works via (current-session), which is a parameter that holds either #f or a session structure. By passing the session object via with-session it will automatically parameterize current-session.

Session Expirations

You can use session-expired? to test whether the session object has expired:

(session-expired? <session>) 
The expiration value is stored as a number (in Julian days) in session_t, which by default will be 14 days in the future from the time when save-session! is called.

The default session expiration value of 14 days can be controlled via the session-expiration-interval parameter.

Web Server Continuation

To simplify the usage of bzlib/session with web-server continuation calls, the following wrappers are exported to replace the send/suspend family:

(require (planet bzlib/session/web-server)) 
;; you then have access to... 
These wrappers have the same corresponding APIs from web-server itself, and they'll call save-session! before making the continuation, and call refresh-session! before returning from the continuation.

That's it for now, enjoy.


  1. Hi, I got following error:

    > (refresh-session! s)
    . . procedure build-session: no clause matching 4 arguments: # # 2455299.8333333917 #

  2. Hi - thanks for the bug report.

    I have uploaded a fix to planet.

    Please do (require (planet bzlib/session:1:1)) to get the fix and that should remove the refresh-session! error.