clojure, ring, and 1990's style counters

January 31, 2013

Tags: clojure ring web-apps

Clojure is a Lisp-like programming language that runs on the JVM. Ring is a web application library that provides a simple framework for serving HTTP content with clojure. It is similar to Rack for Ruby or WSGI for Python.

In this post we will be creating a web application that serves up a 90s style hit-counter GIF image. Skip ahead to the demo (hosted on Heroku) or check out the source.


Leiningen is a tool for managing Clojure projects. It is highly recommend to use this for new clojure projects.

If you are on Mac OS X run the following command to install the lein tool or click here if you are on another platform.

brew install --devel leiningen
The --devel flag is used to install version 2.x of Leiningen. (As of this writing 1.x is installed by default with Homebrew).

Counters from the 90s!

Remember those old sites with a hit-counter at the bottom of the page?

You are visitor number:

That is what we will be creating in clojure – a dynamic counter image serving web app. If you do not remember those hit-counters, maybe you can find some examples with the Wayback Machine!

Creating the project

The following command creates a new Clojure project and a source file at src/web_counter/core.clj.

lein new web-counter

The project.clj allows you to configure your Clojure project. It is similar in function (but not in syntax!) to a pom.xml (AHHH) or build.gradle.

(defproject web-counter "1.0.0-SNAPSHOT"
  :description "web counters are so cool"
  :dependencies [[org.clojure/clojure "1.3.0"]
                 [ring "1.1.8"]
                 [spy/spymemcached "2.8.1"]]
  :plugins [[lein-ring "0.8.2"]]
  :min-lein-version "2.0.0"
  :repositories [["couchbase" {:url "" :checksum :warn}]]
  :ring {:handler web-counter.core/app :init web-counter.core/init})

We have added a few dependencies, notably ring and spymemcached.

The lein-ring plugin is important as it gives the lein command extra tasks specific to ring.

The ring key configures our web app:

Request handler

The core of a ring application is the request handler. It takes a single argument, request, which contains all of the request attributes. In our handler we are only concerned with the uri, or the path of the request.

(defn app [request]
  (case (:uri request)
      (let [n (.incr memcached-client counter-key 1)]
        {:status 200
         :headers {"Content-Type" "image/gif"}
         :body (ByteArrayInputStream. (make-gif-counter n))})
      {:status 200
       :headers {"Content-Type" "text/html"}
       :body (str "<html><body><p>You are visitor number:</p><img src=\"/counter\"/></body></html>")}
    {:status 404}))

The return of a request handler is a map containing information about the response. Above are some common response attributes:

Our handler does the following:

Counter Storage

The counter is stored in memcached (rather than in-memory) so that the application can be deployed on multiple instances (any shared storage will work, though).

First we make a forward declaration for a var named memcached-client. When the web application starts we will set the root binding for this var so that it can be used in our request handler.

(declare memcached-client)

This function creates a memcached client using the spymemcached library. The memcached server information is retrieved from environment variables. (The environment variables were used to work with the Heroku memcache addon)

(defn make-memcached-client []
  (let [servers (map (fn [^String s]
        (let [sp (.split s ":")
              ^String host (aget sp 0)
              ^Integer port (if (> (alength sp) 1) (Integer/parseInt (aget sp 1)) 11211)]
          (InetSocketAddress. host port)))
        (.split (System/getenv "MEMCACHE_SERVERS") ";"))
        username (System/getenv "MEMCACHE_USERNAME")
        password (System/getenv "MEMCACHE_PASSWORD")
        callback-handler (PlainCallbackHandler. username password)
        auth-descriptor (AuthDescriptor. (into-array ["PLAIN"]) callback-handler)
        builder (-> (ConnectionFactoryBuilder.)
                  (.setAuthDescriptor auth-descriptor)
                  (.setProtocol (ConnectionFactoryBuilder$Protocol/BINARY)))
        conn-factory (.build builder)]
    (let [^MemcachedClient client (MemcachedClient. conn-factory servers)]
      (if (nil? (.get client counter-key)) (.set client counter-key 0 0))

Below is the initialization function. In our project.clj we set :init to web-counter.core/init so that Ring knows which function to call. The function below just sets the binding for the var memcached-client to a valid memcached client instance.

(defn init []
  (alter-var-root (var memcached-client) (fn [x] (make-memcached-client))))

Generating the GIF images

The counter image is generated by piecing together a series of digit images. The code below loads digit images from a folder in the resource path. By default lein expects resources to be in the resources directory. In our app we have a folder named odometer that contains 10 digit images named 0.gif, 1.gif…

(def counter-images
  (reduce (fn [cache n]
    (let [res ( (str "odometer/" n ".gif"))]
      (assoc cache (format "%d" n) (ImageIO/read res)))) {} (range 10)))

The var counter-images is bound to a map of digit -> counter image.

The function below generates the counter image by extracting the individual digits from the number and putting them into a single GIF.

(defn make-gif-counter [n]
  (let [numStr (format "%d" n)
        images (map #(get counter-images (String/valueOf %1)) (.toCharArray numStr))
        width (reduce (fn [w ^BufferedImage img] (+ w (.getWidth img))) 0 images)
        height (reduce (fn [h ^BufferedImage img] (max h (.getHeight img))) 0 images)
        output (ByteArrayOutputStream.)
        img (BufferedImage. width height BufferedImage/TYPE_BYTE_INDEXED)
        ^Graphics graphics (.createGraphics img)]

    (loop [xOffset 0 digit-images images]
      (when-let [^BufferedImage digit-image (first digit-images)]
        (.drawImage graphics digit-image xOffset 0 nil)
        (recur (+ xOffset (.getWidth digit-image)) (rest digit-images))))
    (.dispose graphics)
    (ImageIO/write img "GIF" output)
    (.toByteArray output)))

The end

Clojure is a great language and Ring makes writing web applications extremely easy. If you have not tried clojure yet, I highly recommend you do!

Check out the counter in action here and grab the full source code here.

comments powered by Disqus