Friday, August 14, 2009

Enabling Cookie Support

Allright - time to add more environment support, and this time it's the cookies.

web-server already provide cookie support, so it's just a matter of wrapping around them to get started.

Let's first define a parameter to hold cookies - this parameter will be populated at the start of the request, and be used to generate cookie headers during response time.

(define $cookies (make-parameter '()))

We will also use our own structure, since in web-server there are two types of cookie objects (client-cookie and cookie):

(define-struct cookie (name value domain path max-age comment secure?))


Extracting Cookies

web-server put handling of parsing request-cookies under web-server/http/cookie-parse, let's wrap around it so we have access to cookies:

;; convert ws:client-cookie (web-server cookie) to cookie
(define (ws:client-cookie->cookie c)
(make-cookie (ws:client-cookie-name c)
(ws:client-cookie-value c)
#f
#f
#f
#f
#f
))
;; extract the cookies from the request object
(define (init-cookies!)
(map (lambda (c)
(let ((c (ws:client-cookie->cookie c)))
(cons (cookie-name c) c)))
(ws:request-cookies ($request))))

;; parameterize cookie with extraction
(define (handle-request server request)
(parameterize (($pathinfo ($pathinfo))
($server server)
($request request))
(parameterize (($cookies (init-cookies!)))
...)))

So once we are inside the scripts we have access to the cookies from the request.

Manipulating Cookies


We will want to access cookie values, set cookies, and delete cookies, as follows:


(define (cookie-set! name value
#:domain (domain #f)
#:path (path #f)
#:max-age (max-age #f)
#:comment (comment #f)
#:secure? (secure? #f))
($cookies (cons (cons name (make-cookie name value domain path max-age comment secure?))
($cookies))))

(define (cookie-ref name)
(let ((kv (assoc name ($cookies))))
(if kv (cdr kv) #f)))

(define (cookie-del! name)
(let ((kv (assoc name ($cookies))))
($cookies (if kv
(filter (lambda (c)
(not (equal? kv c)))
($cookies))
($cookies)))))

Generating Cookies for Response


Finally - we will send the cookies in $cookies parameter through the response object back to the client:

(define (cookie->header c)
(ws:cookie->header (ws:make-cookie (cookie-name c)
(cookie-value c)
#:domain (cookie-domain c)
#:path (cookie-path c)
#:max-age (cookie-max-age c)
#:comment (cookie-comment c)
#:secure? (cookie-secure? c))))

(define ($cookies->headers)
(map (lambda (kv)
(cookie->header (cdr kv)))
($cookies)))


The IE Bug

If you like to set path to "/", IE has an issue with handling "/" in the cookie path; it prefers / instead. So we will have to do a manual conversion to solve this problem:

(define (cookie->header c)
(define (helper c)
(ws:make-header #"Set-Cookie"
(string->bytes/utf-8
(regexp-replace #px"Path=\\\"/\\\""
(ws:print-cookie c)
"Path=/"))))
(helper
(ws:make-cookie (cookie-name c)
(cookie-value c)
#:domain (cookie-domain c)
#:path (cookie-path c)
#:max-age (cookie-max-age c)
#:comment (cookie-comment c)
#:secure? (cookie-secure? c))))

Now we can enjoy cookies in all its glory of good, bad, and ugliness like other platforms :)

No comments:

Post a Comment