TinyWeb in Clojure

Now let’s take TinyWeb and translate it into Clojure. This is going to be a bigger leap than the translation from Java to Scala, so we’ll take it slowly.

The most obvious difference between Clojure and Java is the syntax. It’s very different than the C-inspired syntax found in most modern programming languages. This isn’t incidental: the syntax enables one of Clojure’s most powerful features, macros, which we’ll cover in Pattern 21, Domain-Specific Language.

A Gentle Introduction to Clojure

For now let’s just have a gentle introduction. Clojure uses prefix syntax, which just means that the function name comes before the function arguments in a function call. Here we call the count function to get the size of a vector, one of Clojure’s immutable data structures:

 
=> (count [1 2 3 4])
 
4

Like Scala, Clojure has excellent interoperability with existing Java code. Calling a method on a Java class looks almost exactly like calling a Clojure function; you just need to prepend the method name with a period and put it before the class instance rather than after. For instance, this is how we call the length method on an instance of a Java String:

 
=> (.length "Clojure")
 
7

Instead of organizing Clojure code into objects and methods in Java or into objects, methods, and functions in Scala, Clojure code is organized into functions and namespaces. Our Clojure version of TinyWeb is based on models, views, controllers, and filters, just like the Java and Scala versions; however, these components will take quite a different form.

Our views, controllers, and filter codes are simply functions, and our models are maps. To tie everything together, we use a function named TinyWeb, which takes in all our components and returns a function that takes in an HTTP request, runs it through the filters, and then routes it to the proper controller and view.

Controllers in Clojure

Let’s start our look at the Clojure code with the controllers. Below, we implement a simple controller that takes the body of an incoming HTTP request and uses it to set a name in a model. For this first iteration, we’ll use the same HttpRequest as our Java code. We’ll change it to be more idiomatic Clojure later on:

ClojureExamples/src/mbfpp/oo/tinyweb/stepone.clj
 
(​ns​ mbfpp.oo.tinyweb.stepone
 
(:import (com.mblinn.oo.tinyweb HttpRequest HttpRequest$Builder)))
 
(​defn​ test-controller [http-request]
 
{:name (​.​getBody http-request)})
 
(​def​ test-builder (HttpRequest$Builder/newBuilder))
 
(​def​ test-http-request (​..​ test-builder (body ​"Mike"​) (path ​"/say-hello"​) build))
 
(​defn​ test-controller-with-map [http-request]
 
{:name (http-request :body)})

Let’s take a look at this code piece by piece, starting with the namespace declaration.

ClojureExamples/src/mbfpp/oo/tinyweb/stepone.clj
 
(​ns​ mbfpp.oo.tinyweb.stepone
 
(:import (com.mblinn.oo.tinyweb HttpRequest HttpRequest$Builder)))

Here we define a namespace called mbfpp.oo.tinyweb.stepone. A namespace is simply a collection of functions that form a library that can be imported in full or in part by another namespace.

As part of the definition, we import a couple of Java classes, HttpRequest and HttpRequest$Builder. The second one might look a little strange, but it’s just the full name for the static inner Builder class we created as part of our HttpRequest. Clojure doesn’t have any special syntax for referring to static inner classes, so we need to use the full class name.

The keyword :import is an example of a Clojure keyword. A keyword is just an identifier that provides very fast equality checks and is always prepended with a colon. Here we’re using the :import keyword to indicate what classes should be imported into the namespace we’ve just declared, but keywords have many other uses. They’re often used as keys in a map, for instance.

Now let’s take a look at our controller, which takes an HttpRequest from the original Java solution and produces a Clojure map as a model:

ClojureExamples/src/mbfpp/oo/tinyweb/stepone.clj
 
(​defn​ test-controller [http-request]
 
{:name (​.​getBody http-request)})

Here we call the getBody method on the HttpRequest to get the body of the request, and we use it to create a map with a single key-value pair. The key is the keyword :name, and the value is the String body of the HttpRequest.

Before we move on, let’s look at Clojure maps in greater detail. In Clojure, it’s common to use maps to pass around data. The syntax for creating a map in Clojure is to enclose key-value pairs inside curly braces. For instance, here we’re creating a map with two key-value pairs. The first key is the keyword :name, and the value is the String "Mike". The second is the keyword :sex, and the value is another keyword, :male>:

 
=> {:name "Mike" :sex :male}
 
{:name "Mike" :sex :male}

Maps in Clojure are functions of their keys. This means that we can call a map as a function, passing a key we expect to be in the map, and the map will return the value. If the key isn’t in the map, nil is returned, as the code below shows:

 
=> (def test-map {:name "Mike"})
 
#'mbfpp.oo.tinyweb.stepone/test-map
 
=> (test-map :name)
 
"Mike"
 
=> (test-map :orange)
 
nil

Keywords in Clojure are also functions. When they are passed a map, they will look themselves up in it, as in the following snippet, which shows the most common way to look up a value from a map:

 
=> (def test-map {:name "Mike"})
 
#'mbfpp.oo.tinyweb.stepone/test-map
 
=> (:name test-map)
 
"Mike"
 
=> (:orange test-map)
 
nil

Now let’s create some test data. Below, we create an HttpRequest$Builder and use it to create a new HttpRequest:

ClojureExamples/src/mbfpp/oo/tinyweb/stepone.clj
 
(​def​ test-builder (HttpRequest$Builder/newBuilder))
 
(​def​ test-http-request (​..​ test-builder (body ​"Mike"​) (path ​"/say-hello"​) build))

This code features two more Clojure/Java interop features. First, the forward slash lets us call a static method or reference a static variable on a class. So the snippet (HttpRequest$Builder/newBuilder) is calling the newBuilder method on the HttpRequest$Builder class. As another example, we can use this syntax to parse an integer from a String using the parseInt method on the Integer class:

 
=> (Integer/parseInt "42")
 
42

Next up is the .. macro, a handy interop feature that makes calling a series of methods on a Java object easy. It works by taking the first argument to .. and threading it through calls to the rest of the arguments.

The snippet (.. test-builder (body "Mike") (path "/say-hello") build) first calls the body method on test-builder with the argument "Mike". Then it takes that result and calls the path method on it with the argument "say-hello" and finally calls build on that result to return an instance of HttpResult.

Here’s another example of using the .. macro to uppercase the string "mike" and then take the first character of it:

 
=> (.. "mike" toUpperCase (substring 0 1))
 
"M"

Maps for Data

Now that we’ve seen some basic Clojure and Clojure/Java interoperability, let’s take the next step in transforming TinyWeb into Clojure. Here we’ll change test-controller so that the HTTP request it takes in is also a map, just like the model it returns. We’ll also introduce a view function and a render function that’s responsible for calling views. The code for our next iteration is below:

ClojureExamples/src/mbfpp/oo/tinyweb/steptwo.clj
 
(​ns​ mbfpp.oo.tinyweb.steptwo
 
(:import (com.mblinn.oo.tinyweb RenderingException)))
 
 
(​defn​ test-controller [http-request]
 
{:name (http-request :body)})
 
 
(​defn​ test-view [model]
 
(​str​ ​"<h1>Hello, "​ (model :name) ​"</h1>"​))
 
 
(​defn-​ render [view model]
 
(​try
 
(view model)
 
(​catch​ Exception e (​throw​ (RenderingException. e)))))

Let’s take a closer look at the pieces, starting with our new test-controller. As we can see in the code, we’re expecting http-request to be a map with a :body key that represents the body of the HTTP request. We’re pulling out the value for that key and putting it into a new map that represents our model:

ClojureExamples/src/mbfpp/oo/tinyweb/steptwo.clj
 
(​defn​ test-controller [http-request]
 
{:name (http-request :body)})

We can explore how test-controller works very easily using the REPL. All we need to do is define a test-http-request map and pass it into test-controller, which we do in this REPL output:

 
=> (def test-http-request {:body "Mike" :path "/say-hello" :headers {}})
 
#'mbfpp.oo.tinyweb.steptwo/test-http-request
 
=> (test-controller test-http-request)
 
{:name "Mike"}

Views in Clojure

Now that we’ve got our controller approach buttoned up, let’s take a look at some view code. Just like our controllers, views will be functions. They take a map that represents the model they operate on and return a String that represents the output of the view.

Here is some code for a simple test-view that just wraps a name in an <h1> tag:

ClojureExamples/src/mbfpp/oo/tinyweb/steptwo.clj
 
(​defn​ test-view [model]
 
(​str​ ​"<h1>Hello, "​ (model :name) ​"</h1>"​))

Again, we can try this out simply in the REPL by defining a test model and passing it into the function:

 
=> (def test-model {:name "Mike"})
 
#'mbfpp.oo.tinyweb.steptwo/test-model
 
=> (test-view test-model)
 
"<h1>Hello, Mike</h1>"

We need one more piece to finish our view-handling code. In Java, we used Pattern 7, Replacing Strategy, to ensure that any exceptions in view-handling code were properly wrapped up in a RenderingException. In Clojure we’ll do something similar with higher-order functions. As the code below shows, all we need to do is pass our view function into the render function, which takes care of running the view and wrapping any exceptions:

ClojureExamples/src/mbfpp/oo/tinyweb/steptwo.clj
 
(​defn-​ render [view model]
 
(​try
 
(view model)
 
(​catch​ Exception e (​throw​ (RenderingException. e)))))

Tying It All Together

Now that we’ve got a handle on our Clojure views and controllers, let’s finish up the example by adding in filters and the glue code that ties everything together. We’ll do this final step in a namespace called core. This is the standard core namespace that Clojure’s build tool Leiningen creates when you create a new project, so it’s become the de facto standard core namespace for Clojure projects.

To do this, we’ll add an execute-request function, which is responsible for executing an http-request. The function takes an http-request and a request handler. The request handler is simply a map containing the controller and view that should be used to handle the request.

We’ll also need apply-filters, which takes an http-request, applies a series of filters to it, and returns a new http-request. Finally, we’ll need the tinyweb function.

The tinyweb function is what ties everything together. It takes in two arguments: a map of request handlers keyed off the path each should handle and a sequence of filters. It then returns a function that takes an http-request, applies the sequence of filters to it, routes it to the appropriate request handler, and returns the result.

Here is the code for the full Clojure TinyWeb library:

ClojureExamples/src/mbfpp/oo/tinyweb/core.clj
 
(​ns​ mbfpp.oo.tinyweb.core
 
(:require [clojure.string :as ​str​])
 
(:import (com.mblinn.oo.tinyweb RenderingException ControllerException)))
 
(​defn-​ render [view model]
 
(​try
 
(view model)
 
(​catch​ Exception e (​throw​ (RenderingException. e)))))
 
(​defn-​ execute-request [http-request handler]
 
(​let​ [controller (handler :controller)
 
view (handler :view)]
 
(​try
 
{:status-code 200
 
:body
 
(render
 
view
 
(controller http-request))}
 
(​catch​ ControllerException e {:status-code (​.​getStatusCode e) :body ​""​})
 
(​catch​ RenderingException e {:status-code 500
 
:body ​"Exception while rendering"​})
 
(​catch​ Exception e (​.​printStackTrace e) {:status-code 500 :body ​""​}))))
 
(​defn-​ apply-filters [filters http-request]
 
(​let​ [composed-filter (​reduce​ ​comp​ (​reverse​ filters))]
 
(composed-filter http-request)))
 
(​defn​ tinyweb [request-handlers filters]
 
(​fn​ [http-request]
 
(​let​ [filtered-request (apply-filters filters http-request)
 
path (http-request :path)
 
handler (request-handlers path)]
 
(execute-request filtered-request handler))))

The render method is unchanged from the previous iteration, so let’s start by examining the execute-request function. We have already defined the function in the full Clojure TinyWeb library. To start picking apart the execute-request function, let’s first define some test data in the REPL. We’ll need the test-controller and test-view we defined in our last iteration to create a test request handler, which we do below:

 
=> (defn test-controller [http-request]
 
{:name (http-request :body)})
 
 
(defn test-view [model]
 
(str "<h1>Hello, " (model :name) "</h1>"))
 
#'mbfpp.oo.tinyweb.core/test-controller
 
#'mbfpp.oo.tinyweb.core/test-view
 
=> (def test-request-handler {:controller test-controller
 
:view test-view})
 
#'mbfpp.oo.tinyweb.core/test-request-handler

Now we just need our test-http-request, and we can verify that execute-request runs the passed-in request-handler on the passed-in http-request, as we’d expect:

 
=> (def test-http-request {:body "Mike" :path "/say-hello" :headers {}})
 
#'mbfpp.oo.tinyweb.steptwo/test-http-request
 
=> (execute-request test-http-request test-request-handler)
 
{:status-code 200, :body "<h1>Hello, Mike</h1>"}

Let’s look at the pieces of execute-request in more detail by trying them out in the REPL, starting with the let statement that picks the controller and view out of request-handler, which we’ve outlined here:

 
(​let​ [controller (handler :controller)
 
view (handler :view)]
 
let-body)

A let statement is how you assign local names in Clojure, somewhat like a local variable in Java. However, unlike a variable, the value these names refer to isn’t meant to be changed. In the let statement above, we’re picking the view and controller functions out of the request-handler map and naming them controller and view. We can then refer to them by those names inside the let statement.

Let’s take a look at a simpler example of a let expression. Below, we use let to bind name to the String "Mike" and to bind greeting to the String "Hello". Then, inside the body of the let expression, we use them to create a greeting:

 
=> (let [name "Mike"
 
greeting "Hello"]
 
(str greeting ", " name))
 
"Hello, Mike"

Now that we’ve got let under our belts, let’s take a look at the try expression, which we’ve repeated below. Much like in Scala, try is an expression with a value. If no exception is thrown, try takes on the value of the body of the expression itself; otherwise it takes on the value of a catch clause:

 
(try
 
{:status-code 200
 
:body
 
(render
 
view
 
(controller http-request))}
 
(catch ControllerException e {:status-code (.getStatusCode e) :body ""})
 
(catch RenderingException e {:status-code 500
 
:body "Exception while rendering"})
 
(catch Exception e (.printStackTrace e) {:status-code 500 :body ""})

If no exception is thrown, then the try expression takes the value of a map with two key-value pairs, which represents our HTTP response. The first key is the :status-code with a value of 200. The second is :body. Its value is computed by passing the http-request into the controller and then passing that result into the render function along with the view to be rendered.

We can see this in action using our test-view and test-controller below:

 
=> (render test-view (test-controller test-http-request))
 
"<h1>Hello, Mike</h1>"

Before we move on, let’s take a bit of a closer look at Clojure’s exception handling using a couple of simpler examples. Below, we see an example of a try expression where the body is just the String "hello, world", so the value of the whole expression is "hello, world":

 
=> (try
 
"hello, world"
 
(catch Exception e (.message e)))
 
"hello, world"

Here’s a simple example of how try expressions work when things go wrong. In the body of the try expression below, we’re throwing a RuntimeException with the message "It’s broke!". In the catch branch, we’re catching Exception and just pulling the message out of it, which then becomes the value of the catch branch and thus the value of the entire try expression:

 
=> (try
 
(throw (RuntimeException. "It's broke!"))
 
(catch Exception e (.getMessage e)))
 
"It's broke!"

Next up, let’s take a look at how we apply our filters. We use an apply-filters function, which takes a sequence of filters and an HTTP request, composes them into a single filter, and then applies it to the HTTP request. The code is below:

 
(defn- apply-filters [filters http-request]
 
(let [composed-filter (reduce comp filters)]
 
(composed-filter http-request)))

We explore the comp function further as part of Pattern 16, Function Builder.

To finish off our Clojure TinyWeb implementation, we need a function, tinyweb, to tie everything together. This function takes in a map of request handlers and a sequence of filters. It returns a function that takes an HTTP request, using apply-filters to apply all the filters to the request.

Then it picks the path out of the HTTP request, looks in the map of request handlers to find the appropriate handler, and uses execute-request to execute it. The following is the code for tinyweb:

 
(​defn​ tinyweb [request-handlers filters]
 
(​fn​ [http-request]
 
(​let​ [filtered-request (apply-filters filters http-request)
 
path (:path http-request)
 
handler (request-handlers path)]
 
(execute-request filtered-request handler))))

Using TinyWeb

Let’s take a look at using the Clojure version of TinyWeb. First let’s define a test HTTP request:

 
=> (​def​ request {:path ​"/greeting"​ :body ​"Mike,Joe,John,Steve"​})
 
#'mbfpp.oo.tinyweb.core/request

Now let’s take a look at our controller code, which is just a simple function and works much like our Scala version:

ClojureExamples/src/mbfpp/oo/tinyweb/example.clj
 
(​defn​ make-greeting [​name​]
 
(​let​ [greetings [​"Hello"​ ​"Greetings"​ ​"Salutations"​ ​"Hola"​]
 
greeting-count (​count​ greetings)]
 
(​str​ (greetings (​rand-int​ greeting-count)) ​", "​ ​name​)))
 
 
(​defn​ handle-greeting [http-request]
 
{:greetings (​map​ make-greeting (str/split (:body http-request) #","))})

Running our test request through it returns the appropriate model map, as seen below:

 
=> (handle-greeting request)
 
{:greetings ("Greetings, Mike" "Hola, Joe" "Hola, John" "Hola, Steve")}

Next up is our view code. This code renders the model into HTML. It’s just another function that takes in the appropriate model map and returns a string:

ClojureExamples/src/mbfpp/oo/tinyweb/example.clj
 
(​defn​ render-greeting [greeting]
 
(​str​ ​"<h2>"​greeting​"</h2>"​))
 
 
(​defn​ greeting-view [model]
 
(​let​ [rendered-greetings (str/join ​" "​ (​map​ render-greeting (:greetings model)))]
 
(​str​ ​"<h1>Friendly Greetings</h1> "​ rendered-greetings)))

If we run greeting-view over the output of handle-greeting, we get our rendered HTML:

 
=> (greeting-view (handle-greeting request))
 
"<h1>Friendly Greetings</h1>
 
<h2>Hola, Mike</h2>
 
<h2>Hello, Joe</h2>
 
<h2>Greetings, John</h2>
 
<h2>Salutations, Steve</h2>"

Next let’s look at our logging-filter. This is just a simple function that logs out the path of the request before returning it:

ClojureExamples/src/mbfpp/oo/tinyweb/example.clj
 
(​defn​ logging-filter [http-request]
 
(​println​ (​str​ ​"In Logging Filter - request for path: "​ (:path http-request)))
 
http-request)

Finally, we’ll wire everything together into an instance of TinyWeb, as we do in the following code:

ClojureExamples/src/mbfpp/oo/tinyweb/example.clj
 
(​def​ request-handlers
 
{​"/greeting"​ {:controller handle-greeting :view greeting-view}})
 
(​def​ filters [logging-filter])
 
(​def​ tinyweb-instance (tinyweb request-handlers filters))

If we run our test request through the instance of TinyWeb, it’s filtered and processed as it should be:

 
=> (tinyweb-instance request)
 
In Logging Filter - request for path: /greeting
 
{:status-code 200,
 
:body "<h1>Friendly Greetings</h1>
 
<h2>Greetings, Mike</h2>
 
<h2>Greetings, Joe</h2>
 
<h2>Hello, John</h2>
 
<h2>Hola, Steve</h2>"}

That wraps up our look at TinyWeb! The code in this chapter has been kept simple; we’ve stuck to a minimal set of language features and omitted much error handling and many useful features. However, it does show how quite a few of the patterns we’ll examine in this book fit together.

Throughout the remainder of the book, we’ll take a closer look at these patterns and many others as we continue our journey through functional programming.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.173.53