Feeds:
Posts
Comments

Archive for October, 2007

There are a number of different methods for passing data between pages. These include:

1. Passing the data in a query string.
2. Storing the data in a hidden field and using Javascript to respond to onClick on the links.
3. Storing the data on the server and retrieving it using a session id.

The first option can be very wasteful if we have a large table with many columns. We would have to replicate the data for each column.

The second option seems reasonable but excludes people who disable Javascript so I favour using sessions.

I’m not aware of a PLT scheme library that handles sessions for CGI but it is easy enough to create a very basic example. For our purposes, we will store the sessions in the temporary directory and not have any type of session expiry.

PLT scheme has a nice library called serialize.ss which replaces a lot of the code I discussed earlier. We define a session as a serializable struct which contains the session id, the filename where the data lives and the data itself.

(define-serializable-struct session (id filename data))

The data in a session is simply key value pairs so we provide functions to store and retrieve the data and check if it exists. This implementation obviously assumes that data is a hash table.

(define (session-data-put! session key value)
  (hash-table-put! (session-data session) key value))

(define (session-data-get session key)
  (hash-table-get (session-data session) key (lambda () #f)))

(define (session-data-exists? session key)
  (with-handlers ((exn:fail:contract? (lambda (exn) #f)))
    (hash-table-get (session-data session) key)
    #t))

We also need some way of storing and retrieving sessions on the filesystem. Loading the session checks if one exists for a given session ID. If not, it uses make-temporary-file.

(define *temp-file-prefix* "cgisession-")
(define *temp-file-format-string*
  (string-append *temp-file-prefix* "~a"))

(define (filename->session-id filename)
  (cadr (regexp-match (string-append *temp-file-prefix*
                                     "([-0-9]+)$")
                      (path->string filename))))

(define (load-session session-id)
  (let ((tmp-filename (build-path (find-system-path 'temp-dir)
                                  (string-append *temp-file-prefix*
                                                 session-id))))
    (if (file-exists? tmp-filename)
        (call-with-input-file tmp-filename
          (lambda (port) (deserialize (read port))))
        (let ((filename (make-temporary-file *temp-file-format-string*)))
          (make-session (filename->session-id filename)
                        filename
                        (make-hash-table 'equal))))))

This is less robust than it could be, e.g. if the temporary file exists and does not contain something that we can deserialize then it will fall over. Fixing that is left as an exercise.

Saving the session is trivial.

(define (save-session session)
  (let ((port (open-output-file (session-filename session) 'truncate)))
    (print (serialize session) port)
    (close-output-port port)))

Finally, here is the full code listing including a few tests.

(require (lib "file.ss"))
(require (lib "serialize.ss"))

(define *temp-file-prefix* "cgisession-")
(define *temp-file-format-string*
  (string-append *temp-file-prefix* "~a"))

(define-serializable-struct session (id filename data))

(define (filename->session-id filename)
  (cadr (regexp-match (string-append *temp-file-prefix*
                                     "([-0-9]+)$")
                      (path->string filename))))

(define (load-session session-id)
  (let ((tmp-filename (build-path (find-system-path 'temp-dir)
                                  (string-append *temp-file-prefix*
                                                 session-id))))
    (if (file-exists? tmp-filename)
        (call-with-input-file tmp-filename
          (lambda (port) (deserialize (read port))))
        (let ((filename (make-temporary-file *temp-file-format-string*)))
          (make-session (filename->session-id filename)
                        filename
                        (make-hash-table 'equal))))))

(define (save-session session)
  (let ((port (open-output-file (session-filename session) 'truncate)))
    (print (serialize session) port)
    (close-output-port port)))

(define (session-data-put! session key value)
  (hash-table-put! (session-data session) key value))

(define (session-data-get session key)
  (hash-table-get (session-data session) key (lambda () #f)))

(define (session-data-exists? session key)
  (with-handlers ((exn:fail:contract? (lambda (exn) #f)))
    (hash-table-get (session-data session) key)
    #t))

;;; --- Test code --- ;;;

(define *session-id* "1234")
(define s (load-session *session-id*))
(set! *session-id* (session-id s))

(session-data-put! s 'k 'v)
(session-data-get s 'k)
(session-data-exists? s 'k1)

(save-session s)

Read Full Post »