21
Frameworkless Web Development in Clojure Andreas ’Kungi’ Klein 24.01.2015 Andreas ’Kungi’ Klein Frameworkless Web Development in Clojure 24.01.2015 1 / 21

Frameworkless Web Development in Clojure

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