Tech behind Tech

Raw information. No finesse :)

Posts Tagged ‘ring

Introduction to Clojure Web Development using Ring, Compojure and Sandbar

with 14 comments


I gave an introduction to clojure web development presentation to bay area clojure user group. As my slides do not have much information, I am writing this blog so everyone can follow and get started with a sample clojure web application using compojure.

Setting Up:

 lein new address_book 

Add Compojure to Project:

Edit project.clj

(defproject address_book "1.0.0-SNAPSHOT"
  :description "Runnering log"
  :dependencies [[org.clojure/clojure "1.2.0"]
                 [org.clojure/clojure-contrib "1.2.0"]
                 [ring/ring-jetty-adapter "0.2.5"]
                 [compojure "0.4.1"]
                 [hiccup "0.2.6"]
                 [sandbar "0.3.0-SNAPSHOT"]
                 [clj-json "0.3.1"]]
  :dev-dependencies [[swank-clojure "1.3.0-SNAPSHOT"]])

Run

lein deps

Test whether our setup works:

Edit src/address_book/core.clj

(ns address-book.core
  (:use [compojure.core]
        [ring.adapter.jetty])
  (:require [compojure.route :as route]))

(defroutes rts
  (GET "/" [] "Address Book!!")
  (route/not-found "Page not found"))

(def application-routes
     rts)

(defn start []
  (run-jetty application-routes {:port 8080
                                 :join? false}))

Goto http://localhost:8080 and you should see “Address Book”

Interactive Development:

In core.clj, change start function to

(defn start []
  (run-jetty application-routes {:port 8080
                                 :join? false}))

Add Static Folders:

mkdir -p public/{css,js}

Create public/index.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">	
 <head>		
   <meta http-equiv="content-type" content="text/html; charset=utf-8" />		
   <title>My Address book</title>		
   <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>	
 </head>
 <body id="main">    
    <p>      My Address Book    </p>  
 <body>
</html>

Try to access http://localhost:8080/index.html. This will fail, as we never told compojure how to handle static files.

Edit core.clj and replace rts

(defroutes rts
  (GET "/" [] "Address Book!!")
   (route/files "/")
  (route/not-found "Page not found"))

Now when you access http://localhost:8080/index.html, it should display correctly.

Ring:

Three components

1) Handlers:
Handlers are main functions that process a request. We define handlers using defroutes macro.

2) Middleware:
Middleware are functions that could be chained together to process a request. Middleware functions can take any number of arguments, but the spec stats that first argument should be an handler and function should return an handler. An example for middleware is logging all requests that comes to your webserver. Ring and compojure comes with some standard middleware. We will see in next part how to create our own middleware.

3) Adapters:
Adapters are functions could adapt our handler to a web server. We are using jetty adapter to tie our handler to jetty server.

Lets create our Middleware:

Create src/address_book/middleware.clj

(ns address-book.middleware)

(defn- log [msg & vals]
  (let [line (apply format msg vals)]
    (locking System/out (println line))))

(defn wrap-request-logging [handler]
  (fn [{:keys [request-method uri] :as req}]
    (let [resp (handler req)]
      (log "Processing %s %s" request-method uri)
      resp)))

Edit core.clj ns form

(ns address-book.core
  (:use [compojure.core]
        [ring.adapter.jetty])
  (:require [compojure.route :as route]
			[address-book.middleware :as mdw]))

Edit core.clj application-routes def

(def application-routes
     (-> rts
	     mdw/wrap-request-logging))

Now when you access your server, you should see request log messages.

Main sourcecode:

Create address_book/address.clj

(ns address-book.address
  (:import [java.util Date])
  (:refer-clojure :exclude (find create)))

(def STORE (atom {:1 {:id :1 :name "Siva Jagadeesan" :street1 "88 7th" :street2 "#203" :city "Cupertino" :country "USA" :zipsourcecode 98802}}))

(defn to-keyword [num]
  (if-not (keyword? num)
    (keyword (str num))
    num))

(defn random-number []
  (to-keyword (.getTime (Date.))))

(defn create [attrs]
  (let [id (random-number)
        new-attrs (merge {:id id} attrs)]
    (swap! STORE merge {id new-attrs})
    new-attrs))

(defn find-all []
  (vals @STORE))

(defn find [id]
  ((to-keyword id) @STORE))

(defn update [id attrs]
  (let [updated-attrs (merge (find id) attrs)]
    (swap! STORE assoc id updated-attrs)
    updated-attrs))

(defn delete [id]
  (let [old-attrs (find id)]
    (swap! STORE dissoc id)
    old-attrs))

Edit core.clj ns form

(ns address-book.core
  (:use [compojure.core]
        [ring.adapter.jetty])
  (:require [compojure.route :as route]
			[address-book.middleware :as mdw]
            [address-book.address :as address]
            [clj-json.core :as json]))

Add this function to core.clj

(defn json-response [data & [status]]
  {:status (or status 200)
   :headers {"Content-Type" "application/json"}
   :body (json/generate-string data)})

Add these routes to core.clj

(GET "/addresses" [] (json-response (address/find-all)))
(GET "/addresses/:id" [id] (json-response (address/find id)))
(POST "/addresses" {params :params}  (json-response (address/create params)))

Replace your public folder with https://files.me.com/sivajag/sh35fq

This folder has needed css and js files.

Now go to http://localhost:8080/index.html and you should see a address book webapp. You can add and view addresses.

Authentication:

Create src/address_book/auth.clj

(ns address-book.auth
  (:use [sandbar.form-authentication ]
        [sandbar.validation]))

(defrecord AuthAdapter []
  FormAuthAdapter
  (load-user [this username password]
             (cond (= username "example")
                   {:username "example" :password "password" :roles #{:user}}))
  (validate-password [this]
                     (fn [m]
                       (if (= (:password m) "password")
                         m
                         (add-validation-error m "Unable to authenticate user.")))))

(defn form-authentication-adapter []
  (merge
   (AuthAdapter.)
   {:username "Username"
    :password "Password"
    :username-validation-error "You must supply a valid username."
    :password-validation-error "You must supply a password."
    :logout-page "/"}))

Edit core.clj ns form


(ns address-book.core
  (:use [compojure.core]
        [ring.adapter.jetty]
        [sandbar.auth]
        [sandbar.form-authentication ]
        [sandbar.validation ])
  (:require [compojure.route :as route]
			[address-book.middleware :as mdw]
            [address-book.address :as address]
            [address-book.auth :as auth]
            [clj-json.core :as json]))

Add this def to core.clj

(def security-policy
  [#".*\.(css|js|png|jpg|gif|ico)$" :any
   #"/login.*" :any
   #"/logout.*" :any
   #"/permission-denied.*" :any
   #"/addresses" :user
   #"/index.html" :user
   #"/" #{:user}])

Add this route to core.clj

 (form-authentication-routes (fn [_ c] (html c)) (auth/form-authentication-adapter))

Change application routes function in core.clj

(def application-routes
     (-> rts
        (with-security security-policy form-authentication)  		 
        wrap-stateful-session
	mdw/wrap-request-logging))

Now when you access http://localhost:8080/index.html it will take you to login page. You can login using “example” and “password”.

That is it folks. A simple web app using clojure.

I am sure this blog could be improved a lot. Please leave comments I will update this blog with your feedback.

Written by Siva Jagadeesan

January 19, 2011 at 12:36 am

Posted in Clojure

Tagged with , , , ,

Follow

Get every new post delivered to your Inbox.

Join 146 other followers

%d bloggers like this: