Search This Blog

Wednesday, January 16, 2013

Ring: Using Sessions in a Web Application

My first attempt to actually use sessions has left me feeling very uncomfortable.

Does anyone know what I'm supposed to do instead?




;;  necessary dependencies
;; [[org.clojure/clojure "1.4.0"]
;;  [ring/ring "1.1.6"]]
;; -------------

;; Here's an app, built in a way which should surprise no-one who's read the previous posts:

(require 'ring.adapter.jetty
         'ring.middleware.stacktrace
         'ring.middleware.session
         'clojure.pprint)

(defn html-escape [string]
  (str "<pre>" (clojure.string/escape string {\< "&lt;", \> "&gt;"}) "</pre>"))

(defn format-request [name request kill-keys kill-headers]
  (let [r1 (reduce dissoc request kill-keys)
        r (reduce (fn [h n] (update-in h [:headers] dissoc n)) r1 kill-headers)]
  (with-out-str
    (println "-------------------------------")
    (println name)
    (println "-------------------------------")
    (clojure.pprint/pprint r)
    (println "-------------------------------"))))


(def kill-keys [:body :request-method :character-encoding :remote-addr :server-name :server-port :ssl-client-cert :scheme  :content-type  :content-length])
(def kill-headers ["user-agent" "accept" "accept-encoding" "accept-language" "accept-charset" "connection" "host"])

(defn wrap-spy [handler spyname]
  (fn [request]
    (let [incoming (format-request (str spyname ":\n Incoming Request:") request kill-keys kill-headers)]
      (println incoming)
      (let [response (handler request)]
        (let [outgoing (format-request (str spyname ":\n Outgoing Response Map:") response kill-keys kill-headers)]
          (println outgoing)
          (update-in response  [:body] (fn[x] (str (html-escape incoming) x  (html-escape outgoing)))))))))



(declare handler)

(def app
  (-> #'handler
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy "what the handler sees" )
      (ring.middleware.session/wrap-session )
      (wrap-spy "what the web server sees" )
      (ring.middleware.stacktrace/wrap-stacktrace)
      ))

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (let [s (request :session)]
           (if (empty? s) 
             (str "<h1>Hello World!</h1>" )
             (str "<h1>Your Session</h1><p>" s "</p>" )))
   :session "I am a session. Fear me."})

(defonce server (ring.adapter.jetty/run-jetty #'app {:port 8080 :join? false}))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Everything so far should be comprehensible.

;; Let's see if we can use the tools we have so far to build a little personality test


(defn status-response [code body]
  {:status code
   :headers {"Content-Type" "text/html"}
   :body body})

(def response (partial status-response 200))

(defn handler [request]
  (case (request :uri)
    "/" (response "<h1>The Moral Maze</h1>What do you choose: <a href=\"/good\">good</a> or <a href=\"/evil\">evil</a>?")
    "/good" (response "<h1>good</h1> <a href=\"/\">choose again</a>" )
    "/evil" (response "<h1>evil</h1> <a href=\"/\">choose again</a>")
    (status-response 404 (str "<h1>404 Not Found: " (:uri request) "</h1>" ))))



;; So far so good. 

;; Ring has an implementation of 'flash messages', which allows one page to send a message to another.

;; We need to plumb it in:

(require 'ring.middleware.flash)

(def app
  (-> #'handler
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy "what the handler sees" )
      (ring.middleware.flash/wrap-flash)
      (wrap-spy "what the flash middleware sees" )      
      (ring.middleware.session/wrap-session )
      (wrap-spy "what the web server sees" )
      (ring.middleware.stacktrace/wrap-stacktrace)))


(defn handler [request]
  (case (request :uri)
    "/" (response (str "<h1>The Moral Maze</h1>"
                       (if-let [f (request :flash)]
                         (str "You last chose " (if (= f :evil) "evil" "good") ".<p> What do you choose now:")
                         "What do you choose:")
                       "<a href=\"/good\">good</a> or <a href=\"/evil\">evil</a>?"))
    "/good" (assoc (response "<h1>good</h1> <a href=\"/\">choose again</a>" ) :flash :good)
    "/evil" (assoc (response "<h1>evil</h1> <a href=\"/\">choose again</a>" ) :flash :evil)
    (status-response 404 (str "<h1>404 Not Found: " (:uri request) "</h1>" ))))

;; This works fine in firefox, but the flash messages get lost in chrome because of its constant pestering
;; about favicon.ico. So we'd better make the flash messages persist in that case:

(defn handler [request]
  (case (request :uri)
    "/" (response (str "<h1>The Moral Maze</h1>"
                       (if-let [f (request :flash)]
                         (str "You last chose " (if (= f :evil) "evil" "good") ".<p> What do you choose now:")
                         "What do you choose:")
                       "<a href=\"/good\">good</a> or <a href=\"/evil\">evil</a>?"))
    "/good" (assoc (response "<h1>good</h1> <a href=\"/\">choose again</a>" ) :flash :good)
    "/evil" (assoc (response "<h1>evil</h1> <a href=\"/\">choose again</a>" ) :flash :evil)
    "/favicon.ico" {:flash (request :flash)}
    (status-response 404 (str "<h1>404 Not Found: " (:uri request) "</h1>" ))))



;; So far so good, but what if we want to keep scores for each user?

(defn home [request]
  (let
      [f         (request :flash)
       good   (get-in request [:session :good] 0)
       evil   (get-in request [:session :evil] 0)]
    (response (str "<h1>The Moral Maze</h1>"
                   "Good " good " : Evil " evil "<p>"
                   (if f
                     (str "You last chose " (if (= f :evil) "evil" "good") ".<p> What do you choose now:")
                     "What do you choose: ")
                   "<a href=\"/good\">good</a> or <a href=\"/evil\">evil</a>?"))))

(defn good [request]
  (let [ good   (get-in request [:session :good] 0) ]   
    (assoc (response "<h1>good</h1> <a href=\"/\">choose again</a>" ) 
      :flash :good 
      :session (assoc (request :session) :good (inc good)))))

(defn evil [request]
  (let [ evil   (get-in request [:session :evil] 0) ]   
    (assoc (response "<h1>evil</h1> <a href=\"/\">choose again</a>" ) 
      :flash :evil 
      :session (assoc (request :session) :evil (inc evil)))))

(defn handler [request]
  (case (request :uri)
    "/" (home request)
    "/good" (good request)
    "/evil" (evil request)
    "/favicon.ico" {:flash (request :flash)}
    (status-response 404 (str "<h1>404 Not Found: " (:uri request) "</h1>" ))))


;; Let's hide our workings and save the user from potential overclicking injuries

(require 'ring.util.response)

(defn good [request]
  (let [ good   (get-in request [:session :good] 0) ]   
    (assoc (ring.util.response/redirect "/")
      :flash :good 
      :session (assoc (request :session) :good (inc good)))))

(defn evil [request]
  (let [ evil   (get-in request [:session :evil] 0) ]   
    (assoc (ring.util.response/redirect "/")
      :flash :evil 
      :session (assoc (request :session) :evil (inc evil)))))

;; And then as a final flourish we'll keep total statistics as well

(def goodness (atom 0))
(def evilness (atom 0))

(defn good [request]
  (let [ good   (get-in request [:session :good] 0) ] 
    (swap! goodness inc)
    (assoc (ring.util.response/redirect "/")
      :flash :good 
      :session (assoc (request :session) :good (inc good)))))

(defn evil [request]
  (let [ evil   (get-in request [:session :evil] 0) ]  
    (swap! evilness inc)
    (assoc (ring.util.response/redirect "/")
      :flash :evil 
      :session (assoc (request :session) :evil (inc evil)))))

(defn home [request]
  (let
      [f         (request :flash)
       good   (get-in request [:session :good] 0)
       evil   (get-in request [:session :evil] 0)]
    (response (str "<h1>The Moral Maze</h1>"
                   "Good " good " : Evil " evil "<p>"
                   (if f
                     (str "You last chose " (if (= f :evil) "evil" "good") ".<p> What do you choose now:")
                     "What do you choose: ")
                   "<a href=\"/good\">good</a> or <a href=\"/evil\">evil</a>?"
                   "<p> Global Good: " (deref goodness) " Evil: " (deref evilness)))))


;; This all seems to work. But for some reason it makes me deeply uncomfortable.

;; I suppose I shouldn't really be using get requests to modify state,
;; and none of my data is going to survive a server restart, but I
;; don't think that's it.

;; There just seems to be something overcomplicated and fragile about
;; this, even though I don't seem to be able to break it.

;; Can anyone find a way of exposing the problem more clearly, or suggest a better way?







Thursday, January 10, 2013

How Sessions Work in Ring

Sessions are good fun, I found:


;;  necessary dependencies
;; [[org.clojure/clojure "1.4.0"]
;;  [ring/ring "1.1.6"]]
;; -------------

;; Here's an app, built in a way which should surprise no-one who's read the previous posts:

(require 'ring.adapter.jetty
         'ring.middleware.stacktrace
         'clojure.pprint)

;; Middleware for spying on the doings of other middleware:
(defn html-escape [string]
  (clojure.string/escape string {\< "&lt;", \> "&gt;"}))

(defn html-preformatted-escape [string]
  (str "<pre>\n" (html-escape string) "</pre>\n"))

(defn format-request [name request kill-keys kill-headers]
  (let [r1 (reduce dissoc request kill-keys)
        r (reduce (fn [h n] (update-in h [:headers] dissoc n)) r1 kill-headers)]
  (with-out-str
    (println "-------------------------------")
    (println name)
    (println "-------------------------------")
    (clojure.pprint/pprint r)
    (println "-------------------------------"))))


;; I have taken the liberty of removing some of the less fascinating entries from the request and response maps, for clarity
(def kill-keys [:body :character-encoding :remote-addr :server-name :server-port :ssl-client-cert :scheme  :content-type  :content-length])
(def kill-headers ["user-agent" "accept" "accept-encoding" "accept-language" "accept-charset" "cache-control" "connection"])

(defn wrap-spy [handler spyname]
  (fn [request]
    (let [incoming (format-request (str spyname ":\n Incoming Request:") request kill-keys kill-headers)]
      (println incoming)
      (let [response (handler request)]
        (let [outgoing (format-request (str spyname ":\n Outgoing Response Map:") response kill-keys kill-headers)]
          (println outgoing)
          (update-in response  [:body] (fn[x] (str (html-preformatted-escape incoming) x  (html-preformatted-escape outgoing)))))))))



;; Absolute binding promise to someday get around to writing the app
(declare handler)

;; plumbing
(def app
  (-> #'handler
      (ring.middleware.stacktrace/wrap-stacktrace) ;; belt
      (wrap-spy "what the handler sees" )
      (wrap-spy "what the web server sees" )
      (ring.middleware.stacktrace/wrap-stacktrace) ;; braces
      ))

;; The actual application
(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (str "<h1>Hello World!</h1>" )})


;; Start the server if it hasn't already been started
(defonce server (ring.adapter.jetty/run-jetty #'app {:port 8080 :join? false}))


;; Have a look at http://localhost:8080, and while you're there,
;; delete any cookies that your browser is storing for localhost:8080

;; In Chrome, you can right-click on a page, Inspect Element, and then
;; choose Resources/Cookies/localhost to give you a live view of your
;; cookies, which is nice to watch for the following.

;; In Firefox I can't find anything as nice. 

;; With curl of course, you have total control.
;; Real men, those who do not cower behind their mother's apron strings, like whining infants
;; may wish to experiment with commands such as:
;; $ curl -sv http://localhost:8080 -b cookies.txt -c cookies.txt && cat cookies.txt


;; Now we'll add the session middleware:

(require 'ring.middleware.session)

;; and re-plumb
(def app
  (-> #'handler
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy "what the handler sees" )
      (ring.middleware.session/wrap-session)
      (wrap-spy "what the web server sees" )
      (ring.middleware.stacktrace/wrap-stacktrace)
      ))


;; Have another look at http://localhost:8080, (i.e. refresh the page)


;; If you examine the request as passed to the handler, then you'll
;; see a :session key, whose value currently is {}. That's been
;; inserted by the session middleware.

;; That seems to be the only difference so far.

;; Now redefine the handler to return a :session key/value pair in the response map
(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (str "<h1>Hello World!</h1>" )
   :session "I am a session. Fear me."})


;; And refresh the page again. Only once! And pay careful attention!

;; The incoming maps are exactly the same, of course, but on the way out the middleware
;; notices the :session key, and transforms it into a cookie-setting header

;; Your browser should now have a stored cookie, named ring-session,
;; with a cryptic but hopefully unique random value

;; Again refresh, and notice that this time, the browser presents its cookie, 
;; and the middleware decodes it and puts a :session key in the request.
;; Notice that no new cookie is set the second time.

;; The cookie stays the same.

;; Notice that we can change the data in the session without changing
;; the cookie on the browser.  In the default implementation at least,
;; the data is stored on the server, and the browser's cookie just
;; tells the server which session to use.

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (str "<h1>Hello World!</h1>" )
   :session (let [rs (request :session)] (if (empty? rs) "I am a session. Fear me!"  (str rs "!")))})


;; One thing we have control of is the length of time before the session expires
;; Ten seconds is a bit short, but it does allow for some interesting effects:
(def app
  (-> #'handler
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy "what the handler sees" )
      (ring.middleware.session/wrap-session {:cookie-attrs {:max-age 10}})
      (wrap-spy "what the web server sees" )
      (ring.middleware.stacktrace/wrap-stacktrace)
      ))


;; Here's an app using sessions to store state in much the same way that we were using cookies earlier:
(defn handler [request]
  (when (not= (request :uri) "/favicon.ico")
    (let [count ((request :session {}) :count 0)]  ;; no, I didn't know you could do this either. neat, isn't it?
      {:status 200
       :headers {"Content-Type" "text/html"}
       :body (cond (zero? count) (str "<h1>Hello Stranger!</h1>" )
                   :else (str "<h1>Hello Again (" count ")!</h1>" ))
       :session {:count (inc count)}})))

;; How many times can you ping the virtual goldfish?
;; I got it up to 25 with:
;; watch -d -n 0 curl -sv http://localhost:8080 -b cookies.txt -c cookies.txt


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Footnote for Smug Lisp Weenies only

;; Ring provides an alternative to storing session variables in
;; memory, where it can encrypt them into a cookie

;; This seems a more 'functional' way to do things, without carrying
;; state in the server, and it probably is a good way to do things if
;; you're careful.

;; But they're not quite equivalent: Some things that you can do with the
;; memory backed store won't work if you have to serialize your
;; session data.

;; Try this:
(require 'ring.middleware.session.cookie)

(def app
  (-> #'handler
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy "what the handler sees" )
      (ring.middleware.session/wrap-session {:store (ring.middleware.session.cookie/cookie-store {:key "a 16-byte secret"})})
      (wrap-spy "what the web server sees" )
      (ring.middleware.stacktrace/wrap-stacktrace)
      ))

;; Everything should still work fine, but now notice that the cookie
;; is changing every time you refresh the page.

;; But if you redefine the handler

(defn handler [request]
      {:status 200
       :headers {"Content-Type" "text/html"}
       :body (str "<h1>Hello " (((request :session {}) :fn (fn[] "Stranger") )) "</h1>" )
       :session {:fn (fn[] "Again")}})

;; and refresh twice, then it should cause some sort of nasty exception

;; Now restore the memory-backed version and try again

(def app
  (-> #'handler
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy "what the handler sees" )
      (ring.middleware.session/wrap-session )
      (wrap-spy "what the web server sees" )
      (ring.middleware.stacktrace/wrap-stacktrace)
      ))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; One last flourish: flash messages

;; Flash messages use the session mechanism to allow a redirect to
;; leave a message on the page it is redirecting to:

(require 'ring.middleware.flash)

(def app
  (-> #'handler
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy "what the handler sees" )
      (ring.middleware.flash/wrap-flash)
      (wrap-spy "what the flash middleware sees" )
      (ring.middleware.session/wrap-session )
      (wrap-spy "what the web server sees" )
      (ring.middleware.stacktrace/wrap-stacktrace)
      ))

(defn link [s]
  (str "<a href=\"" s "\">" s "</a>"))

(defn handler [request]
  (case (request :uri)
    "/favicon.ico" {:status 404}
    "/" {:body (str "<h1>home " (request :flash) "</h1>"  "<p>" (link "/bother") "<p>" (link "/"))}
    "/bother" {:status 302, :headers {"Location" "/"}, :body "" :flash "(bothered)"}))

;; The mechanism here is quite subtle and bears thinking about.


;; The interested reader might also wish to get a load of this mother:

(defn handler [request]
  (case (request :uri)
    "/favicon.ico" {:status 404 
                    :session (update-in (request :session) [:favicon] (fnil inc 0))}
    "/" {:body (str "<h1>home " (request :flash) " </h1>"  
                    "<p> favicon requests: " (get-in request [:session :favicon] 0) 
                    "<p> bother requests: "  (get-in request [:session :bother ] 0) 
                    "<p>" (link "/bother") 
                    "<p>" (link "/"))}
    "/bother" {:status 302, :headers {"Location" "/"}, :body "" 
               :flash "(bothered)" 
               :session (update-in (request :session) [:bother] (fnil inc 0))}))





Wednesday, January 9, 2013

How Cookies Work in Ring

Cookies turn the stateless http protocol into something with memory.

Here's how to use them in Ring:



;;  necessary dependencies 
;; [[org.clojure/clojure "1.4.0"]
;;  [ring/ring "1.1.6"]]
;; -------------

;; Here's an app, built in a way which should surprise no-one who's read the previous posts

(require 'ring.adapter.jetty 
         'ring.middleware.stacktrace 
         'clojure.pprint)

;; Middleware for spying on the doings of other middleware:
(defn html-escape [string] 
  (str "<pre>" (clojure.string/escape string {\< "&lt;", \> "&gt;"}) "</pre>"))

(defn format-request [name request]
  (with-out-str
    (println "-------------------------------")
    (println name)
    (clojure.pprint/pprint request)
    (println "-------------------------------")))

(defn wrap-spy [handler spyname include-body]
  (fn [request]
    (let [incoming (format-request (str spyname ":\n Incoming Request:") request)]
      (println incoming)
      (let [response (handler request)]
        (let [r (if include-body response (assoc response :body "#<?>"))
              outgoing (format-request (str spyname ":\n Outgoing Response Map:") r)]
          (println outgoing)
          (update-in response  [:body] (fn[x] (str (html-escape incoming) x  (html-escape outgoing)))))))))



;; Absolute binding promise to someday get around to writing the app
(declare handler)

;; plumbing
(def app
  (-> #'handler
      (wrap-spy "what the handler sees" true)
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy "what the web server sees" false)))  

;; The actual application
(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (str "<h1>Hello World!</h1>" )})

 
;; Start the server if it hasn't already been started
(defonce server (ring.adapter.jetty/run-jetty #'app {:port 8080 :join? false}))


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Next we'll include the cookies middleware

(require 'ring.middleware.cookies)

;; And re-plumb

(def app
  (-> #'handler
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy "what the handler sees" true)
      (ring.middleware.cookies/wrap-cookies)
      (wrap-spy "what the web server sees" false)))


;; Now go and look at http://localhost:8080 again.

;; In the map the handler sees, there is a key :cookies, whose value is {}
;; ( If it's not, you might want to clear cookies for localhost from your browser )

;; Let's make our app set a cookie:
(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (str "<h1>Setting Cookie!</h1>" )
   :cookies {"yo" {:value "hi"}} })


;; What happens now is quite complicated. 

;; Our key 
{ :cookies {"yo" {:value "hi"}}}
;; Gets converted by the middleware, and combined with our header, to make
{ :headers {"Set-Cookie" '("yo=hi"), "Content-Type" "text/html"}}
;; in the map given to the jetty adapter

;; If you look at the page with
;; $ curl -sv http://localhost:8080
;; Then you'll see
;; < Set-Cookie: yo=hi
;; as part of the http transaction

;; Now if we look at http://localhost:8080, the response will contain the Set-Cookie header.

;; Most browsers will react to this by including the cookie whenever they contact the site.
;; You can examine cookies from the browser's point of view by 
;; (In Chrome) looking at chrome://chrome/settings/cookies
;; (In Firefox) following some interminable GUI procedure that life is too short to describe. 


;; If you refresh the page yet again, you should now see:
{:headers {"cookie" "yo=hi"}}
;; in the incoming request from the webserver
;; and a new key:
{:cookies {"yo" {:value "hi"}}} 
;; in the map the eventual handler sees (put there by the middleware of course!)



;; We can use this to count how many times a particular browser has been greeted:
(defn seen-before [request]
  (try (Integer/parseInt (((request :cookies) "yo") :value))
       (catch Exception e :never-before)))

(defn handler [request]
  (let [s (seen-before request)]
    (cond
     (= s :never-before) {:status 200
                          :headers {"Content-Type" "text/html"}
                          :body (str "<h1>Hello Stranger!</h1>" )
                          :cookies {"yo" {:value "1"}}}
     (= s 1) {:status 200
                          :headers {"Content-Type" "text/html"}
                          :body (str "<h1>Hello Again!</h1>" )
                          :cookies {"yo" {:value "2"}}}
     :else {:status 200
                          :headers {"Content-Type" "text/html"}
                          :body (str "<h1>Hi, this is visit "s"</h1>" )
                          :cookies {"yo" {:value (str (inc s))}}})))



;; And now, an exercise for the reader!

;; If I look at my site in Firefox, it works as I expected.

;; If I look at it with Chrome, it double counts

;; If I use curl, like so:
;; curl -sv http://localhost:8080 | grep -i hello

;; Then all I ever see is "Hello Stranger"

;; What is going on?



Tuesday, January 8, 2013

Really Rather Overdoing the Interest in How Parameters Work in Ring

The next interesting bit of the Ring Tutorial http://github.com/ring-clojure/ring/wiki/Parameters is pretty straightforward, but I got carried away a bit.


;;  necessary dependencies 
;; [[org.clojure/clojure "1.4.0"]
;;  [ring/ring "1.1.6"]]
;; -------------

;; Behold the mighty app which we have constructed so far:

(require 'ring.adapter.jetty)
(require 'ring.middleware.stacktrace)
(require 'clojure.pprint)

(defn wrap-spy [handler]
  (fn [request]
    (println "-------------------------------")
    (println "Incoming Request:")
    (clojure.pprint/pprint request)
    (let [response (handler request)]
      (println "Outgoing Response Map:")
      (clojure.pprint/pprint response)
      (println "-------------------------------")
      response)))



(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (str "<h1>Hello World!!!!!!!!!!!!!!!</h1>" )})

(def app
  (-> #'handler
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy)))

(defonce server (ring.adapter.jetty/run-jetty #'app {:port 8080 :join? false}))

;; You can access your greeting at:
;; http://localhost:8080 in your favourite browser.

;; Actually, I think it is nice to see the request and response maps as part of the web page
;; So we can modify our spy function

(defn html-escape [string] 
  (str "<pre>" (clojure.string/escape string {\< "&lt;", \> "&gt;"}) "</pre>"))

(defn wrap-spy [handler spyname include-body]
  (fn [request]
    (let [incoming (with-out-str
                     (println "-------------------------------")
                     (println spyname ":\n Incoming Request:")
                     (clojure.pprint/pprint request))]
      (println incoming)
      (let [response (handler request)]
        (let [outgoing (with-out-str 
                         (println spyname ":\n Outgoing Response Map:")
                         (clojure.pprint/pprint (if include-body response
                                                  (assoc response :body "#<?>")))
                         (println "-------------------------------"))]
          (println outgoing)
          (update-in response  [:body] (fn[x] (str (html-escape incoming) x  (html-escape outgoing)))))))))


(def app
  (-> #'handler
      (wrap-spy "what the handler sees" true)
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy "what the web server sees" false)
      ))

;; With our spying middleware, we can investigate the subject of parameters

;; point your browser at http://localhost:8080/?doom=sarnath

;; In the map which is presented to app by jetty and ring, there is the key :query-string
;; Which should have the value "doom=sarnath"

;; We could use that directly

(defn handler [request]
    {:status 200
     :headers {"Content-Type" "text/html"}
     :body (if-let [s (request :query-string)]
             (let [[a b c] (re-matches #"(.*)=(.*)" (request :query-string))]
                (if (and a b c) (str "<h1>You Have Invoked " b " Upon the City of " c "</h1>")
                    (str "<h1>I do not understand, oh dark master...</h1>" )))
             (str "<h1>Hello World!!!!!!!!!!!!!!!</h1>" ))})

;; But clearly there are issues with that approach.

;; Instead, we can let ring take care of it for us by inserting another piece of middleware:

(require 'ring.middleware.params)

(def app
  (-> #'handler
      (wrap-spy "what the handler sees" true)
      (ring.middleware.stacktrace/wrap-stacktrace)
      (ring.middleware.params/wrap-params)
      (wrap-spy "what the web server sees" false)
      ))


;; If you have another look at http://localhost:8080/?Doom=Sarnath

;; You'll see that in between the web server and the handler function, wrap-params has inserted
;; another key, :query-params, with the :query-string already split up into a key-value map

;; So we don't need to parse the query string ourselves any more:

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (if-let [m (request :query-params)]
           (if (empty? m)
             (str "<h1>Hello World!</h1>" )
             (apply str (for [[k v] m] (str "<h1>You Have Invoked " k " Upon the City of " v "</h1>"))))
           (str "<h1>Missing :query-params. Have you included ring.middleware.stacktrace/wrap-stacktrace, oh dark master?</h1>" ))})


             

Getting Started with Ring

In order to write a web app in clojure, it's necessary to understand the library Ring. Ring is a lovely clean design, with good up-to-date docs in the form of its wiki on github.

I'd like to understand how ring works rather better than I do, and so I'm working my way through the whole tutorial step by step, and as I'm going, I'm trying out various things, and making notes.

Here's my version of the 'getting started' tutorial. It's got a few extra frills over the original. I don't know whether other people will find it confusing or helpful, but I'm pretty sure that I'll need to refer to it myself in the future!



;;  necessary dependencies 
;; [[org.clojure/clojure "1.4.0"]
;;  [ring/ring "1.1.6"]]
;; -------------

;; A ring application is a function which takes a request map, and
;; returns a response map

;; Our first response map will have the HTTP status code 200, OK, a
;; content-type header that tells the browser that it's getting plain text
;; and a traditional body text.

(defn app [request]
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body "Hello World"})


;; Having got a ring application, we need to start a webserver to hand the pages out
;; We'll use jetty (via ring)
(require 'ring.adapter.jetty)

;; And we'd like those pages served on port 8080 

;; Three things to note here:
;;
;; :join? false means that the evaluating thread won't wait for the
;; server to finish (so that the repl doesn't seem to hang).
;;
;; referring to the application function via #' means that ring sees
;; the variable user/app rather than the function (fn[x]{:status 200})
;; which that variable evaluates to. And that means that if we
;; reevaluate the definition, the behaviour the browser sees will
;; change.
;;
;; finally defonce means that if we reload this file, or re-evaluate
;; this line, nothing will happen. That prevents us from accidentally
;; creating multiple copies of the jetty server.

(defonce server (ring.adapter.jetty/run-jetty #'app {:port 8080 :join? false}))

;; So, go and look at http://localhost:8080 in your favourite browser.



;; Now let's check that redefining the handler causes a change in the running webapp
(defn app [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "<h1>Hello World</h1>"})

;; Refresh your browser to see the change.

;; I like to leave the web browser of the gods:
;; $ watch -d -n 1 curl -sv http://localhost:8080/ 
;; running in a terminal somewhere.

;; Let's demonstrate that we can stop and restart our server

(.stop server)

(.start server)

;; Now, let's look at the information that is going in and out of our application
(require 'clojure.pprint)

;; First we'll delegate the actual functionality of our app to a handler

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "<h1>Hello World</h1>"})

;; And then we'll wrap that in a wrapper that prints the incoming and outgoing data:

(defn app [request]
  (println "-------------------------------")
  (println "Incoming Request:")
  (clojure.pprint/pprint request)
  (let [response (handler request)]
    (println "Outgoing Response Map:")
    (clojure.pprint/pprint response)
    (println "-------------------------------")
    response))

;; Another way to do the same thing is to define what's called a
;; middleware. This is a concept from python, and a good demonstration
;; of why dynamically typed functional languages are such pleasant
;; things to use

;; We define wrap-spy as a function which does to any handler what app does to our handler

(defn wrap-spy [handler]
  (fn [request]
    (println "-------------------------------")
    (println "Incoming Request:")
    (clojure.pprint/pprint request)
    (let [response (handler request)]
      (println "Outgoing Response Map:")
      (clojure.pprint/pprint response)
      (println "-------------------------------")
      response)))


;; And now we can write

(def app 
  (wrap-spy handler))

;; Or more idiomatically

(def app
  (-> handler
      (wrap-spy)))

;; which means exactly the same thing!

;; Unfortunately, we've now lost the ability to redefine handler and see the change 
;; reflected in the running app.
(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "<h1>Hello World!</h1>" })

;; But the same trick with passing the var in works again.
(def app
  (-> #'handler
      (wrap-spy)))

;; And now we do see changes reflected immediately:
(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body "<h1>Hello World!!!!!!!!!!!!!!!1</h1>" })

;; Error handling in our app is conservative.

(defn handler [request]
  {:status 200
   :headers {"Content-Type" "text/html"}
   :body (str "<h1>Hello World!!!!!!!!!!!!!!!1</h1>" (/ 1 0))})

;; The browser gets an HTTP 500 Server Error, and the divide by zero
;; message goes to the console where the server is running.

;; But for development purposes, we can use one of the middlewares provided with ring:

(require 'ring.middleware.stacktrace)

(def app
  (-> #'handler
      (ring.middleware.stacktrace/wrap-stacktrace)
      (wrap-spy)))

;; Now the stacktrace appears nicely formatted in the web browser instead.










Thursday, October 25, 2012

£500 if you can find me a job!



I'm looking for a job, and as usual I'll pay a commission of £500 to anyone who can find me a good one.

Details here: http://johnlawrenceaspden.blogspot.co.uk/2012/10/gis-job-500-reward.html
CV here: http://www.aspden.com

Obviously Clojure is a speciality, and I'd love a Clojure job, but I can program in all styles. I've spent the last year or so working in C and Verilog, and I like those too, when they're being used for the domain where they were intended.

In fact the main reason this blog hasn't been updated recently is that after a full day of programming, the last thing I want to do is come home and program for fun, so I've hardly used Clojure since last summer.

On the other hand, if you're a local company using Java, who might be interested in giving Clojure a try, I'd love to try to show you what all the fuss is about.

And as a bonus, if I was using Clojure for work, I'd probably get interested again and start updating this blog.

Thursday, September 22, 2011

Clojure Setup Tutorial with EMACS: clojure, clojure.contrib, swank, slime, maven and maven-clojure-plugin in a couple of seconds

As of 22nd September 2011, this is still working fine, this time on Fedora 14 (substituting yum for apt-get, but that's the only difference)

------------------------------------------------------------------------------------------------------------------------------------

I've just (15th February 2011) had to set this up again for someone on an Ubuntu 10.10 box.

And to my speechless amazement (16th February 2011), it worked absolutely the same way on Radek Ostrowski (a complete stranger) 's mac at dev8d, thus condemning him to years of suffering at the hands of Aquamacs.

This method is still my favourite, still the one I actually use in practice, and still the easiest, and it's been stable for about a year now.


Since I've just confirmed that it still works, I've moved it back to the top.







Install maven: 


$sudo apt-get install maven2 


Then create a pom.xml file to tell maven which repositories to use. There's an example below to copy and paste.


Once you've got maven installed, and made a pom.xml file, then:


$mvn clojure:repl


Will start a REPL with clojure 1.2 and clojure-contrib 1.2 on the classpath. The REPL should use jLine to give it editing and history on all platforms.

If you like to use EMACS instead, then

$mvn clojure:swank



Will start a swank server, which you can connect to with M-x slime-connect.


That's it from the clojure side.



To set up emacs, and equip it to talk to the clojure swank server:


$sudo apt-get install emacs


Then install the emacs lisp package archive (see http://tromey.com/elpa/) by evaluating this code:

 (cut and paste it into the scratch buffer, put the cursor in the middle, and use M-C-x):


--emacs lisp to install elpa---------------






(let ((buffer (url-retrieve-synchronously
        "http://tromey.com/elpa/package-install.el")))
  (save-excursion
    (set-buffer buffer)
    (goto-char (point-min))
    (re-search-forward "^$" nil 'move)
    (eval-region (point) (point-max))
    (kill-buffer (current-buffer))))



---end of emacs lisp to install elpa-------------




Now use M-x package-list-packages to bring up the list of packages, and use i and then x to mark and then install slime, slime-repl, and clojure-mode.


Then connect emacs to the already running clojure image with M-x slime-connect


That should be it. You should now be at a running clojure 1.2 repl inside emacs.





Here's an example of a pom.xml file that pulls in clojure and clojure-contrib 1.2 . Just cut and paste it.





As well as the essentials, I've also added: the clojars.org repository, where many useful clojure packages live;  the maven versions plugin, which helps with keeping everything cutting edge; and jline so that command line repls work better (mvn clojure:repl)

I like to have my startup repls conditioned a little, so if you have a startup script that you always want to run, add this snippet

        <configuration>
          <replScript>startup.clj</replScript>
        </configuration>

to the clojure-maven-plugin section so that when maven starts a repl, the code in startup.clj is loaded as the first action. This is a good place to set print-length and print-level, so that they will be set before the swank server starts, which means that you won't hang emacs by evaluating an infinite sequence.


I also like to require everything on the classpath, so that I can use things like find-doc to find out about everything. 


pom.xml files look terrifying, but they're really not. It's just that xml is such a godawful verbose way to write things out.

It contains: the addresses of three repositories which hold vital code; the names and versions of four vital jar files that you need; and the names and versions of two helpful maven plugins.


pom.xml



<project>

  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>hello-maven-clojure-swank</artifactId>

  <version>1.0-SNAPSHOT</version>
  <name>hello-maven</name>
  <description>maven, clojure, emacs: together at last</description>

  <repositories>
    <repository>
      <id>clojars</id>
      <url>http://clojars.org/repo/</url>
    </repository>
    <repository>
      <id>clojure</id>
      <url>http://build.clojure.org/releases</url>
    </repository>
    <repository>
      <id>central</id>
      <url>http://repo1.maven.org/maven2</url>
    </repository>
  </repositories>

  <dependencies>
    <dependency>
      <groupId>org.clojure</groupId>
      <artifactId>clojure</artifactId>
      <version>1.2.0</version>
    </dependency>
    <dependency>
      <groupId>org.clojure</groupId>
      <artifactId>clojure-contrib</artifactId>
      <version>1.2.0</version>
    </dependency>
    <dependency>
      <groupId>jline</groupId>
      <artifactId>jline</artifactId>
      <version>0.9.94</version>
    </dependency>
    <dependency>
      <groupId>swank-clojure</groupId>
      <artifactId>swank-clojure</artifactId>
      <version>1.2.1</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
         <groupId>com.theoryinpractise</groupId>
            <artifactId>clojure-maven-plugin</artifactId>
         <version>1.3.3</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
          <artifactId>versions-maven-plugin</artifactId>
        <version>1.2</version>
      </plugin>
    </plugins>

  </build>

</project>




Followers