emacs.d/clones/lisp/clojure-doc.org/articles/cookbooks/middleware/index.html

326 lines
18 KiB
HTML

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta charset="utf-8"/>
<title>Clojure Guides: Middleware in Clojure</title>
<meta name="description" content="This work is licensed under a Creative Commons
Attribution 3.0 Unported License (including images &amp;
stylesheets). The source is available on
Github.What is Middleware?">
<meta property="og:description" content="This work is licensed under a Creative Commons
Attribution 3.0 Unported License (including images &amp;
stylesheets). The source is available on
Github.What is Middleware?">
<meta property="og:url" content="https://clojure-doc.github.io/articles/cookbooks/middleware/" />
<meta property="og:title" content="Middleware in Clojure" />
<meta property="og:type" content="article" />
<link rel="canonical" href="https://clojure-doc.github.io/articles/cookbooks/middleware/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://fonts.googleapis.com/css?family=Alegreya:400italic,700italic,400,700" rel="stylesheet"
type="text/css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/css/bootstrap.min.css">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.7.0/styles/default.min.css">
<link href="../../../css/screen.css" rel="stylesheet" type="text/css" />
</head>
<body>
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="../../../index.html">Clojure Guides</a>
</div>
<div id="navbar" class="navbar-collapse collapse">
<ul class="nav navbar-nav navbar-right">
<li ><a href="../../../index.html">Home</a></li>
<li><a href="https://github.com/clojure-doc/clojure-doc.github.io">Contribute</a></li>
</ul>
</div><!--/.nav-collapse -->
</div><!--/.container-fluid -->
</nav>
<div class="container">
<div class="row">
<div class="col-lg-9">
<div id="content">
<div id="custom-page">
<div id="page-header">
<h2>Middleware in Clojure</h2>
</div>
<p>This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by/3.0/">Creative Commons
Attribution 3.0 Unported License</a> (including images &amp;
stylesheets). The source is available <a href="https://github.com/clojure-doc/clojure-doc.github.io">on
Github</a>.</p><h2 id="what-is-middleware">What is Middleware?</h2><p>Middleware in Clojure is a common design pattern for threading a
<em>request</em> through a series of functions designed to operate on it as
well as threading the <em>response</em> through the same series of functions.</p><p>Middleware is used in many Clojure projects such as
<a href="https://github.com/mmcgrana/ring">Ring</a>,
<a href="https://github.com/dakrone/clj-http">clj-http</a> and
<a href="https://clojure-doc.org/articles/cookbooks/middleware/TODO">something else here</a>.</p><h2 id="the-client-function">The <code>client</code> function</h2><p>The base of all middleware in Clojure is the <code>client</code> function, which
takes a request object (usually a Clojure map) and returns a response
object (also usually a Clojure map).</p><p>For example, let's use a <code>client</code> function that pulls some keys out of
a map request and does an HTTP GET on a site:</p><pre><code class="clojure">(ns middleware.example
(:require [clj-http.client :as http]))
(defn client [request]
(http/get (:site request) (:options request)))
</code></pre><p>To use the client method, call it like so (response shortened to fit
here):</p><pre><code class="clojure">(client {:site "http://www.aoeu.com" :options {}})
;; ⇒ {:status 200, :headers {...}, :request-time 3057, :body "..."}
</code></pre><p>Now that a client function exists, middleware can be wrapped around it
to change the <em>request</em>, the <em>response</em>, or both.</p><p>Let's start with a middleware function that doesn't do anything. We'll
call it the <code>no-op</code> middleware:</p><pre><code class="clojure">;; It is standard convention to name middleware wrap-&lt;something&gt;
(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)))
</code></pre><p>So how is this middleware used? First, it must be 'wrapped' around the
existing client function:</p><pre><code class="clojure">(def new-client (wrap-no-op client))
;; Now new-client can be used just like the client function:
(new-client {:site "http://www.aoeu.com" :options {}})
;; ⇒ {:status 200, :headers {...}, :request-time 3057, :body "..."}
</code></pre><p>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.</p><p>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:</p><pre><code class="clojure">(defn wrap-https
[client-fn]
(fn [request]
(let [site (:site request)
new-site (.replaceAll site "http:" "https:")
new-request (assoc request :site new-site)]
(client-fn new-request))))
</code></pre><p>The <code>wrap-https</code> middleware can be tested again by creating a new
client function:</p><pre><code class="clojure">(def https-client (wrap-https client))
;; Notice the :trace-redirects key shows that HTTPS was used instead
;; of HTTP
(https-client {:site "http://www.google.com" :options {}})
;; ⇒ {:trace-redirects ["https://www.google.com"],
;; :status 200,
;; :headers {...},
;; :request-time 3057,
;; :body "..."}
</code></pre><p>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 <code>wrap-https</code> middleware returns the
clojure map with the :site changed from 'http' to 'https':</p><pre><code class="clojure">((wrap-https identity) {:site "http://www.example.com"})
;; ⇒ {:site "https://www.example.com"}
</code></pre><h2 id="combining-middleware">Combining middleware</h2><p>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:</p><pre><code class="clojure">(defn wrap-add-date
[client]
(fn [request]
(let [response (client request)]
(assoc response :date (java.util.Date.)))))
</code></pre><p>And again, we can test it without using any other functions using
<code>identity</code> as the client function:</p><pre><code class="clojure">((wrap-add-date identity) {})
;; ⇒ {:date #inst "2012-11-09T12:41:05.171-00:00"}
</code></pre><p>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:</p><pre><code class="clojure">(def my-client (wrap-add-date (wrap-https (wrap-no-op client))))
(my-client {:site "http://www.google.com"})
;; ⇒ {:date #inst "2012-11-09T12:43:39.451-00:00",
;; :cookies {...},
;; :trace-redirects ["https://www.google.com/"],
;; :request-time 1634,
;; :status 200,
;; :headers {...},
;; :body "..."}
</code></pre><p>(The response map has been edited to take less space where you see
'...')</p><p>Here we can see that the <code>wrap-https</code> middleware has successfully
turned the request for http://www.google.com into one for
https://www.google.com, additionally the <code>wrap-add-date</code> middleware
has added the :date key with the date the request happened. (the
<code>wrap-no-op</code> middleware did execute, but since it didn't do anything,
there's no output to tell)</p><p>This is a good start, but adding middleware can be expressed in a much
cleaner and clearer way by using Clojure's threading macro, <code>-&gt;</code>. The
<code>my-client</code> definition from above can be expressed like this:</p><pre><code class="clojure">(def my-client
(-&gt; client
wrap-no-op
wrap-https
wrap-add-date))
(my-client {:site "http://www.google.com"})
;; ⇒ {:date #inst "2012-11-09T12:47:32.130-00:00",
;; :cookies {...},
;; :trace-redirects ["https://www.google.com/"],
;; :request-time 1630,
;; :status 200,
;; :headers {...},
;; :body "..."}
</code></pre><p>Something else to keep in mind is that middleware expressed in this
way will be executed <em>from the bottom up</em>, so in this case,
<code>wrap-add-date</code> will call <code>wrap-https</code>, which in turn calls
<code>wrap-no-op</code>, which finally calls the <code>client</code> function.</p><p>For an example of combining a large amount of middleware, see
<a href="https://github.com/dakrone/clj-http/blob/5534950b5ed48e3bc7285f0e956444ea832399da/src/clj_http/client.clj#L542-567">clj-http's
client.clj</a>
file</p>
<div id="prev-next">
<a href="../files_and_directories/index.html">&laquo; Working with Files and Directories in Clojure</a>
||
<a href="../../ecosystem/java_jdbc/home.html">java.jdbc - Getting Started &raquo;</a>
</div>
</div>
</div>
</div>
<div class="col-md-3">
<div id="sidebar">
<h3>Links</h3>
<ul id="links">
<li><a href="../../about/index.html">About</a></li>
<li><a href="../../content/index.html">Table of Contents</a></li>
<li><a href="../../tutorials/getting_started/index.html">Getting Started with Clojure</a></li>
<li><a href="../../tutorials/introduction/index.html">Introduction to Clojure</a></li>
<li><a href="../../tutorials/emacs/index.html">Clojure with Emacs</a></li>
<li><a href="../../tutorials/vim_fireplace/index.html">Clojure with Vim and fireplace.vim</a></li>
<li><a href="../../tutorials/eclipse/index.html">Starting with Eclipse and Counterclockwise For Clojure Development</a></li>
<li><a href="../../tutorials/basic_web_development/index.html">Basic Web Development</a></li>
<li><a href="../../tutorials/parsing_xml_with_zippers/index.html">Parsing XML in Clojure</a></li>
<li><a href="../../tutorials/growing_a_dsl_with_clojure/index.html">Growing a DSL with Clojure</a></li>
<li><a href="../../language/core_overview/index.html">Overview of clojure.core, the standard Clojure library</a></li>
<li><a href="../../language/namespaces/index.html">Clojure Namespaces and Vars</a></li>
<li><a href="../../language/collections_and_sequences/index.html">Collections and Sequences in Clojure</a></li>
<li><a href="../../language/functions/index.html">Functions in Clojure</a></li>
<li><a href="../../language/laziness/index.html">Laziness in Clojure</a></li>
<li><a href="../../language/interop/index.html">Clojure interoperability with Java</a></li>
<li><a href="../../language/macros/index.html">Clojure Macros and Metaprogramming</a></li>
<li><a href="../../language/polymorphism/index.html">Polymorphism in Clojure: Protocols and Multimethods</a></li>
<li><a href="../../language/concurrency_and_parallelism/index.html">Concurrency and Parallelism in Clojure</a></li>
<li><a href="../../language/glossary/index.html">Clojure Terminology Guide</a></li>
<li><a href="../../ecosystem/libraries_directory/index.html">A Directory of Clojure Libraries</a></li>
<li><a href="../../ecosystem/libraries_authoring/index.html">Library Development and Distribution</a></li>
<li><a href="../../ecosystem/generating_documentation/index.html">Generating Documentation</a></li>
<li><a href="../../ecosystem/data_processing/index.html">Data Processing (Help Wanted)</a></li>
<li><a href="../../ecosystem/web_development/index.html">Web Development (Overview)</a></li>
<li><a href="../../ecosystem/maven/index.html">How to use Maven to build Clojure projects</a></li>
<li><a href="../../ecosystem/community/index.html">Clojure Community</a></li>
<li><a href="../../ecosystem/user_groups/index.html">Clojure User Groups</a></li>
<li><a href="../../ecosystem/running_cljug/index.html">Running a Clojure User Group</a></li>
<li><a href="../../ecosystem/books/index.html">Books about Clojure and ClojureScript</a></li>
<li><a href="../data_structures/index.html">Data Structures (Help wanted)</a></li>
<li><a href="../strings/index.html">Strings</a></li>
<li><a href="../math/index.html">Mathematics with Clojure</a></li>
<li><a href="../date_and_time/index.html">Date and Time (Help wanted)</a></li>
<li><a href="../files_and_directories/index.html">Working with Files and Directories in Clojure</a></li>
<li><a href="index.html">Middleware in Clojure</a></li>
<li><a href="../../ecosystem/java_jdbc/home.html">java.jdbc - Getting Started</a></li>
<li><a href="../../ecosystem/java_jdbc/using_sql.html">java.jdbc - Manipulating data with SQL</a></li>
<li><a href="../../ecosystem/java_jdbc/using_ddl.html">java.jdbc - Using DDL and Metadata</a></li>
<li><a href="../../ecosystem/java_jdbc/reusing_connections.html">java.jdbc - How to reuse database connections</a></li>
<li><a href="../../ecosystem/core_typed/home/index.html">core.typed - User Documentation Home</a></li>
<li><a href="../../ecosystem/core_typed/user_documentation/index.html">core.typed - User Documentation</a></li>
<li><a href="../../ecosystem/core_typed/rationale/index.html">core.typed - Rationale</a></li>
<li><a href="../../ecosystem/core_typed/quick_guide.html">core.typed - Quick Guide</a></li>
<li><a href="../../ecosystem/core_typed/start/introduction_and_motivation/index.html">core.typed - Getting Started: Introduction and Motivation</a></li>
<li><a href="../../ecosystem/core_typed/types/index.html">core.typed - Types</a></li>
<li><a href="../../ecosystem/core_typed/start/annotations/index.html">core.typed - Annotations</a></li>
<li><a href="../../ecosystem/core_typed/poly_fn/index.html">core.typed - Polymorphic Functions</a></li>
<li><a href="../../ecosystem/core_typed/filters/index.html">core.typed - Filters</a></li>
<li><a href="../../ecosystem/core_typed/mm_protocol_datatypes/index.html">core.typed - Protocols</a></li>
<li><a href="../../ecosystem/core_typed/loops/index.html">core.typed - Looping constructs</a></li>
<li><a href="../../ecosystem/core_typed/function_types/index.html">core.typed - Functions</a></li>
<li><a href="../../ecosystem/core_typed/limitations/index.html">core.typed - Limitations</a></li>
</ul>
</div>
</div>
</div>
<footer>Copyright &copy; 2021 Multiple Authors
<p style="text-align: center;">Powered by <a href="http://cryogenweb.org">Cryogen</a></p></footer>
</div>
<script src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.0/js/bootstrap.min.js"></script>
<script src="../../../js/highlight.pack.js" type="application/javascript"></script>
<script>hljs.initHighlightingOnLoad();</script>
</body>
</html>