Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
519 lines (383 sloc) 12.5 KB

Clojure School

One I prepared earlier

Clojure Style guide

Definitely bedtime reading

Today’s lesson

  • Clojure’s Web Stack
  • Managing State in Clojure

Clojure’s web stack

  • Ring: provides web server capability
  • Compojure: provides a dsl for creating web handlers
  • Hiccup: provides html templating

Concepts: handlers, routes, requests, responses, middleware

A new Compojure application

lein new compojure <name>

Look in your new project’s ./project.clj to see the dependencies.

Note the ring key in the project map:

  :ring {:handler clojureschool.handler/app}

Boot up the web server

lein ring server-headless

You will probably see a web server start on http://localhost:3000

The template web application

Open ./src/<name>/handler.clj

(ns clojureschool.handler
  (:use compojure.core)
  (:require [compojure.handler :as handler]
            [compojure.route :as route]))

(defroutes app-routes
  (GET "/" [] "Hello World")
  (route/resources "/")
  (route/not-found "Not Found"))

(def app
  (handler/site app-routes))

Try changing “Hello World”, code should be reloaded on browser refresh.

A generic response - a plain old map

;; The routes are just a function that takes a request
;; and returns a response

(def app
  (handler/site (fn [request]
                  {:status 200
                   :headers {"Content-Type" "text/plain"}
                   :body "Hello World!"})))

What is a request?

(def app
  (handler/site (fn [request]
                  {:status 200
                   :headers {"Content-Type" "text/plain"}
                   :body (str request)})))
{:ssl-client-cert nil, :remote-addr "0:0:0:0:0:0:0:1%0", :scheme :http, :query-params {}, :session {}, :form-params {}, :multipart-params {}, :request-method :get, :query-string nil, :content-type nil, :cookies {}, :uri "/", :session/key nil, :server-name "localhost", :params {}, :headers {"accept-encoding" "gzip,deflate,sdch", "cache-control" "max-age=0", "connection" "keep-alive", "user-agent" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36", "accept-language" "en-US,en;q=0.8,fr-FR;q=0.6,fr;q=0.4", "accept" "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "host" "localhost:3001"}, :content-length nil, :server-port 3001, :character-encoding nil, :body #<HttpInput org.eclipse.jetty.server.HttpInput@13552393>, :flash nil}

Requests and responses are just Clojure maps

The string “Hello World” is converted into a response map automatically.


(ns clojureschool.handler
  (:use compojure.core)
  (:require [compojure.handler :as handler]
            [compojure.route :as route]))

(defroutes app-routes
  (GET "/" [] "Hello World")
  (route/resources "/")
  (route/not-found "Not Found"))

(def app
  (handler/site app-routes))

Compojure provides a nice DSL for mapping requests to responses. Don’t worry about how it works yet!


Update the handler to return a web page at:


That returns “pong”.


Syntax familiar to anyone with a rails background

;; Named params
(GET "/:a/:b" [a b] ...)

;; Query parameters
;; e.g. /something?a=1&b=2
(GET "/something" [a b] ...)

;; Catch-all
(GET "/blah/*" [] ...)

Web templating with Hiccup

:dependencies [[hiccup "1.0.4"] ...]

(ns webapp.core
  (:require [hiccup.core :refer :all]
            [ :refer [html5]]))

(def index-view
   [:head [:title "Hiccup Website"]]
   [:body [:h1 "Title goes here"]
    [:ul (for [x (range 10)]
           [:li (str "Item " x)])]]))

(GET "/" [] index-view)


Update the handler to return a web page at:


That returns a page with “List: [list id]” in the title and a list of integers from 1 to n.

Adding a visitor counter!

(def counter 0)

(defn index-view [{:keys [counter]}]
   [:head [:title "Web counter"]]
   [:body [:h1 "Web counter"]
    [:p (str "You're visitor number " (inc counter))]]))

How can we update the counter?

Thinking Functionally - Mutable State

A Clojure atom is a mutable pointer to an immutable data structure.

user=> (def counter (atom 0))

We read the current value of the atom by dereferencing it:

user=> (deref counter)

user=> @counter

Updating an atom

We update the value of an atom by passing it a pure function, which takes the previous value of the atom and returns the next value:

user=> (swap! counter inc)

user=> @counter

Modelling Time in Clojure


Kicking off another thread

user=> (def a-future (future 
                       (println "Starting massive calculation!")
                       (Thread/sleep 10000)
                       (println "Finished massive calculation!")
Starting massive calculation!

user=> a-future

user=> @a-future
Finished massive calculation!

See also: realized?, future-cancel, future-cancelled?, deref (multiple arities)

Why bother with atoms?

Atoms support sharing state between multiple threads, without many of the common concurrency pitfalls:

  • No (user) locking
  • No deadlocks
  • ‘ACI’ (no durability - it’s in memory!)

But how?!

Software Transactional Memory

The function you pass to ‘swap!’ is run inside a transaction.

If multiple updates are made simultaneously to the same atom, the Clojure STM system (transparently) aborts and retries the updates so that anyone reading the atoms sees consistent values.

I/O in transactions


The transaction might be aborted and retried - it’s very difficult to abort I/O and (most of the time) unwise to retry it!

(let [counter (atom 0)]
  (dotimes [_ 10]
      (swap! counter
             (fn [old-counter]
               (print old-counter)
               (inc old-counter))))))

=> 00000000001111111122222213334332554366576879

We have ways around this (to be covered later)


  • Start a thread (future) to sum all the numbers to one hundred million
  • Create a function which appends the numbers 1-10 to a vector in an atom (hint: dotimes)
  • Run the function across 10 threads simultaneously and observe the output
  • Extra credit: write a function that confirms you have ten of each number

Clojure’s other concurrency primitives:


Clojure’s other concurrency primitives:


Refs - co-ordinated updates

Refs are also pointers to values, but updates to multiple refs are co-ordinated.

Transactions must be explictly demarcated with a (dosync …) block:

(def account-a (ref 2341))
(def account-b (ref 4123))
(def transaction-log (ref []))

  (alter account-a + 100)
  (alter account-b - 100)
  (alter transaction-log conj {:from :b, :to :a, :amount 100}))

Agents - asynchronous updates

Agents are also pointers to values, but updates are asynchronous:

(def log-messages (agent []))

(send-off log-messages conj "Something happened!")

Actions sent to an individual agent are queued, not re-tried - only one action runs on any given agent at any time.

So they’re suitable for I/O!

See also: send

I/O in transactions - revisited:

(def log-agent (agent nil))
(def my-counter (atom 0))

(dotimes [_ 10]
  (swap! my-counter
         (fn [old-counter]
           (let [new-counter (inc old-counter)]
             (send-off log-agent (fn [_] (println "Counter is now:" new-counter)))
  • Sent/Sent-off actions are only queued when the transaction is successful


Add a visitor counter to the bottom of your web page that increments for each visit.


Middleware are functions which are chained together and adapt requests and/or responses

(defn wrap-log-request [handler]
  (fn [req]
    (println req)
    (handler req))

(def app
  (handler/site (wrap-log-request app-routes)))

Middleware can be chained

(defn wrap-log-request [handler]
  (fn [request]
    (println request)
    (handler request))

(defn wrap-log-response [handler]
  (fn [request]
    (let [response (handler request)]
      (println response)

(def app
  (handler/site (wrap-log-response (wrap-log-request app-routes))))

Threading macros

(def app
  (-> app-routes

Compare with:

(def app
  (handler/site (wrap-response-log (wrap-request-log app-routes))))

Useful Middleware

e.g for JSON response

:dependencies [[ring-middleware-format "0.3.1"] ...]

(ns clojureschool.handler
  (:require [ring.middleware.format :refer [wrap-restful-format]]))

(def app
  (-> app-routes
      (wrap-restful-format :formats [:json-kw :edn])

This instructs ring to convert Clojure data structures to their JSON equivalent, or as edn format if the Accept header requests it.

Also middleware for cookies, authentication, rate limiting, compression etc…

That’s it!

What we’ve covered:

  • Ring, Compojure, Hiccup
  • Handlers, Routes, Requests, Responses, Middleware
  • Managing state with Atoms, Refs, Agents

Further exercises

Add a CSS stylesheet to your hiccup template and change the font color (hint: include-css)

Write some middleware that returns a 401 status code unless ?token=123 is supplied in the URL

Adapt your counter update to print a message to the console every 10th visitor (hint: I/O and agents)

;; Re-write the below using -> threading macro
(/ (* (+ 10 2) 5) (* 2 5))

;; Re-write the below using ->> threading macro
(* 10 (apply + (map inc (range 10))))