(require 'ring.adapter.jetty
'ring.middleware.stacktrace
'ring.middleware.session.cookie
'ring.middleware.session
'clojure.pprint)
(defn html-escape [string]
(clojure.string/escape string {\< "<", \" """, \& "&", \> ">"}))
(defn html-pre-escape [string]
(str "<pre>" (html-escape string) "</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 " (request):") request kill-keys kill-headers)]
(println incoming)
(let [response (handler request)]
(let [outgoing (format-request (str spyname " (response):") response kill-keys kill-headers)]
(println outgoing)
(if (= (type (response :body)) java.lang.String)
(update-in response [:body] (fn[x] (str (html-pre-escape incoming) x (html-pre-escape outgoing))))
response))))))
(defn status-response [code body]
{:status code
:headers {"Content-Type" "text/html"}
:body body})
(def response (partial status-response 200))
(defn hppp[x] (html-pre-escape (with-out-str (binding [clojure.pprint/*print-right-margin* 120] (clojure.pprint/pprint x)))))
(defn hpp[x] (html-pre-escape (str x)))
(defn hp[x] (html-escape (str x)))
(declare handler)
(def app
(-> #'handler
(ring.middleware.stacktrace/wrap-stacktrace)
(wrap-spy "handler" )
(ring.middleware.session/wrap-session {:store (ring.middleware.session.cookie/cookie-store {:key "a 16-byte secret"})})
(ring.middleware.stacktrace/wrap-stacktrace)))
(defonce server (ring.adapter.jetty/run-jetty #'app {:port 8080 :join? false}))
(defn good [request]
(assoc (response "<h1>good</h1> <a href=\"/\">choose again</a>" )
:session (update-in (request :session) [:good] (fnil inc 0))))
(defn evil [request]
(assoc (response "<h1>evil</h1> <a href=\"/\">choose again</a>" )
:session (update-in (request :session) [:evil] (fnil inc 0))))
(defn home [request]
(let
[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> What do you choose: "
"<a href=\"/good\">good</a> or <a href=\"/evil\">evil</a>?"))))
(defn handler [request]
(case (request :uri)
"/" (home request)
"/good" (good request)
"/evil" (evil request)
(status-response 404 (str "<h1>404 Not Found: " (:uri request) "</h1>" ))))
(defmacro routefn [& addresses]
`(fn[~'request]
(case (~'request :uri)
~@(mapcat (fn[x] [(str "/" x) (list x 'request)]) addresses)
"/" (home ~'request)
(status-response 404 (str "<h1>404 Not Found: " (:uri ~'request) "</h1>" )))))
(def handler (routefn good evil))
((handler {:uri "/"}) :status)
((handler {:uri "/evil" :session {:mysesh 'yo}}) :session)
(defn sprocess [session uri]
(let [ns (:session (handler{:uri uri :session session}))]
(if (nil? ns) session ns)))
(sprocess {} "/home") (sprocess {} "/evil") (sprocess {:evil 1} "/evil")
(sprocess (sprocess {} "/evil") "/evil")
(-> {}
(sprocess "/home")
(sprocess "/good")
(sprocess "/evil")
(sprocess "/good"))
(reduce sprocess {} ["/evil" "/evil" "/good" "/evil" ])
(use 'clojure.test)
(deftest sitetest
(testing "page status"
(is (= (map (fn[x] ((handler {:uri x}) :status)) ["/" "/good" "/evil" ]) '(200 200 200)))
(is (= (map (fn[x] ((handler {:uri x}) :status)) ["/home" "/favicon.ico" ]) '(404 404))))
(testing "html"
(is (re-find #"Good\W*0\W:\WEvil\W0" ((handler {:uri "/"}) :body)))
(is (re-find #"Good\W*10\W:\WEvil\W0" ((handler {:uri "/" :session {:good 10}}) :body)))
(is (re-find #"Good\W*10\W:\WEvil\W20" ((handler {:uri "/" :session {:good 10 :evil 20}}) :body))))
(testing "session"
(is (= 21 (((handler {:uri "/evil" :session {:good 10 :evil 20}}) :session) :evil)))
(is (= 10 (((handler {:uri "/evil" :session {:good 10 :evil 20}}) :session) :good)))
(is (= (reduce sprocess {:userid "fred" :good 2}
["/evil" "/good" "/" "/home" "/evil" "/favicon.ico" "/evil" "/evil"])
{:good 3, :evil 4, :userid "fred"}))))
(defonce db (atom {}))
(def app
(-> #'handler
(ring.middleware.stacktrace/wrap-stacktrace)
(wrap-spy "handler" )
(ring.middleware.session/wrap-session {:store (ring.middleware.session.memory/memory-store db)})
(ring.middleware.stacktrace/wrap-stacktrace)))
(defn database [request]
(response
(str "<h1>Database</h1>"
"<pre>" "(swap! db (fn[x] (merge x " (hppp @db) ")))" "</pre>")))
(def handler (routefn good evil database))
(defn highscores [request]
(let [score (fn[[k v]]
(let [e (v :evil 0)
g (v :good 0)
r (if (zero? (+ e g)) 1/2 (/ e (+ e g)))]
[ r k g e]))
hst (sort (map score @db))]
(response (str
"<h1>High Score Table</h1>"
"<table>"
(str "<tr>""<th>" "User ID" "<th/>""<th>" "Chose Good" "<th/>""<th>" "Chose Evil" "<th/>" "</tr>")
(apply str (for [i hst] (str "<tr>""<td>" (i 1) "<td/>""<td>" (i 2) "<td/>""<td>" (i 3) "<td/>" "</tr>")))
"</table>"
))))
(def handler (routefn good evil database highscores))
(use 'ring.middleware.params)
(def app
(-> #'handler
(ring.middleware.stacktrace/wrap-stacktrace)
(wrap-spy "handler" )
(ring.middleware.params/wrap-params)
(ring.middleware.session/wrap-session {:store (ring.middleware.session.memory/memory-store db)})
(ring.middleware.stacktrace/wrap-stacktrace)))
(defn home [request]
(let
[good (get-in request [:session :good] 0)
evil (get-in request [:session :evil] 0)
name (get-in request [:session :name] "one who wishes anonymity")]
(response (str "<h1>The Moral Maze</h1>"
"<p>Welcomes: <b>" name "</b>"
" (<a href=\"/namechange\">change</a>)"
"<p>Good " good " : Evil " evil
"<p> What do you choose: "
"<a href=\"/good\">good</a> or <a href=\"/evil\">evil</a>?"
"<p><hr/><a href=\"/database\">database</a> or <a href=\"/highscores\">high scores</a>"))))
(defn namechange [request]
(response (str "<form name=\"form\" method=\"post\" action=\"/change-my-name\">"
"<input name=\"newname\" value=\"" ((request :session) :name "type name here") "\">")))
(defn change-my-name [request]
(let [newname ((request :params) "newname")]
(assoc (response (str "ok " newname "<p><a href=\"/\">back</a>"))
:session (assoc (request :session) :name newname))))
(def handler (routefn good evil database highscores namechange change-my-name))
(defn highscores [request]
(let [score (fn[[k v]]
(let [e (v :evil 0)
g (v :good 0)
n (v :name "anon")
r (if (zero? (+ e g)) 1/2 (/ e (+ e g)))]
[ r n g e k]))
hst (sort (map score @db))]
(response (str
"<h1>High Score Table</h1>"
"<table border=1 frame=box rules=rows>"
(str "<tr>""<th>" "Name" "<th/>""<th>" "Chose Good" "<th/>""<th>" "Chose Evil" "<th/>" "</tr>")
(apply str (for [i hst] (str "<tr>""<td>" (hp (i 1)) "<td/>""<td>" (hp (i 2)) "<td/>""<td>" (hp (i 3)) "<td/>" "</tr>")))
"</table>"
))))
(defn change-my-identity [request]
(let [newid ((request :params) "newidentity")]
(if-let [newsessioncookie (ffirst (filter (fn[[k v]] (= (v :name) newid)) @db))]
(assoc (response (str "if you say so...<i>" newid "</i><p><a href=\"/\">home</a>"))
:cookies {"ring-session" {:value newsessioncookie}})
(response "<span style=\"color:red\"><b><i>I think not!</i></b></span>"))))
(defn changeidentity [request]
(response (str "<form name=\"form\" method=\"post\" action=\"/change-my-identity\">"
"If you ain't " ((request :session) :name "dat geezer") " den who <i>are</i> you? :"
"<input name=\"newidentity\" value=\"" ((request :session) :name "type name here") "\">")))
(defn home [request]
(let
[good (get-in request [:session :good] 0)
evil (get-in request [:session :evil] 0)
name (get-in request [:session :name] "one who wishes anonymity")]
(response (str "<h1>The Moral Maze</h1>"
"<p>Welcomes: <b>" name "</b>"
" (<a href=\"/namechange\">change</a>)"
"<p> (<a href=\"/changeidentity\">not " name "? log in as someone else.</a>)"
"<p>Good " good " : Evil " evil
"<p> What do you choose: "
"<a href=\"/good\">good</a> or <a href=\"/evil\">evil</a>?"
"<p><hr/><a href=\"/database\">database</a> or <a href=\"/highscores\">high scores</a>"))))
(def handler (routefn good evil database highscores namechange change-my-identity change-my-name changeidentity))
(defn change-my-name [request]
(let [newname ((request :params) "newname")
newpassword ((request :params) "password")]
(if (and newname newpassword)
(assoc
(response (str "ok " newname "<p><a href=\"/\">back</a>"))
:session (assoc (request :session) :name newname :password newpassword))
(response "fail"))))
(defn namechange [request]
(response (str "<form name=\"form\" method=\"post\" action=\"/change-my-name\">"
"Name: <input name=\"newname\" value=\"" ((request :session) :name "type name here") "\">"
"<p>Password: <input name=\"password\" value=\"" ((request :session) :password "f@ilz0r!") "\">"
"<input type=\"submit\" value=\"Click!\" />"
"</form>")))
(defn changeidentity [request]
(response (str "<form name=\"form\" method=\"post\" action=\"/change-my-identity\">"
"If you ain't " ((request :session) :name "dat geezer") " den who <i>are</i> you? :<p>"
"Name : <input name=\"newidentity\" value=\"" ((request :session) :name "type name here") "\">"
"Password: <input name=\"password\" value=\"\">"
"<input type=\"submit\" value=\"Click!\" />"
"</form>")))
(defn change-my-identity [request]
(let [newid ((request :params) "newidentity")
password ((request :params) "password")]
(if-let [newsessioncookie (ffirst (filter (fn[[k v]] (and (= (v :name) newid) (= (v :password) password))) @db))]
(assoc (response (str "if you say so...<i>" newid "</i><p><a href=\"/\">home</a>"))
:cookies {"ring-session" {:value newsessioncookie}})
(response "<span style=\"color:red\"><b><i>I think not!</i></b></span>"))))
(defn passwords [request]
(if ((request :session) :admin)
(response (hppp (for [[ k {n :name p :password}] @db] [n p])))
(response "no way!")))
(def handler
(routefn good evil highscores
database passwords
namechange change-my-name
changeidentity change-my-identity))
(swap! db (fn[x] (merge { "no session" {:name "admin" :password "pa55word" :admin true }})))
Thank you for this introduction - glad to see new stuff here!
ReplyDeleteBest, fb