Upload
kungi2342
View
449
Download
5
Tags:
Embed Size (px)
Citation preview
Frameworkless Web Development in Clojure
Andreas ’Kungi’ Klein
24.01.2015
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 1 / 21
Outline
1 Web programming in Clojure
2 HTTP Abstraction
3 Routing
4 HTML Templating
5 Authentication
6 Conclusion
7 Bibliography
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 2 / 21
Web programming in Clojure
What’s I like?No dominant web frameworksNo framework dependent paradigmsJust plain clojure data structures and functionsThe full power of clojure is available in every ”step”
Web programming from the bottom up
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 3 / 21
What’s nice to have for web programming?
The typical web framework offers:Web Server / HTTP abstractionMiddlewares
SessionCSRF Protection
Request RoutingHTML TemplatingAuthentication(Database abstraction)(Communication with the frontend)
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 4 / 21
Clojure’s libraries
Feature LibraryHTTP Abstraction RingMiddlewares RingRouting Clout / CompojureHTML Templating Hiccup / EnliveAuthentication Friend
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 5 / 21
What is Ring?
Ring is the most widely used Clojure interface between web servers and webapplications.Similar technologies in other languages include
Plack (Perl)Rack (Ruby)Clack (Common Lisp)WSGI (Python)
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 6 / 21
Ring concepts
Client
Adapter
HTTP Request
Ring Request
Middleware
Ring RequestHandler
Adapter
HTTP Response
Ring Response
Middleware
Ring Response
Requests, Responses, Middlewares, Handlers, AdaptersAndreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 7 / 21
Ring exampleExample project.clj:dependencies [[org.clojure/clojure "1.6.0"]
[ring/ring-core "1.3.2"]]:plugins [[lein-ring "0.9.1"]]:ring {:handler ring-example.core/handler}
Example Ring handler(ns ring-example.core)
(defn handler [request]{:status 200:headers {"Content-Type" "text/html"}:body "Hello World"})
Start withlein ring server
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 8 / 21
Request / Response
Requests and responses are maps (see ring spec)(require '[ring.mock.request :as mock])(mock/request :get "/hallo")=> {:server-port 80
:server-name "localhost":remote-addr "localhost":uri "/hallo":query-string nil:scheme :http:request-method :get:headers {"host" "localhost"}}
(handler (mock/request :get "/hallo"))=> {:headers {"Content-Type" "text/html"}
:status 200:body "Hello World"}
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 9 / 21
Middleware
A middleware is a higher order function that enhances a handler. The firstargument is a handler-function and the middleware itself returns a handlerfunction.(defn wrap-content-type [handler content-type]
(fn [request](let [response (handler request)]
(assoc-in response [:headers "Content-Type"] content-type))))
Usage(def app
(-> handler(wrap-content-type "text/html")))
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 10 / 21
Adapter
Adapters translate from a concrete web server to ring requests and responses. Forexample ring.adapter.jetty as the name suggests connects ring to jetty. Thisis where most of the magic happens:(defn- proxy-handler
"Returns an Jetty Handler implementationfor the given Ring handler."[handler](proxy [AbstractHandler] []
(handle [_ ^Request base-request request response](let [request-map (servlet/build-request-map request)
response-map (handler request-map)](when response-map
(servlet/update-servlet-response responseresponse-map)
(.setHandled base-request true))))))
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 11 / 21
Clout
Clout is an HTTP matching library using the same syntax as Rails or Sinatra.(require '[clout.core :as clout])(require '[ring.mock.request :as mock])
(clout/route-matches "/article/:title/author/:name"(mock/request :get "/article/clojure/author/rich"))
=> {:title "clojure" :name "rich"}
(clout/route-matches "/article/:title"(mock/request :get "schnuff-schnuff"))
=> nil
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 12 / 21
Compojure
Most of the time Clout is used through compojure. Compojure combines ring andclout through a concise syntax.(require '[compojure.core :refer [GET POST defroutes]])(require '[ring.util.response :refer [redirect-after-post]])
(defroutes app(GET "/article/:title" [title]
(str "Found article " title))(POST "/article" request
(let [article (create-article (:body request))](reqirect-after-post (str "/article/" (:id article))))))
Compojure routes are ring handlers.
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 13 / 21
Hiccup
Hiccup is a literal translation from HTML to clojure vectors and maps.(use 'hiccup.page)(html5[:head [:title "How much do I love clojure?"]][:body [:h1 "Very Much!"]])
=> "<!DOCTYPE html><html>
<head><title>How much do I love clojure?</title></head><body><h1>Very Much!</h1></body>
</html>"It also provides some CSS selector like shortcuts.[:div#id.class-1.class2 "Schnuff!"]=> "<div class=\"class-1 class2\" id=\"id\">Schnuff!</div>"
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 14 / 21
EnliveA completely different strategy to HTML templating is provided by Enlive. Itworks with selectors and transformations.Suppose the following HTML exists in test.html:<!DOCTYPE html><html lang="en">
<head><title>This is a title placeholder</title></head><body><ul></ul></body>
</html>
(use '[net.cgrand.enlive-html])
(deftemplate main-template "test.html"[foo][:head :title] (content (str "Enlive starter kit: " foo))[:body :ul] (do-> (add-class "item-list")
(append (html [:li "Item 1"]))(append (html [:li "Item 2"]))))
deftemplate creates a function which returns html.
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 15 / 21
Friend
Friend calls itself an ”extensible authentication and authorization library forClojure Ring web applications and services.”
Works very well with ring/compojureProvides role based authentication
Friend contains two mayor conceptsCredential functionsAuthentication workflows (e.g. OAuth, HTTP Basic Auth, Login Form, …)
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 16 / 21
Credential function example
A credential function returns either nil or a map representing the credentials like
{:username "joe":app.foo/passphrase "bcrypt hash"}
(defn bcrypt-credential-fn[load-credentials-fn {:keys [username password]}](when-let [creds (load-credentials-fn username)]
(let [password-key (or (-> creds meta ::password-key):password)]
(when (bcrypt-verify password (get creds password-key))(dissoc creds password-key)))))
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 17 / 21
Workflow exampleA workflow function either returns an authorization map containing the userscredentials or it delivers an error page depending on the kind of workflow. Theinteractive-form workflow for example delivers a login page in case of failure.See the following simplified example from the friend source:(defn http-basic
[& {:keys [credential-fn realm] :as basic-config}](fn [{{:strs [authorization]} :headers :as request}](when (and authorization (re-matches #"\s*Basic\s+(.+)" authorization))
(if-let [[[_ username password]] (extract-username-password ...)](if-let [user-record
((gets :credential-fnbasic-config(::friend/auth-config request))
^{::friend/workflow :http-basic}{:username username, :password password})]
(make-auth user-record{::friend/workflow :http-basic::friend/redirect-on-auth? false::friend/ensure-session false})
(http-basic-deny realm request)){:status 400:body "Malformed Authorization header for HTTP Basic authentication."}))))
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 18 / 21
Putting it together(ns your.ring.app
(:require [cemerick.friend :as friend](cemerick.friend [workflows :as workflows]
[credentials :as creds])))
;; a dummy in-memory user "database"(def users {"root" {:username "root"
:password (creds/hash-bcrypt "admin_password"):roles #{::admin}}
"jane" {:username "jane":password (creds/hash-bcrypt "user_password"):roles #{::user}}})
(def ring-app;; ... assemble routes however you like ...)
(def secured-app(-> ring-app
(friend/authenticate{:credential-fn (partial creds/bcrypt-credential-fn users):workflows [(workflows/interactive-form)]})
;; ...required Ring middlewares ...))
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 19 / 21
Frameworkless Web Development
What’s great about Frameworkless?Small libraries can be composed easilyCommunication between these libraries is easy because plain old clojure datastructures are usedSystems become easier through separation of concerns
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 20 / 21
Bibliography
https://github.com/ring-clojure/ringhttps://github.com/weavejester/clouthttps://github.com/weavejester/compojurehttps://github.com/weavejester/hiccuphttps://github.com/cgrand/enlivehttps://github.com/cemerick/friend
Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 21 / 21