SlideShare une entreprise Scribd logo
1  sur  41
Télécharger pour lire hors ligne
Ring

deconstructing the functional web
Norman Richards
orb@nostacktrace.com
Response Map

Request Map

HTTP → {} → fn → {} → HTTP
Handler FN
ring-jetty-adaptor

ring handler
(def server
(ring.adapter.jetty/run-jetty #’handler
{:port 8080 :join? false}))
!

(.stop server)
 ring.adaptor.jetty/proxy-handler

(defn- proxy-handler
  "Returns an Jetty Handler implementation for 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 response response-map)
          (.setHandled base-request true))))))
ring.util.servlet/build-request-map
(defn build-request-map
  "Create the request map from the HttpServletRequest object."
  [^HttpServletRequest request]
  {:server-port
(.getServerPort request)
   :server-name
(.getServerName request)
   :remote-addr
(.getRemoteAddr request)
   :uri
(.getRequestURI request)
   :query-string
(.getQueryString request)
   :scheme
(keyword (.getScheme request))
   :request-method
(keyword (.toLowerCase (.getMethod request)))
   :headers
(get-headers request)
   :content-type
(.getContentType request)
   :content-length
(get-content-length request)
   :character-encoding (.getCharacterEncoding request)
   :ssl-client-cert
(get-client-cert request)
   :body
(.getInputStream request)})
not functional :(
ring.util.servlet/update-servlet-response

(defn update-servlet-response
  "Update the HttpServletResponse using a response map."
  [^HttpServletResponse response, {:keys [status headers body]}]
  (when-not response
    (throw (Exception. "Null response given.")))
  (when status
    (set-status response status))
  (doto response
    (set-headers headers)
    (set-body body)))
Ring Handlers
Handler: (RequestMap → ResponseMap)
(defn handler-nil [req]
{:body nil})
response body
ring.util.servlet/set-body

(defn- set-body
  "Update a HttpServletResponse body with a String, ISeq, File or InputStream."
  [^HttpServletResponse response, body]
  (cond
    (string? body)
      (with-open [writer (.getWriter response)]
        (.print writer body))
    (seq? body)
  ;; …
    (instance? File body)
  ;; …
    (nil? body)
      nil
    :else
      (throw (Exception. ^String (format "Unrecognized body: %s" body)))))
(defn handler-string [req]
{:body "hello world”})
(defn handler-file [req]
{:body (clojure.java.io/file "info.txt")})
(defn handler-status [req]
{:status 402
:headers {"Location"
"bitcoin:1G9TyAaKrfJn7q4Vrr15DscLXFSRPxBFaH?amount=.001"}})
Handlers can return status code and headers
ring.util.response/*

A few response helpers

(defn response
  "Returns a skeletal Ring response with the given body, status of 200, and no
headers."
  [body]
  {:status 200
   :headers {}
   :body
body})
!

(defn not-found
  "Returns a 404 'not found' response."
  [body]
  {:status 404
   :headers {}
   :body
body})
!

(defn redirect
  "Returns a Ring response for an HTTP 302 redirect."
  [url]
  {:status 302
   :headers {"Location" url}
   :body
""})
(defn handler [req]
(response/response "Hello, world!”))
!

(defn handler [req]
(response/redirect "http://lmgtfy.com/?q=http+redirect"))
!

(defn handler [req]
(response/resource-response "hello.txt"))
Building up a response

(defn handler [req]
(-> (response/response "")
(response/status 302)
(response/header "Location" "http://www.google.com")))
Wrapping requests
(middleware)
Middleware: (Handler → Handler)
(defn handler-reload1 [req]
(response/response (reload-me/some-work)))
A function we’d like to be reloaded if it changes
(defn handler-reload2 [req]
(require 'ringtest.reload-me :reload)
(handler-reload1 req))
The original handler is wrapped
Abstracting the reloading

(defn wrap-reload [other-handler]
(fn [req]
(require 'ringtest.reload-me :reload)
(other-handler req)))
 
(def handler-reload3 (wrap-reload #'handler-reload1))
ring.middleware.reload/wrap-reload

(defn wrap-reload
  "Reload namespaces of modified files before the request is passed to the
supplied handler.
!

Takes the following options:
:dirs - A list of directories that contain the source files.
Defaults to ["src"]."
  [handler & [options]]
  (let [source-dirs (:dirs options ["src"])
        modified-namespaces (ns-tracker source-dirs)]
    (fn [request]
Smarter reloading surrounding the wrapped handler
      (doseq [ns-sym (modified-namespaces)]
        (require ns-sym :reload))
      (handler request))))
ring.server.standalone/add-middleware

This is what “lein ring server” does

(defn- add-middleware [handler options]
  (-> handler
      (add-auto-refresh options)
      (add-auto-reload options)
      (add-stacktraces options)))
Middleware stacks
(middleware1 (middleware2 (middleware3 handler)))
(defn ring-stack [handler]
Our custom “ring” middleware stack
(-> handler
(wrap-reload)
(wrap-stacktrace)))
 
(defonce server-atom (atom nil))
And some custom sever code
 
(defn start [handler]
(swap! server-atom
(fn [server]
(when server (.stop server))
(jetty/run-jetty handler
{:port 8080 :join? false}))))
 
(defn stop []
(swap! server-atom
(fn [server]
(when server (.stop server))
nil)))
ring.middleware.*

•
•
•
•
•
•
•
•
•
•
•
•

wrap-content-type
wrap-cookies
wrap-file-info
wrap-flash
wrap-head 
wrap-keyword-params
wrap-multi-part-params
wrap-nested-params
wrap-not-modified
wrap-params
wrap-resource
wrap-session

Lot’s of middleware to choose from
compojure.handler/api
An existing minimal stack for APIs

(defn api
  "Create a handler suitable for a web API. This adds the following
middleware to your routes:
- wrap-params
- wrap-nested-params
- wrap-keyword-params"
  [routes]
  (-> routes
      wrap-keyword-params
      wrap-nested-params
      wrap-params))
compojure.handler/site
(defn site
  "Create a handler suitable for a standard website. This adds the
following middleware to your routes:
- wrap-session
- wrap-flash
- wrap-cookies
- wrap-multipart-params
- wrap-params
- wrap-nested-params
- wrap-keyword-params
!

A map of options may also be provided. These keys are provided:
:session
- a map of session middleware options
:multipart - a map of multipart-params middleware options"
  [routes & [opts]]
  (-> (api routes)
      (with-opts wrap-multipart-params (:multipart opts))
Extends the API stack
      (wrap-flash)
      (with-opts wrap-session (:session opts))))
Use it, or make your own

(def handler (-> #'app
compojure.handler/site
ring-stack))
 
noir.util.middleware/app-handler
(defn app-handler [app-routes & {:keys [session-options store multipart
middleware access-rules formats]}]
  (letfn [(wrap-middleware-format [handler]
            (if formats (wrap-restful-format handler :formats formats) handler))]
    (-> (apply routes app-routes)
A hook to extend the noir stack
        (wrap-middleware middleware)
        (wrap-request-map)
        (api)
        (wrap-base-url)
        (wrap-middleware-format)
        (with-opts wrap-multipart-params multipart)
        (wrap-access-rules access-rules)
        (wrap-noir-validation)
Lot’s of customization
        (wrap-noir-cookies)
        (wrap-noir-flash)
        (wrap-noir-session
         (update-in session-options [:store] #(or % (memory-store mem)))))))
Routing
Route: (RequestMap → (Option ResponseMap))
Not ring handlers
because they don’t
take a request.

(defn home []
(response/response "Home Page"))
 
(defn foo []
(response/response "Foo Page"))
 
(defn foo-n [n]
(response/response (str "This is Foo#" n)))
 
 
(defn app1 [req]
(condp re-matches (:uri req)
#"/"
(home)
#"/foo"
(foo)
#"/foo/(.*)" :>> #(foo-n (second %))
(response/not-found "Wat")))

Select the page to
show based on URL
Abstract the route dispatch

(defn route-to [handler]
(fn [match]
(if (string? match)
(handler)
(apply handler (rest match)))))
 
(defn app2 [req]
(condp re-matches (:uri req)
#"/"
Cleaner, but still awkward
:>> (route-to home)
 
#"/foo"
:>> (route-to foo)
 
#"/foo/(.*)"
:>> (route-to foo-n)
 
(response/not-found "Wat")))
Include the pattern

(defn my-route [pattern page-fn]
(fn [req]
(if-let [match (re-matches pattern (:uri req))]
((route-to handler) page-fn))))
 
(defn app3 [req]
(let [my-routes [(my-route #"/"
home)
Much cleaner
(my-route #"/foo"
foo)
(my-route #"/foo/(.*)" foo-n)
(my-route #".*"
#(response/not-found "Wat"))]]
(some #(% req) my-routes)))
The first route that responds wins
Routing fn includes method and path

(defn app4 [req]
(let [my-routes [(GET "/" [] (home))
Some extra macro magic
(GET "/foo" [] (foo))
(GET "/foo/:id" [id] (foo-n id))
(route/not-found "Wat")]]
(some #(% req) my-routes)))
 
Some things never change
compojure.core/*
(defn make-route
  "Returns a function that will only call the handler if the method and Clout
route match the request."
  [method route handler]
  (if-method method
    (if-route route
      (fn [request]
        (render (handler request) request)))))
!

(defn- compile-route
  "Compile a route in the form (method path & body) into a function."
  [method route bindings body]
  `(make-route
    ~method ~(prepare-route route)
    (fn [request#]
      (let-request [~bindings request#] ~@body))))
!

(defmacro GET "Generate a GET route."
  [path args & body]
  (compile-route :get path args body))
a collection of routes

(def app5
(routes
(GET "/" [] (home))
(GET "/foo" [] (foo))
(GET "/foo/:id" [id] (foo-n id))
(route/not-found "Wat")))
(defn routing
  "Apply a list of routes to a Ring request map."
  [request & handlers]
  (some #(% request) handlers))
!

(defn routes
  "Create a Ring handler by combining several handlers into one."
  [& handlers]
  #(apply routing % handlers))
(def foo-routes
(routes
(GET "/foo" [] (foo))
(GET "/foo/:id" [id] (foo-n id))))
 
(def app6
(routes
(GET "/" [] (home))
Routing functions nest easily
foo-routes
(route/not-found "Wat")))
(defn foobar-routes [foobar-type]
(routes
(GET "/" [] (str foobar-type " Page"))
(GET "/:id" [id] (str foobar-type "#" id))))
 
(def app7
Not ideal - generates the route fn each call
(routes
(GET "/" [] (home))
(context "/foo" [] (foobar-routes "Foo"))
(context "/bar" [] (foobar-routes "Bar"))
(route/not-found "Wat")))
(defmacro context
  "Give all routes in the form a common path prefix and set of bindings.
!

The following example demonstrates defining two routes with a common
path prefix ('/user/:id') and a common binding ('id'):
!

(context "/user/:id" [id]
(GET "/profile" [] ...)
(GET "/settings" [] ...))"
  [path args & routes]
  `(#'if-route ~(context-route path)
     (#'wrap-context
       (fn [request#]
         (let-request [~args request#]
           (routing request# ~@routes))))))
compojure.response/Renderable
(defprotocol Renderable
  (render [this request]
    "Render the object into a form suitable for the given request map."))
!

(extend-protocol Renderable
  nil …
  String …
  APersistentMap …
  IFn …
  IDeref …
  File …
  ISeq …
  InputStream …
  URL … )

Generate response maps based on types
Norman Richards
orb@nostacktrace.com

Contenu connexe

Tendances

Building Real Time Systems on MongoDB Using the Oplog at Stripe
Building Real Time Systems on MongoDB Using the Oplog at StripeBuilding Real Time Systems on MongoDB Using the Oplog at Stripe
Building Real Time Systems on MongoDB Using the Oplog at Stripe
MongoDB
 

Tendances (20)

Building Real Time Systems on MongoDB Using the Oplog at Stripe
Building Real Time Systems on MongoDB Using the Oplog at StripeBuilding Real Time Systems on MongoDB Using the Oplog at Stripe
Building Real Time Systems on MongoDB Using the Oplog at Stripe
 
Mastering Kotlin Standard Library
Mastering Kotlin Standard LibraryMastering Kotlin Standard Library
Mastering Kotlin Standard Library
 
Building Real Time Systems on MongoDB Using the Oplog at Stripe
Building Real Time Systems on MongoDB Using the Oplog at StripeBuilding Real Time Systems on MongoDB Using the Oplog at Stripe
Building Real Time Systems on MongoDB Using the Oplog at Stripe
 
PostgreSQL Procedural Languages: Tips, Tricks and Gotchas
PostgreSQL Procedural Languages: Tips, Tricks and GotchasPostgreSQL Procedural Languages: Tips, Tricks and Gotchas
PostgreSQL Procedural Languages: Tips, Tricks and Gotchas
 
Gevent rabbit rpc
Gevent rabbit rpcGevent rabbit rpc
Gevent rabbit rpc
 
dotCloud and go
dotCloud and godotCloud and go
dotCloud and go
 
Building Real Time Systems on MongoDB Using the Oplog at Stripe
Building Real Time Systems on MongoDB Using the Oplog at StripeBuilding Real Time Systems on MongoDB Using the Oplog at Stripe
Building Real Time Systems on MongoDB Using the Oplog at Stripe
 
Python postgre sql a wonderful wedding
Python postgre sql   a wonderful weddingPython postgre sql   a wonderful wedding
Python postgre sql a wonderful wedding
 
"PostgreSQL and Python" Lightning Talk @EuroPython2014
"PostgreSQL and Python" Lightning Talk @EuroPython2014"PostgreSQL and Python" Lightning Talk @EuroPython2014
"PostgreSQL and Python" Lightning Talk @EuroPython2014
 
The Ring programming language version 1.5.4 book - Part 40 of 185
The Ring programming language version 1.5.4 book - Part 40 of 185The Ring programming language version 1.5.4 book - Part 40 of 185
The Ring programming language version 1.5.4 book - Part 40 of 185
 
Kotlin Coroutines. Flow is coming
Kotlin Coroutines. Flow is comingKotlin Coroutines. Flow is coming
Kotlin Coroutines. Flow is coming
 
PostgreSQL and PL/Java
PostgreSQL and PL/JavaPostgreSQL and PL/Java
PostgreSQL and PL/Java
 
How to ride a whale
How to ride a whaleHow to ride a whale
How to ride a whale
 
ZeroMQ Is The Answer
ZeroMQ Is The AnswerZeroMQ Is The Answer
ZeroMQ Is The Answer
 
KubeCon EU 2016: Custom Volume Plugins
KubeCon EU 2016: Custom Volume PluginsKubeCon EU 2016: Custom Volume Plugins
KubeCon EU 2016: Custom Volume Plugins
 
Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots Dpilot Source Code With ScreenShots
Dpilot Source Code With ScreenShots
 
Source Code for Dpilot
Source Code for Dpilot Source Code for Dpilot
Source Code for Dpilot
 
リローダブルClojureアプリケーション
リローダブルClojureアプリケーションリローダブルClojureアプリケーション
リローダブルClojureアプリケーション
 
ZeroMQ: Messaging Made Simple
ZeroMQ: Messaging Made SimpleZeroMQ: Messaging Made Simple
ZeroMQ: Messaging Made Simple
 
(BDT401) Big Data Orchestra - Harmony within Data Analysis Tools | AWS re:Inv...
(BDT401) Big Data Orchestra - Harmony within Data Analysis Tools | AWS re:Inv...(BDT401) Big Data Orchestra - Harmony within Data Analysis Tools | AWS re:Inv...
(BDT401) Big Data Orchestra - Harmony within Data Analysis Tools | AWS re:Inv...
 

En vedette

The Logical Burrito - pattern matching, term rewriting and unification
The Logical Burrito - pattern matching, term rewriting and unificationThe Logical Burrito - pattern matching, term rewriting and unification
The Logical Burrito - pattern matching, term rewriting and unification
Norman Richards
 
Ring: Web Apps in Idiomatic Clojure
Ring: Web Apps in Idiomatic ClojureRing: Web Apps in Idiomatic Clojure
Ring: Web Apps in Idiomatic Clojure
Mark McGranaghan
 

En vedette (11)

core.logic introduction
core.logic introductioncore.logic introduction
core.logic introduction
 
The Logical Burrito - pattern matching, term rewriting and unification
The Logical Burrito - pattern matching, term rewriting and unificationThe Logical Burrito - pattern matching, term rewriting and unification
The Logical Burrito - pattern matching, term rewriting and unification
 
ClojuTRE2015: Kekkonen - making your Clojure web APIs more awesome
ClojuTRE2015: Kekkonen - making your Clojure web APIs more awesomeClojuTRE2015: Kekkonen - making your Clojure web APIs more awesome
ClojuTRE2015: Kekkonen - making your Clojure web APIs more awesome
 
Wieldy remote apis with Kekkonen - ClojureD 2016
Wieldy remote apis with Kekkonen - ClojureD 2016Wieldy remote apis with Kekkonen - ClojureD 2016
Wieldy remote apis with Kekkonen - ClojureD 2016
 
Functional web with clojure
Functional web with clojureFunctional web with clojure
Functional web with clojure
 
Frameworkless Web Development in Clojure
Frameworkless Web Development in ClojureFrameworkless Web Development in Clojure
Frameworkless Web Development in Clojure
 
Swaggered web apis in Clojure
Swaggered web apis in ClojureSwaggered web apis in Clojure
Swaggered web apis in Clojure
 
Euroclojure2014: Schema & Swagger - making your Clojure web APIs more awesome
Euroclojure2014: Schema & Swagger - making your Clojure web APIs more awesomeEuroclojure2014: Schema & Swagger - making your Clojure web APIs more awesome
Euroclojure2014: Schema & Swagger - making your Clojure web APIs more awesome
 
Ring: Web Apps in Idiomatic Clojure
Ring: Web Apps in Idiomatic ClojureRing: Web Apps in Idiomatic Clojure
Ring: Web Apps in Idiomatic Clojure
 
Clojure and the Web
Clojure and the WebClojure and the Web
Clojure and the Web
 
Web programming in clojure
Web programming in clojureWeb programming in clojure
Web programming in clojure
 

Similaire à Deconstructing the Functional Web with Clojure

Similaire à Deconstructing the Functional Web with Clojure (20)

Clojure Workshop: Web development
Clojure Workshop: Web developmentClojure Workshop: Web development
Clojure Workshop: Web development
 
InfluxData Platform Future and Vision
InfluxData Platform Future and VisionInfluxData Platform Future and Vision
InfluxData Platform Future and Vision
 
(map Clojure everyday-tasks)
(map Clojure everyday-tasks)(map Clojure everyday-tasks)
(map Clojure everyday-tasks)
 
ClojureScript loves React, DomCode May 26 2015
ClojureScript loves React, DomCode May 26 2015ClojureScript loves React, DomCode May 26 2015
ClojureScript loves React, DomCode May 26 2015
 
(first '(Clojure.))
(first '(Clojure.))(first '(Clojure.))
(first '(Clojure.))
 
Server Side Swift: Vapor
Server Side Swift: VaporServer Side Swift: Vapor
Server Side Swift: Vapor
 
Writing Redis in Python with asyncio
Writing Redis in Python with asyncioWriting Redis in Python with asyncio
Writing Redis in Python with asyncio
 
Hadoop
HadoopHadoop
Hadoop
 
Play vs Rails
Play vs RailsPlay vs Rails
Play vs Rails
 
Terraform 0.9 + good practices
Terraform 0.9 + good practicesTerraform 0.9 + good practices
Terraform 0.9 + good practices
 
Fabric Python Lib
Fabric Python LibFabric Python Lib
Fabric Python Lib
 
Avoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.jsAvoiding Callback Hell with Async.js
Avoiding Callback Hell with Async.js
 
A Tour of Building Web Applications with R Shiny
A Tour of Building Web Applications with R Shiny A Tour of Building Web Applications with R Shiny
A Tour of Building Web Applications with R Shiny
 
Reitit - Clojure/North 2019
Reitit - Clojure/North 2019Reitit - Clojure/North 2019
Reitit - Clojure/North 2019
 
Elixir & Phoenix - fast, concurrent and explicit
Elixir & Phoenix - fast, concurrent and explicitElixir & Phoenix - fast, concurrent and explicit
Elixir & Phoenix - fast, concurrent and explicit
 
Think Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJSThink Async: Asynchronous Patterns in NodeJS
Think Async: Asynchronous Patterns in NodeJS
 
DevOps with Fabric
DevOps with FabricDevOps with Fabric
DevOps with Fabric
 
Customising Your Own Web Framework In Go
Customising Your Own Web Framework In GoCustomising Your Own Web Framework In Go
Customising Your Own Web Framework In Go
 
Grails transactions
Grails   transactionsGrails   transactions
Grails transactions
 
こわくないよ❤️ Playframeworkソースコードリーディング入門
こわくないよ❤️ Playframeworkソースコードリーディング入門こわくないよ❤️ Playframeworkソースコードリーディング入門
こわくないよ❤️ Playframeworkソースコードリーディング入門
 

Plus de Norman Richards

Plus de Norman Richards (6)

An Adventure in Serverless ClojureScript
An Adventure in Serverless ClojureScriptAn Adventure in Serverless ClojureScript
An Adventure in Serverless ClojureScript
 
Logic programming a ruby perspective
Logic programming a ruby perspectiveLogic programming a ruby perspective
Logic programming a ruby perspective
 
Lisp 1.5 - Running history
Lisp 1.5 - Running historyLisp 1.5 - Running history
Lisp 1.5 - Running history
 
Immutant
ImmutantImmutant
Immutant
 
Vert.X mini-talk
Vert.X mini-talkVert.X mini-talk
Vert.X mini-talk
 
The Lambda Calculus and The JavaScript
The Lambda Calculus and The JavaScriptThe Lambda Calculus and The JavaScript
The Lambda Calculus and The JavaScript
 

Dernier

+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
?#DUbAI#??##{{(☎️+971_581248768%)**%*]'#abortion pills for sale in dubai@
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
Joaquim Jorge
 

Dernier (20)

Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Boost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdfBoost Fertility New Invention Ups Success Rates.pdf
Boost Fertility New Invention Ups Success Rates.pdf
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?A Year of the Servo Reboot: Where Are We Now?
A Year of the Servo Reboot: Where Are We Now?
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024Axa Assurance Maroc - Insurer Innovation Award 2024
Axa Assurance Maroc - Insurer Innovation Award 2024
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
Artificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and MythsArtificial Intelligence: Facts and Myths
Artificial Intelligence: Facts and Myths
 
[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf[2024]Digital Global Overview Report 2024 Meltwater.pdf
[2024]Digital Global Overview Report 2024 Meltwater.pdf
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 

Deconstructing the Functional Web with Clojure

  • 1. Ring deconstructing the functional web Norman Richards orb@nostacktrace.com
  • 2. Response Map Request Map HTTP → {} → fn → {} → HTTP Handler FN
  • 3. ring-jetty-adaptor ring handler (def server (ring.adapter.jetty/run-jetty #’handler {:port 8080 :join? false})) ! (.stop server)
  • 4.  ring.adaptor.jetty/proxy-handler (defn- proxy-handler   "Returns an Jetty Handler implementation for 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 response response-map)           (.setHandled base-request true))))))
  • 5. ring.util.servlet/build-request-map (defn build-request-map   "Create the request map from the HttpServletRequest object."   [^HttpServletRequest request]   {:server-port (.getServerPort request)    :server-name (.getServerName request)    :remote-addr (.getRemoteAddr request)    :uri (.getRequestURI request)    :query-string (.getQueryString request)    :scheme (keyword (.getScheme request))    :request-method (keyword (.toLowerCase (.getMethod request)))    :headers (get-headers request)    :content-type (.getContentType request)    :content-length (get-content-length request)    :character-encoding (.getCharacterEncoding request)    :ssl-client-cert (get-client-cert request)    :body (.getInputStream request)}) not functional :(
  • 6. ring.util.servlet/update-servlet-response (defn update-servlet-response   "Update the HttpServletResponse using a response map."   [^HttpServletResponse response, {:keys [status headers body]}]   (when-not response     (throw (Exception. "Null response given.")))   (when status     (set-status response status))   (doto response     (set-headers headers)     (set-body body)))
  • 8. (defn handler-nil [req] {:body nil}) response body
  • 9. ring.util.servlet/set-body (defn- set-body   "Update a HttpServletResponse body with a String, ISeq, File or InputStream."   [^HttpServletResponse response, body]   (cond     (string? body)       (with-open [writer (.getWriter response)]         (.print writer body))     (seq? body)   ;; …     (instance? File body)   ;; …     (nil? body)       nil     :else       (throw (Exception. ^String (format "Unrecognized body: %s" body)))))
  • 11. (defn handler-file [req] {:body (clojure.java.io/file "info.txt")})
  • 12. (defn handler-status [req] {:status 402 :headers {"Location" "bitcoin:1G9TyAaKrfJn7q4Vrr15DscLXFSRPxBFaH?amount=.001"}}) Handlers can return status code and headers
  • 13. ring.util.response/* A few response helpers (defn response   "Returns a skeletal Ring response with the given body, status of 200, and no headers."   [body]   {:status 200    :headers {}    :body body}) ! (defn not-found   "Returns a 404 'not found' response."   [body]   {:status 404    :headers {}    :body body}) ! (defn redirect   "Returns a Ring response for an HTTP 302 redirect."   [url]   {:status 302    :headers {"Location" url}    :body ""})
  • 14. (defn handler [req] (response/response "Hello, world!”)) ! (defn handler [req] (response/redirect "http://lmgtfy.com/?q=http+redirect")) ! (defn handler [req] (response/resource-response "hello.txt"))
  • 15. Building up a response (defn handler [req] (-> (response/response "") (response/status 302) (response/header "Location" "http://www.google.com")))
  • 17. (defn handler-reload1 [req] (response/response (reload-me/some-work))) A function we’d like to be reloaded if it changes
  • 18. (defn handler-reload2 [req] (require 'ringtest.reload-me :reload) (handler-reload1 req)) The original handler is wrapped
  • 19. Abstracting the reloading (defn wrap-reload [other-handler] (fn [req] (require 'ringtest.reload-me :reload) (other-handler req)))   (def handler-reload3 (wrap-reload #'handler-reload1))
  • 20. ring.middleware.reload/wrap-reload (defn wrap-reload   "Reload namespaces of modified files before the request is passed to the supplied handler. ! Takes the following options: :dirs - A list of directories that contain the source files. Defaults to ["src"]."   [handler & [options]]   (let [source-dirs (:dirs options ["src"])         modified-namespaces (ns-tracker source-dirs)]     (fn [request] Smarter reloading surrounding the wrapped handler       (doseq [ns-sym (modified-namespaces)]         (require ns-sym :reload))       (handler request))))
  • 21. ring.server.standalone/add-middleware This is what “lein ring server” does (defn- add-middleware [handler options]   (-> handler       (add-auto-refresh options)       (add-auto-reload options)       (add-stacktraces options)))
  • 23. (defn ring-stack [handler] Our custom “ring” middleware stack (-> handler (wrap-reload) (wrap-stacktrace)))   (defonce server-atom (atom nil)) And some custom sever code   (defn start [handler] (swap! server-atom (fn [server] (when server (.stop server)) (jetty/run-jetty handler {:port 8080 :join? false}))))   (defn stop [] (swap! server-atom (fn [server] (when server (.stop server)) nil)))
  • 25. compojure.handler/api An existing minimal stack for APIs (defn api   "Create a handler suitable for a web API. This adds the following middleware to your routes: - wrap-params - wrap-nested-params - wrap-keyword-params"   [routes]   (-> routes       wrap-keyword-params       wrap-nested-params       wrap-params))
  • 26. compojure.handler/site (defn site   "Create a handler suitable for a standard website. This adds the following middleware to your routes: - wrap-session - wrap-flash - wrap-cookies - wrap-multipart-params - wrap-params - wrap-nested-params - wrap-keyword-params ! A map of options may also be provided. These keys are provided: :session - a map of session middleware options :multipart - a map of multipart-params middleware options"   [routes & [opts]]   (-> (api routes)       (with-opts wrap-multipart-params (:multipart opts)) Extends the API stack       (wrap-flash)       (with-opts wrap-session (:session opts))))
  • 27. Use it, or make your own (def handler (-> #'app compojure.handler/site ring-stack))  
  • 28. noir.util.middleware/app-handler (defn app-handler [app-routes & {:keys [session-options store multipart middleware access-rules formats]}]   (letfn [(wrap-middleware-format [handler]             (if formats (wrap-restful-format handler :formats formats) handler))]     (-> (apply routes app-routes) A hook to extend the noir stack         (wrap-middleware middleware)         (wrap-request-map)         (api)         (wrap-base-url)         (wrap-middleware-format)         (with-opts wrap-multipart-params multipart)         (wrap-access-rules access-rules)         (wrap-noir-validation) Lot’s of customization         (wrap-noir-cookies)         (wrap-noir-flash)         (wrap-noir-session          (update-in session-options [:store] #(or % (memory-store mem)))))))
  • 29. Routing Route: (RequestMap → (Option ResponseMap))
  • 30. Not ring handlers because they don’t take a request. (defn home [] (response/response "Home Page"))   (defn foo [] (response/response "Foo Page"))   (defn foo-n [n] (response/response (str "This is Foo#" n)))     (defn app1 [req] (condp re-matches (:uri req) #"/" (home) #"/foo" (foo) #"/foo/(.*)" :>> #(foo-n (second %)) (response/not-found "Wat"))) Select the page to show based on URL
  • 31. Abstract the route dispatch (defn route-to [handler] (fn [match] (if (string? match) (handler) (apply handler (rest match)))))   (defn app2 [req] (condp re-matches (:uri req) #"/" Cleaner, but still awkward :>> (route-to home)   #"/foo" :>> (route-to foo)   #"/foo/(.*)" :>> (route-to foo-n)   (response/not-found "Wat")))
  • 32. Include the pattern (defn my-route [pattern page-fn] (fn [req] (if-let [match (re-matches pattern (:uri req))] ((route-to handler) page-fn))))   (defn app3 [req] (let [my-routes [(my-route #"/" home) Much cleaner (my-route #"/foo" foo) (my-route #"/foo/(.*)" foo-n) (my-route #".*" #(response/not-found "Wat"))]] (some #(% req) my-routes))) The first route that responds wins
  • 33. Routing fn includes method and path (defn app4 [req] (let [my-routes [(GET "/" [] (home)) Some extra macro magic (GET "/foo" [] (foo)) (GET "/foo/:id" [id] (foo-n id)) (route/not-found "Wat")]] (some #(% req) my-routes)))   Some things never change
  • 34. compojure.core/* (defn make-route   "Returns a function that will only call the handler if the method and Clout route match the request."   [method route handler]   (if-method method     (if-route route       (fn [request]         (render (handler request) request))))) ! (defn- compile-route   "Compile a route in the form (method path & body) into a function."   [method route bindings body]   `(make-route     ~method ~(prepare-route route)     (fn [request#]       (let-request [~bindings request#] ~@body)))) ! (defmacro GET "Generate a GET route."   [path args & body]   (compile-route :get path args body))
  • 35. a collection of routes (def app5 (routes (GET "/" [] (home)) (GET "/foo" [] (foo)) (GET "/foo/:id" [id] (foo-n id)) (route/not-found "Wat")))
  • 36. (defn routing   "Apply a list of routes to a Ring request map."   [request & handlers]   (some #(% request) handlers)) ! (defn routes   "Create a Ring handler by combining several handlers into one."   [& handlers]   #(apply routing % handlers))
  • 37. (def foo-routes (routes (GET "/foo" [] (foo)) (GET "/foo/:id" [id] (foo-n id))))   (def app6 (routes (GET "/" [] (home)) Routing functions nest easily foo-routes (route/not-found "Wat")))
  • 38. (defn foobar-routes [foobar-type] (routes (GET "/" [] (str foobar-type " Page")) (GET "/:id" [id] (str foobar-type "#" id))))   (def app7 Not ideal - generates the route fn each call (routes (GET "/" [] (home)) (context "/foo" [] (foobar-routes "Foo")) (context "/bar" [] (foobar-routes "Bar")) (route/not-found "Wat")))
  • 39. (defmacro context   "Give all routes in the form a common path prefix and set of bindings. ! The following example demonstrates defining two routes with a common path prefix ('/user/:id') and a common binding ('id'): ! (context "/user/:id" [id] (GET "/profile" [] ...) (GET "/settings" [] ...))"   [path args & routes]   `(#'if-route ~(context-route path)      (#'wrap-context        (fn [request#]          (let-request [~args request#]            (routing request# ~@routes))))))
  • 40. compojure.response/Renderable (defprotocol Renderable   (render [this request]     "Render the object into a form suitable for the given request map.")) ! (extend-protocol Renderable   nil …   String …   APersistentMap …   IFn …   IDeref …   File …   ISeq …   InputStream …   URL … ) Generate response maps based on types