Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Idiomatic usage of Compojure using sessionless cookies.
Clojure
tree: 2610e07b4c

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
src
.gitignore
README.md
project.clj

README.md

Compojure Cookies Example 2011

cookie

Clojure, being a relatively new language, uses an even newer web framework: Compojure.

Compojure, still sporting a sub 1.0 version, being under active development and reduced to a thin veneer over Ring may prove challenging for developers. If for any reason because many examples and tutorials are just outdated.

I'm going to demonstrate the use of sessionless cookies in Compojure, with working examples.

The Bare Essentials

Example 1, a simple hello-world application is suitable for running on Heroku.

(ns example1
  (:use [ring.adapter.jetty :only [run-jetty]]
        [compojure.core     :only [defroutes GET]]))

(defroutes routes
  (GET  "/" [] "Hi there"))

(defn -main []
  (run-jetty routes {:port (if (nil? (System/getenv "PORT")) 
                             8000 ; localhost or heroku?
                             (Integer/parseInt (System/getenv "PORT")))}) )

After you check out the project from GitHub, it's easy to see in action:

$ lein run -m example1

Middleware: A Morality Tale in I Act

Example 2, the addition of a very simple form requires some changes.

(ns example2
  (:use [ring.adapter.jetty             :only [run-jetty]]
        [compojure.core                 :only [defroutes GET POST]]
        [ring.middleware.params         :only [wrap-params]]))

(defroutes routes
  (POST "/" [name] (str "Thanks " name))
  (GET  "/" [] "<form method='post' action='/'> What's your name? <input type='text' name='name' /><input type='submit' /></form>"))

(def app (wrap-params routes))

(defn -main []
  (run-jetty app {:port (if (nil? (System/getenv "PORT")) 
                          8000 ; localhost or heroku?
                          (Integer/parseInt (System/getenv "PORT")))}) )

The new POST route uses the name variable from the form. This is possible because we're now leveraging middleware:

(def app (wrap-params routes))

Simply put: middleware is features. Rather than forcing you into a one-size-fits all model, it's a way to mix and match whichever ones you need.

In this case, we have to process form variables. wrap-params is what does this for us by making the form variable name available as a local.

You can also look at from an efficiency point of view: ALL we're doing is accessing the form parameters. We aren't using sessions or a plethora of other features that we may or may not want.

C is for Clojure

Example3, cookie stuffing without sessions.

(ns example3
  (:use [ring.adapter.jetty             :only [run-jetty]]
        [compojure.core                 :only [defroutes GET POST]]
        [ring.middleware.cookies        :only [wrap-cookies]]
        [ring.middleware.params         :only [wrap-params]]
        [ring.middleware.keyword-params :only [wrap-keyword-params]]))

(defn main-page [cookies]
  (str "Hi there "
       (if (empty? (:value (cookies "name")))
         "<form method='post' action='/'> What's your name? <input type='text' name='name' /><input type='submit' /></form>"
         (:value (cookies "name")))))

(defn process-form [params cookies]
  (let [name (if (not (empty? (:name params)))
               (:name params)
               (:value (cookies "name")))]

    ;; set cookie, return html
    {:cookies {"name" name}
     :body (str "<html><head><meta HTTP-EQUIV='REFRESH' content='5; url='/'\"</head><body>Thanks!</body></html>")}))

(defroutes routes
  (POST "/" {params :params cookies :cookies} (process-form params cookies))
  (GET  "/" {cookies :cookies}                (main-page cookies)))

(def app (-> #'routes wrap-cookies wrap-keyword-params wrap-params))

(defn -main []
  (run-jetty app {:port (if (nil? (System/getenv "PORT")) 
                          8000 ; localhost or heroku?
                          (Integer/parseInt (System/getenv "PORT")))}) )

Keyword Params

Starting from the bottom, we're now wrapping cookies and keyword params using threading:

(def app (-> #'routes wrap-cookies wrap-keyword-params wrap-params))

Keyword params changes the input parameters from string-based to keyword based. It's string based because form variables technically can contain spaces, however I've yet to see it in real life.

{"name" "Hello world"} ; params without keyword-params
{:name  "Hello world"} ; params with keyword-params

Destructuring Syntax

The routes have a startling new syntax:

(POST "/" {params :params cookies :cookies} (process-form params cookies))

Because we want to manipulate cookies we're a operating at slightly lower level which allows us to pass in the cookies so they can be read.

Return Value

{:cookies {"name" name}
 :body (str "<html><head> ...

Because we want to modify the cookies, we need to return something more than a string. By default returning a string assumes the :body, you can set other various things like headers etc.

Something went wrong with that request. Please try again.