0% found this document useful (0 votes)
23 views5 pages

Clojure Middleware Explained

Uploaded by

eowug
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views5 pages

Clojure Middleware Explained

Uploaded by

eowug
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Middleware in Clojure

This work is licensed under a Creative Commons Attribution 3.0 Unported License
([Link] (including images & stylesheets). The source is available
on Github ([Link]

What is Middleware?
Middleware in Clojure is a common design pattern for threading a request through a series of functions
designed to operate on it as well as threading the response through the same series of functions.

Middleware is used in many Clojure projects such as Ring ([Link]


Compojure ([Link] reitit
([Link]
([Link] and Kit ([Link]

The client function


The base of all middleware in Clojure is the client function, which takes a request object (usually a
Clojure map) and returns a response object (also usually a Clojure map).

For example, let's use a client function that pulls some keys out of a map request and does an HTTP
GET on a site:

(ns [Link]
(:require [[Link] :as http]))

(defn client [request]


(http/get (:site request) (:options request)))

To use the client method, call it like so (response shortened to fit here):

(client {:site "[Link] :options {}})


;; ⇒ {:status 200, :headers {...}, :request-time 3057, :body "..."}

Now that a client function exists, middleware can be wrapped around it to change the request, the
response, or both.

Let's start with a middleware function that doesn't do anything. We'll call it the no-op middleware:

;; It is standard convention to name middleware wrap-<something>


(defn wrap-no-op
;; the wrapping function takes a client function to be used...
[client-fn]
;; ...and returns a function that takes a request...
(fn [request]
;; ...that calls the client function with the request
(client-fn request)))
So how is this middleware used? First, it must be 'wrapped' around the existing client function:

(def new-client (wrap-no-op client))

;; Now new-client can be used just like the client function:


(new-client {:site "[Link] :options {}})
;; ⇒ {:status 200, :headers {...}, :request-time 3057, :body "..."}

It works! Now it's not very exiting because it doesn't do anything yet, so let's add another middleware
wrapper that does something more exiting.

Let's add a middleware function that automatically changes all "HTTP" requests into "HTTPS" requests.
Again, we need a function that returns another function, so we can end up with a new method to call:

;; assume (require '[[Link] :as str])


(defn wrap-https
[client-fn]
(fn [request]
(let [site (:site request)
new-site (str/replace site "http:" "https:")
new-request (assoc request :site new-site)]
(client-fn new-request))))

This could be written more concisely using -> and update , if you prefer:

;; assume (require '[[Link] :as str])


(defn wrap-https
[client-fn]
(fn [request]
(-> request
(update :site #(str/replace % "http:" "https:"))
(client-fn))))

The wrap-https middleware can be tested again by creating a new client function:

(def https-client (wrap-https client))

;; Notice the :trace-redirects key shows that HTTPS was used instead
;; of HTTP
(https-client {:site "[Link] :options {}})
;; ⇒ {:trace-redirects ["[Link]
;; :status 200,
;; :headers {...},
;; :request-time 3057,
;; :body "..."}

Middleware can be tested independently of the client function by providing the identity function (or any
other function that returns a map). For example, we can see the wrap-https middleware returns the
clojure map with the :site changed from 'http' to 'https':
((wrap-https identity) {:site "[Link]
;; ⇒ {:site "[Link]

Combining middleware
In the previous example, we showed how to create and use middleware, but what about using multiple
middleware functions? Let's define one more middleware so we have a total of three to work with. Here's
the source for a middleware function that adds the current data to the response map:

(defn wrap-add-date
[client]
(fn [request]
(let [response (client request)]
(assoc response :date ([Link].)))))

And again, we can test it without using any other functions using identity as the client function:

((wrap-add-date identity) {})


;; ⇒ {:date #inst "2023-11-12T[Link].081-00:00"}

Middleware is useful on its own, but where it becomes truly more useful is in combining middleware
together. Here's what a new client function looks like combining all the middleware:

(def my-client (wrap-add-date (wrap-https (wrap-no-op client))))

(my-client {:site "[Link]


;; ⇒ {:date #inst "2023-11-12T[Link].616-00:00",
;; :cookies {...},
;; :trace-redirects ["[Link]
;; :request-time 1634,
;; :status 200,
;; :headers {...},
;; :body "..."}

(The response map has been edited to take less space where you see ... )

Here we can see that the wrap-https middleware has successfully turned the request for
[Link] into one for [Link] additionally the wrap-add-date middleware
has added the :date key with the date the request happened. (the wrap-no-op middleware did execute,
but since it didn't do anything, there's no output to tell)

This is a good start, but adding middleware can be expressed in a much cleaner and clearer way by using
Clojure's threading macro, -> . The my-client definition from above can be expressed like this:
(def my-client
(-> client
wrap-no-op
wrap-https
wrap-add-date))

(my-client {:site "[Link]


;; ⇒ {:date #inst "2023-11-12T[Link].389-00:00",
;; :cookies {...},
;; :trace-redirects ["[Link]
;; :request-time 1630,
;; :status 200,
;; :headers {...},
;; :body "..."}

Something else to keep in mind is that middleware expressed in this way will be executed from the bottom
up, so in this case, wrap-add-date will call wrap-https , which in turn calls wrap-no-op , which finally
calls the client function.

If you have a lot of middleware to combine, it can be easier to use reduce and a vector of middleware
functions. See how clj-http does this for its default stack of middleware:

(defn wrap-request
"Returns a batteries-included HTTP request function corresponding to the given
core client. See default-middleware for the middleware wrappers that are used
by default"
[request]
(reduce (fn wrap-request* [request middleware]
(middleware request))
request
default-middleware))

See clj-http 's [Link] ([Link]


http/blob/d92be158230e8094436f415324d96f2bd7cf95f7/src/clj_http/[Link]#L1125-L1166) for the full
source.

« Working with Files and Directories in Clojure (/articles/cookbooks/files_and_directories/) || Parsing XML


in Clojure » (/articles/cookbooks/parsing_xml_with_zippers/)

Links
About (/articles/about/)
Table of Contents (/articles/content/)
Getting Started (/articles/tutorials/getting_started/)
Introduction to Clojure (/articles/tutorials/introduction/)
Clojure Editors (/articles/tutorials/editors/)
Clojure Community (/articles/ecosystem/community/)
Basic Web Development (/articles/tutorials/basic_web_development/)
Language: Functions (/articles/language/functions/)
Language: [Link] (/articles/language/core_overview/)
Language: Collections and Sequences (/articles/language/collections_and_sequences/)
Language: Namespaces (/articles/language/namespaces/)
Language: Java Interop (/articles/language/interop/)
Language: Polymorphism (/articles/language/polymorphism/)
Language: Concurrency and Parallelism (/articles/language/concurrency_and_parallelism/)
Language: Macros (/articles/language/macros/)
Language: Laziness (/articles/language/laziness/)
Language: Glossary (/articles/language/glossary/)
Ecosystem: Library Development and Distribution (/articles/ecosystem/libraries_authoring/)
Ecosystem: Web Development (/articles/ecosystem/web_development/)
Ecosystem: Generating Documentation (/articles/ecosystem/generating_documentation/)
Building Projects: [Link] and the Clojure CLI (/articles/cookbooks/cli_build_projects/)
Data Structures (/articles/cookbooks/data_structures/)
Strings (/articles/cookbooks/strings/)
Mathematics with Clojure (/articles/cookbooks/math/)
Date and Time (/articles/cookbooks/date_and_time/)
Working with Files and Directories in Clojure (/articles/cookbooks/files_and_directories/)
Middleware in Clojure
Parsing XML in Clojure (/articles/cookbooks/parsing_xml_with_zippers/)
Growing a DSL with Clojure (/articles/cookbooks/growing_a_dsl_with_clojure/)

Copyright © 2024 Multiple Authors


Powered by Cryogen ([Link]

You might also like