A Very brief overview of Clojure's web

Tom Willis

What's in a web framework

AFAIK: there's no "rails" or "django"-like framework written in clojure. And that's OK.

Here's Why

My Definition

To me, a web framework provides…

  • abstraction for http request and response
  • some way to dispatch to your code
  • templates

Bonus

  • an object relational mapper
  • database migrations
  • authentication
  • <your favorite thing in your favorite framework>

HTTP(ring)

Ring is a lib that specifies the http abstraction(it's just a map) and provides some useful middleware for working with that abstraction(more later)

Request

{:ssl-client-cert nil,
 :remote-addr "0:0:0:0:0:0:0:1",
 :scheme :http,
 :request-method :get,
 :query-string "qs=1",
 :content-type nil,
 :uri "/test/path/",
 :server-name "localhost",
 :headers
 {"accept-encoding" "gzip,deflate,sdch",
  "connection" "keep-alive",
  "user-agent"
  "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36
  (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36",
  "accept-language" "en-US,en;q=0.8",
  "accept"
  "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  "host" "localhost:3000",
  "cookie" ""},
 :content-length nil,
 :server-port 3000,
 :character-encoding nil,
 :body #<HttpInput org.eclipse.jetty.server.HttpInput@43efe432>}

Response

{:headers {"Content-Type" "text/plain"},
 :status 200,
 :body "hello world"}

Simplest web app

(defn hello-app [request]
  {:status 200 :body "hello world"})

And something to serve it…

(ns hello
  (:require [ring.adapter.jetty :as jetty]))

(defn hello-app [request]
  {:status 200 :body "hello world"})

(defn -main []
  ;; Run the server on port 3000
  (jetty/run-jetty handler {:port 3000}))

Routing

Compojure can provide routing to functions that is more in line with what we have come to expect from a web framework

(defroutes app-handler
  (GET "/api/artists/:artist-id/albums/:album-id/tracks/:track-id"
       [artist-id album-id track-id]
       (track-detail artist-id album-id track-id))
  (POST "/api/mounts" [new-mount]
	(add-new-mount new-mount))
  (DELETE "/api/mounts" [mount]
	  (delete-mount mount))
  (POST "/api/do-scan" []
	(do-scan))
  (GET "/index.html" [] (render-to-response (page)))
  (GET "/" []
       {:status 302
	:headers {"Location" "/index.html"}}))

(def app
  (-> app-handler
      (wrap-params)
      (j/wrap-json-response)
      (m/wrap-resource "public")
      (wrap-not-modified)
      (wrap-content-type)
      (wrap-gzip)
      (wrap-partial-content)))

Templating

hiccup

  • + simple
  • - dangerous (no string escaping)

enlive

  • + powerful
  • - complex

Middleware

session db connection cache/memoize