Chapter 10. Clojure and the web

 

This chapter covers

  • Creating web services with Clojure
  • An overview of Ring
  • Using Compojure on top of Ring
  • Generating HTML

 

HTTP is the lingua franca of the internet. If your project involves creating a dynamic website, you’ll need to use a suitable web application framework to get your job done. Even if you aren’t creating a website for end users to visit, writing an HTTP interface to your application can provide an easy-to-use API for other applications. In this chapter, we’ll explore doing that. First, we’ll write some framework code, and then we’ll use some open source libraries to enhance what we wrote. Specifically, after writing our own little framework, we’ll explore the Ring and Compo-jure projects. We’ll also look at clj-html, another open source library that allows HTML generation via Clojure code.

We’ll write own little web framework to demonstrate how easy and straightforward Clojure is. This exercise will also serve as a segue to how you might use open source alternatives for web services in a production environment. Along the way, you’ll see some more idiomatic Clojure, which should help in other programs you might be writing. Let’s get started with our own take on an HTTP service in Clojure.

10.1. An HTTP interface from scratch

In this section, we’ll write some code to make your Clojure functions available as HTTP services. The goal is to show how simply this can be accomplished in Clojure; there’s nothing complicated about it. Our solution won’t be full featured, but will work quite well. If your goal is to get something simple up and running quickly, this will do the trick.

Instead of providing features that will allow someone to write a full-featured web application, we’ll write enough to enable a developer to create a simple web service. The difference is that our program won’t provide a UI to an end user but instead can be used as an HTTP API end point by other programs. Let’s start by picking an off-the-shelf HTTP server to embed your code in.

10.1.1. The HTTP engine

The first thing you’ll need is a way to understand HTTP and to accept requests and respond in kind. You needn’t write any code to do this, because this is a solved problem. You can use one of several open source projects that have HTTP servers built in, hitching a ride on top. In this section, we’ll pick the Grizzly project, which came out of the Glassfish project at Sun Microsystems (now Oracle). The reason for this choice is that it’s based on the Java NIO (new IO) framework, which makes it possible to write Java servers that can scale up to support thousands of users.

The first thing we’ll do is create a way to attach Clojure functions to the Grizzly-Adapter interface, which will allow you to handle HTTP requests. We’ll then write some code to register our functions on web addresses, to allow you to create HTTP services that you can then advertise. Lastly, we’ll make it easier for your functions to work with the request parameters.

Grizzly

The Grizzly NIO and Web Framework project was created to create Java server-based applications that could scale to thousands of concurrent users. It does this via the use of the NIO libraries that are now part of the Java software development kit (SDK). In order to write truly scalable applications, you can use the asynchronous adapters provided by Grizzly, but for the purposes of this example, we’ll use the simpler synchronous option.

The specific Java components of interest to you are an interface called Grizzly-Adapter and a class called GrizzlyWebServer. You’ll use Clojure’s proxy macro to wrap your functions in an implementation of the GrizzlyAdapter interface. You’ll then use an instance of GrizzlyWebServer to listen for requests and service them. The code is shown in the following listing. We’ll discuss a few salient points about the code in the following paragraphs.

Listing 10.1. A simple way to expose Clojure functions as web services

Let’s now see how you might use your newly written system. First, you’ll define a couple of functions that you’ll want to expose as HTTP services. After ensuring that the library is included in your program through an appropriate call to :use or :require, the following code sets up the server:

(defn greet [name]
  (str "hello, " name))

(defn judge-credentials [{u "username" p "password"}]
  (str u "/" p " is a good combo!"))

(def routes {
  "/test/greet" greet
  "/judge/creds" judge-credentials
})

(boot-web-server routes 10000)

Testing that your system works as advertised is easy using a tool like curl (or by typing the URLs in a web browser):

~ > curl "http://localhost:10000/test/greet/amit"
hello, amit

~ > curl "http://localhost:10000/judge/creds?username=amit&password=override"
amit/override is a good combo!

This isn’t too shabby, because you managed to implement this functionality in less than 60 lines of code. You can handle URLs with explicit query strings or parse out parameters from the URL itself, in a RESTful fashion. Your routing logic is also straightforward: if the requested URL begins with a registered path in your routes map, the associated function is selected as the handler.

We can now look into adding a couple of more small features to your little web service framework—specifically, handling JSONP calls and reading/writing cookies.

Adding JSONP support

With the advent of AJAX and rich internet applications, many websites need to get data from backend HTTP servers and process/display them in the browser. Often, JSON is used as the lightweight format to send data to and from the browser. Because of security restrictions, web pages can’t talk to any backend server other than the one they originated from. The JSONP pattern is popular because it allows you to get around this restriction.

As usual, it uses JSON for communication, but the request is made with an extra parameter (conventionally called jsonp). The backend server wraps the return JSON with extra padding (hence the p in jsonp) that translates to a JavaScript function call inside the browser. If a request is made as follows:

http://localhost:10000/some/json/request?arg1=a&arg2=b&jsonp=xxx

If the traditional response is {w: 12, z: 20}, it’s wrapped in the padding mentioned earlier to become xxx({w: 12, z: 20}).

Let’s add this feature to our web service framework. We’ll look for a parameter called jsonp, and if it’s found, we’ll wrap our return to conform to the JSONP format shown previously. We’ll edit our program from listing 10.1 and make the modifications shown in the following listing.

Listing 10.2. Extending our basic web service framework to support JSONP callbacks

To test this, change your registered functions to ones that might return reasonable JSON output. Here’s the new routes map:

(defn js-judge-credentials [{u "username" p "password"}]
  {:judgement (str u "/" p " is a good combo!")})

(defn js-greet [name]
  {:greeting (str "hello, " name)})

(def routes {
  "/test/js_greet" js-greet
  "/judge/js_creds" js-judge-credentials
})

(boot-web-server routes 10000)

Let’s test these two new services with curl at the shell:

~/labs/projectx(master) > curl
     "http://localhost:10000/test/js_greet/amit?&jsonp=asd"
asd({"greeting":"hello, amit"})

Let’s also test the other one:

~/labs/projectx(master) > curl
     "http://localhost:10000/judge/js_creds?username=amit
                                    &password=override&jsonp=asd"
asd({"judgement":"amit/override is a good combo!"})

So there you are; with about a dozen lines of code, you’ve added support for JSONP requests. Clients of our services don’t have to do anything special; if they include a parameter named jsonp, it will work as desired. Further, from a developer standpoint, no extra code needs to be written, only a regular Clojure data structure can be returned (such as a map), which will be converted to JSON format using the JSON library we included (clojure-json by Dan Larkin, available from GitHub.com).

Our next adventure will be to support reading and writing of cookies.

Supporting cookies

In this section, we’ll implement a simple solution to read and write cookies. We won’t support a comprehensive solution that offers fine-grain control but just enough to make it useful and to demonstrate the approach. The code is in the following listing, but instead of replicating the entire program again, it shows only the additional functions needed and the modified service-http-request function.

Listing 10.3. Changes needed to support simple cookie handling

You can test it by making slight modifications to the functions you’ve used as HTTP services so far:

(defn greet [name]
  (add-cookie "greeting" "hello")
  (str "hello, " name))

(defn judge-credentials [{u "username" p "password"}]
  (println "greeting cookie:" (read-cookie "greeting"))
  (str u "/" p " is a good combo!"))

(def routes {
  "/test/greet" greet
  "/judge/creds" judge-credentials
})

(boot-web-server routes 10000)

Here, the greet function sets a cookie, and the judge-credentials function reads it. This is easiest to test from a web browser. Let’s go ahead and test with the following two URLs:

http://localhost:10000/test/greet/amit

and

http://localhost:10000/judge/creds?username=amit&password=override

Because you’re printing your inner workings to the console, you can see the following output when you test with these URLs:

Responding to /test/greet with params: (amit)

This is followed by

greeting cookie: hello
Responding to /judge/creds with params: {password override, username amit}

That’s it. As mentioned earlier, it doesn’t allow you much control over the way cookies are set, for instance, but works well enough to be useful. One example of its inflexibility is that it always sets a cookie to the / path. It’s now easy enough to build on top of this code and extend the functionality. It’s also easy to expose more functionality from the underlying HTTP library, for instance, to do set headers.

Notice also the use of binding to set *the-request* and *the-response* to the appropriate request and response objects and that this happens for each request handled. It avoids passing these two around to all your helper functions. There aren’t many such functions right now, but as you add functionality, this might prove convenient.

This section showed that converting your Clojure functions into HTTP services is a rather trivial task. The code didn’t do everything you might need in a production-ready system, but it gives you the general idea. The next section will explore a few open source options from the Clojure ecosystem.

10.2. Ring

Ring is a project by Mark McGranaghan that aims to abstract away HTTP so that basic web services can be written in Clojure. It isn’t, and doesn’t aim to be, a full-featured web application framework. Instead, out-of-the-box, it implements only basic HTTP support, and by featuring a concise, modular design, it allows extensibility through the use of what are called middleware plug-ins.

The Ring project is hosted on GitHub, at http://github.com/mmcgrana/ring. There are instructions on the site about downloading and installing it onto your computer. For the remainder of this section, we’ll assume that you have it running (you have Ring’s src and lib directories on your JVM classpath).

10.2.1. Understanding Ring

Using Ring is easy. Here’s a simple example, a function that says hello:

(ns chapter13-ring
  (:use ring.adapter.jetty))

(defn hello [req]
  {:body "hello world!"})

(run-jetty hello {:port 8080})

This particular example uses the Jetty adapter, but you can use others, and you can write your own. To understand what an adapter is, it will be useful to get a quick high-level overview of the design of the Ring framework.

The composition of Ring

The diagram in figure 10.1 shows how a request travels through the various pieces of Ring. We’ll discuss each component next; this figure also shows where the name Ring comes from.

Figure 10.1. How an HTTP request flows through the Ring framework

A Ring request is an abstraction over an HTTP request that makes it easy to deal with from Clojure. It’s a map with well-defined keys such as :request-method, :query-string, and :headers.

A handler is a Clojure function that processes a Ring request and produces a Ring response. In our simple example earlier, the function hello was the handler.

A Ring response is a Clojure map with well-defined keys such as :status, :body, and :headers.

None of these components care about the HTTP protocol itself, because their job is to abstract those details away from the programmer. Someone in the picture ultimately needs to speak HTTP, and that’s where adapters fit in. Their job is to implement HTTP, invoke the handler with Ring requests, and take their return values as Ring responses. Adapters can use other HTTP server libraries to do their work, and as you saw in our example, Ring comes bundled with support for Jetty.

10.2.2. Middleware

So far, we haven’t talked about middleware. Ring middleware is a plug-in system that allows the programmer to mix and match modules that augment the functionality of the handlers in some specified way. To understand them, let’s look at the following example that doesn’t use any middleware. It’s a function that echoes a query parameter named echo:

(defn echo [req]
  (let [qs (:query-string req)
        params (apply hash-map (.split qs "="))]
    {:body (params "echo")}))

(run-jetty echo {:port 8080})

If you go to the localhost URL http://localhost:8080/?echo=hellooooo, you’ll see that our echo function behaves as desired. As shown here, we had to manually parse the query string, and you can imagine this would become rather tedious if we had to keep doing it. Even if we abstracted it away as a function, we’d still have to call it in our handlers. It would be nice if we could enhance Ring in some way so that it would hand us the parsed parameters.

Ring middleware modules play exactly this role. They’re functions that can be used to enhance the functionality of Ring’s handlers. Let’s rewrite our previous echo example using the params middleware that comes with Ring:

(ns chapter13-ring
  (:use ring.adapter.jetty)
  (:use ring.middleware.params))

(defn new-echo [req]
  (println "new")
  {:body (get-in req [:query-params "echo"])})

(def echo-app (wrap-params new-echo))

(run-jetty echo-app {:port 8080})

Notice how you were able to use a new key on the Ring request map called :queryparams. This is because echo-app is a function that’s returned from wrap-params, a middleware that processes the incoming request, grabs the parameters (either from a query string or from a form body), and makes it all available via new keys in the regular Ring request. By the time our handler is called, the query parameters are all parsed and ready to use.

Available middleware

The advantage of this design is that Ring itself can stay lean and focused. It provides a framework that hides the details of the HTTP protocol, so that developers can work with Clojure abstractions such as maps. Other web frameworks can be built on top of Ring that are more full featured; for example, they might provide routing, templating, and the like.

Ring comes with several middleware modules; here are a few of them:

  • params—You saw this one in our previous example. It parses the parameters from incoming requests and makes them available in the Ring request map.
  • cookies—This works in a similar fashion to params, but makes cookies available in the Ring request map.
  • file—This one allows a specified directory to be checked for a static file (and returned if found). If the file doesn’t exist, it passes the request onto the handler being wrapped.
  • file-info—This adds content-type and content-length headers to a file being served, if those headers aren’t already present.
  • reload—This allows the source code to be reloaded for each request. This makes it easier to develop because the server doesn’t have to be restarted for each change.
  • stacktrace—This middleware is also helpful during development because it catches any exceptions thrown during handler processing and displays them in a debugging-friendly manner.

Writing Ring middleware is easy, and you can write specific functions for your application as needed. The general API of a middleware function is

(function-name handler options)

where handler is the handler being augmented, and options is a map that can contain configuration switches for the middleware.

That’s pretty much all there is to Ring. If you need to create your own web service framework, building it on top of Ring is a great option. It does a lot of the basic work for you, and you can use the many available middleware functions to get more features for free. Our next stop will be Compojure, a full-featured web application framework that you can use if you need to create a dynamic website in Clojure.

10.3. Compojure

Compojure is presently the leading web-application framework for writing web apps in idiomatic Clojure. It was inspired by the Ruby web-application framework called Sinatra, but it’s growing into a comprehensive solution in its own right. In this section, we’ll look at how to use it to create a simple web service.

10.3.1. Using Compojure

Compojure is built on top of Ring, so parts of it will be familiar. The request and response maps, in particular, use the same keys you saw in the previous section. Compojure uses a few middleware components, for instance, params and cookies that enhance the requests.

Compojure is hosted on GitHub, at http://github.com/weavejester/compojure, and can be downloaded via Git in the usual manner. We’re going to deal with version 0.3. Once you have it downloaded, run the following commands to build the application:

ant deps
ant

The first command downloads the dependencies as a compressed archive and unpacks them into a folder called deps. The second kicks off the Ant build, which leaves a freshly created copy of compojure.jar in the root folder.

For the rest of this section, you’ll need all the JAR files in the deps folder on your JVM classpath, along with compojure.jar itself. Let’s now start with a simple introductory example of creating a web application using Compojure.

Hello, World!

Here’s what a simple Compojure web application looks like:

(ns chapter13-compojure
  (:use compojure))

(defroutes hello
  (GET "/"
    (html [:h1 "Hello, world!"]))
  (ANY "*"
       [404 "Page Not Found!"]))

(run-server {:port 8080}
  "/*" (servlet hello))

Try testing this using curl:

~ > curl "http://localhost:8080/"
<h1>Hello, world!</h1>
~ > curl "http://localhost:8080/some/other/route"
Page Not Found!

Compojure provides forms such as GET, PUT, POST, and so on to handle HTTP requests made with corresponding HTTP methods. Now that you have the basics working, let’s look at some more features of the framework.

Handling parameters

Let’s add some interactivity. We’ll reimplement the echo service we wrote in the last section by adding the following to the hello routes we’ve defined so far:

(GET "/echo"
  (html [:h2 "You said " (params :message)]))

Here it is in action:

~ > curl "http://localhost:8080/echo?message=hiya"
<h2>You said hiya</h2>

Notice that you were able to access the parameters via a map named params. It’s initialized to a map containing parameters from the query string and any form parameters that were passed along with the request. Compojure has other such special names for convenient access to things like cookies and session.

10.3.2. Under the hood

There are several interesting things to learn from the Compojure codebase. Let’s start by looking at the implementation of defroutes. It’s defined in the source file called routes.clj in the http directory. As you can imagine, it’s a macro. The definition is quite short; here it is without the doc string:

(defmacro defroutes [name doc? & routes]
  (let [[name & routes] (apply-doc name doc? routes)]
   `(def ~name
      (routes ~@routes))))

Here, apply-doc is a helper function that applies the doc? doc string to each defined route. It isn’t important for our discussion here. The routes function takes the route specifications passed in (an example being the s-expression starting with the symbol GET) and combines them all into a new Ring handler function (Compojure is built on top of Ring). Here’s the definition of routes:

(defn routes [& handlers]
  (-> (apply routes* handlers)
    with-request-params
    with-cookies))

The creation of the handler is delegated to the routes* function (not shown here). Observe that the handler is being wrapped with two Ring middleware plug-ins: with-request-params and with-cookies. The thread-first macro (you saw it in chapter 2) makes the code easy to read; it’s clear that the handler is being enhanced to add parameter processing and cookie handling.

Magic variables

Let’s now skip over the code a bit and examine the with-request-bindings macro. Here it is:

This macro wraps the body specified in the call to defroutes. To be specific, let’s look at our parameter-handling example from earlier:

(GET "/echo"
  (html [:h2 "You said " (params :message)]))

The second line of code is the body. If you wondered how params are made available to the body, because they need to be unique for each request, the with-request-bindings macro explains it. It sets up local bindings for five things: request, params, cookies, session, and flash. Notice how the macro uses the unquote followed by the quote reader macro to force using a name like params. Without it, Clojure would expand it into a namespace-qualified name, forcing the usage of the auto-gensym reader macro, the hash (#).

This trick of forcing variable capture lets the programmer allow users of a function to use special names in their code. Because they’re local bindings and not dynamically bound vars, they won’t be available in a function you call from within the handler. You’d have to pass whatever you need to any function you call. This is a limitation, and this behavior will probably change in the future. Incidentally, GET, POST, and so on are also macros, which ultimately expand into code that calls with-request-bindings.

The Compojure response

Let’s now examine another part of Compojure—the part that takes the return value of the request handler to construct the final Ring response. The function in question is called create-response, and it’s defined in response.clj. Here it is:

(defn create-response
  "Create a new response map from an update object, x."
  [request x]
  (update-response request default-response x))

Here default-response is defined as

{:status 200, :headers {}}

What’s interesting about create-response is that it calls update-response, which is a multimethod. Here’s how it’s defined:

(defmulti update-response (fn [request reponse update]
                            (class update)))

The dispatch function is the class of the update parameter, which is the parameter named x in create-response. This x is the return value of a handler (as defined in defroutes). This allows the individual handler to return several different kinds of objects (such as URL or File), knowing that update-response will handle them correctly. Here are a couple of examples of the methods defined for update-response:

(defmethod update-response URL
  [request response url]
  (assoc response :body (.openStream url)))

(defmethod update-response Integer
  [request response status]
  (assoc response :status status))

This is convenient, because, for instance, it allows you to return a number such as 404 or 200, and it will be set as the value for the status header in response.

Compojure sessions

Let’s now look briefly at the mechanism of Compojure’s session handling. The middleware that does this is with-session, and it’s defined in the file session.clj. Using it is simple; you wrap your handler with with-session, similar to any other middleware. Here’s an example of setting a value inside the session:

(GET "/set-session"
    [(session-assoc :some-key (params :name)) "Set!"])

As shown in this example, the function to set a value in the session is session-assoc. Although calling this function is quite easy, the syntax for using it inside a handler is a bit awkward. Specifically, notice that we had to wrap the call to session-assoc and the final response string inside a vector. This is because Compojure attempts to avoid state changes until the end by delaying such mutation. It manages this because functions like session-assoc don’t do anything immediately but return functions that, when called later, do effect the requested mutation. Here’s how session-assoc is implemented:

(defn alter-session [func & args]
  (fn [request]
    (set-session
      (apply func (request :session) args))))

(defn session-assoc [& keyvals]
  (apply alter-session assoc keyvals))

This approach of delaying computation by returning functions that will be evaluated later can be useful. You can use the technique to collect actions that can all be performed at a later point (or not), in any order desired. Hence, it’s a useful technique to control evaluation of code.

In the case of Compojure, the current implementation makes using sessions rather unintuitive, and the implementation will probably change as the framework evolves. Meanwhile, here’s an example of how you might get a value out from the session:

(GET "/get-session"
    (html [:h3 "some-key is" (session :some-key)]))

There’s a lot more to learn from the Compojure codebase; you can get many ideas for other applications from reading it. It’s in active development, so a lot may have changed by the time you read this.

Our last stop in this chapter will be to explore a couple of ways to generate HTML in Clojure web apps.

10.4. Generating HTML

So far, you’ve seen several approaches to handling HTTP requests. As you saw, this is easy to do, with minimum ceremony (or even none—we didn’t have to create any XML files or other configuration files). We neatly wrote Clojure functions that were mapped to the incoming request, and any parameters either came in via named function arguments or as a convenient Clojure map.

The code you’ve seen here can be useful in creating HTTP APIs that allow Clojure programs to communicate and interact with other programs (written either in Clojure or any other language). The one thing we haven’t discussed so far is how to create UIs that can be used by end users. What you need to make this happen is a way to generate HTML, which can then be used to render your web UI. The previous sections used an interesting set of forms to generate HTML. Here’s an example from the section on Compojure:

(GET "/echo"
  (html [:h2 "You said " (params :message)]))

The html function is part of the clj-html library, which is the library we’ll examine in the following section.

10.4.1. clj-html

clj-html is an open source templating library written by Mark McGranaghan, who also wrote the Ring library we looked at in section 10.2. It’s hosted on GitHub at http://github.com/mmcgrana/clj-html. In order to use it, you have to do the usual: download it and put it on your classpath.

The central piece of the library is the html macro (or the associated defhtml macro, which can be used to create named templates). The following section shows a few examples of using it.

Usage

Using it is quite simple, as you saw in our examples so far. The simplest case accepts a literal and returns it as a string:

user=> (html "something")
"something"

user=> (html 1)
"1"

If you pass a vector as the argument to html, then the first element must be a keyword. This keyword will be the tag name that will be generated by the evaluation. Here’s an example:

user=> (html [:div])
"<div />"

The tag-name keyword supports the CSS-style syntax for specifying its ID and CLASS attributes. Here are a few examples that demonstrate this usage:

user=> (html [:div#topseller])
"<div id="topseller" />"

user=> (html [:div.product])
"<div class="product" />"

user=> (html [:div#topseller.product])
"<div id="topseller" class="product" />"

The second element of the vector can be a map containing any other attributes that should be part of the expansion, for instance:

user=> (html [:div#topseller.product {:runa_type "product"}])
"<div runa_type="product" id="topseller" class="product" />"

This map containing extra attributes is optional and can be omitted. The second argument can be the content of the tag expansion, as shown here:

user=> (html [:div#topseller.product "Baby T-Shirt"])
"<div id="topseller" class="product">Baby T-Shirt</div>"

You can specify both, as shown in the following:

user=> (html [:div#topseller.product
              {:runa_type "product"}
              "Baby T-Shirt"])
"<div runa_type="product"
      id="topseller"
      class="product">Baby T-Shirt</div>"

Nesting the vectors, as in this example, can generate nested tags:

user=> (html [:span [:div "Baby T-Shirts!"]])
"<span><div>Baby T-Shirts!</div></span>"

Also, the tag content can evaluate to a single string, such as those you’ve seen so far, or a sequence of strings. Here’s an example:

user=> (html [:ul {:type "romans"}
        (for [char '("iii" "iv" "v")]
          [:li char])])
"<ul type="romans"><li>iii</li><li>iv</li><li>v</li></ul>"
Htmli

Finally, one point about html must be noted: it expects the optional map containing the extra tag attributes to be a literal map. This is because html uses this optional map in its macro expansion. If you need to pass in an expression that evaluates to a map instead, you need to use htmli. An example follows:

(defn optional-tags [description price]
        {:desc description :price price})

user=> (htmli [:div.product (optional-tags "Top seller!" 19.95)
        "Baby T-Shirt"])
"<div class="product" desc="Top seller!"
        price="19.95">Baby T-Shirt</div>"

Let’s now look at a couple of implementation points about clj-html.

10.4.2. Under the hood

Because Clojure’s macro system uses Clojure as its implementation language, and Clojure code is represented in Clojure’s own data structures, Clojure macros can themselves be large Clojure programs. The implementation of the html macro is a much larger than any you’ve seen so far in this book.

Overall, the design of clj-html is divided into two portions. The first part is the html macro that we’ve used extensively in this section. The other is htmli, which is the interpreter underneath that handles dynamic content.

The html macro does as much as it can during expansion. For example, let’s look at the following call, which we’ll examine via the macroexpand-1 function:

(macroexpand-1 '(html [:div#topseller.product
                          {:runa_type "product"} "Baby T-Shirt"]))
(clojure.core/let [html-builder (java.lang.StringBuilder.)]
  (.append html-builder "<div runa_type="product"
      id="topseller" class="product">Baby T-Shirt</div>")
  (.toString html-builder))

Here, most of the work is done during expansion. When the function is called, the StringBuilder appends the fully constructed string and then returns it using the call to toString.

This kind of computation done during macro expansion time can result in efficient functions that don’t have to do as much during runtime. Let’s look at another example, this time with nested vectors:

(macroexpand-1 '(html [:span [:div "Baby T-Shirts!"]]))
(clojure.core/let [html-builder (java.lang.StringBuilder.)]
  (.append html-builder "<span><div>Baby T-Shirts!</div></span>")
  (.toString html-builder))

As you can see in the expansion, the html macro does almost all the work in the expansion in this case as well. Macros ought to do as much as possible during expansion time, and this is determined by the forms that are passed to them. It’s important to remember that macros operate on the symbolic expressions themselves and not the values of the expressions (the values are available only at runtime).

Here’s a case where it’s far more difficult to programmatically determine that all information is available at macro expansion time. To handle this case, the macro expands to a let binding (if-let, to be specific), so that part of the computation happens at runtime:

macroexpand-1 '(html [:ul {:type "romans"}
                 (for [char '("iii" "iv" "v")]
                   [:li char])]))
(clojure.core/let [html-builder (java.lang.StringBuilder.)]
  (.append html-builder "<ul type="romans">")
  (clojure.core/if-let [content__2207__auto__ (for [char
                            (quote ("iii" "iv" "v"))] [:li char])]
    (.append html-builder (clj-html.core/flatten-content
                                          content__2207__auto__)))
    (.append html-builder "</ul>") (.toString html-builder))

Obviously, if you try this in your own REPL, you’ll see a different name than content__2207__auto__ because it’s generated internally using gensym. Notice how the list comprehension using for is inserted into an if-let block, and it occurs during runtime.

Finally, it’s worth pointing out that flatten-content, a helper function, ultimately calls htmli, which we used directly in the previous section. As mentioned earlier, html operates on symbolic forms. This is why instead of passing a literal map containing extra attributes, if you pass an expression that returns the map instead, the html macro can’t handle it. It’s why htmli is exposed to clients as an available API.

10.5. Summary

This chapter was about interfacing with the outside world over HTTP. In the first section, we wrote some simple code that allowed you to expose your Clojure functions as web services with little fuss. We didn’t handle things like authentication, or any form of security, and so on, but we did get the basics up and running quickly. We leveraged an existing Java library (from the Grizzly project) that allowed us to use an existing HTTP server. We also used a simple Clojure map to hold routes and Clojure functions that are mapped to those routes. This implementation is easy in a functional language like Clojure.

We then looked at Ring, an open source Clojure framework that makes writing other HTTP frameworks easy. It does so by abstracting away most of the common things that most HTTP application frameworks need to deal with: requests and responses. It also provides a pluggable API for adding functionality to the HTTP request handlers, such as for handling parameters, cookies, and the like. We then also looked at another open source web application development framework called Compojure, which happens to be built on top of Ring.

Our final stop in this chapter was a look at clj-html, an HTML generation library for Clojure. It has an innovative design using macros to enable efficient generation of HTML.

Writing web services in Clojure can be easy and painless. The fact that Clojure is functional helps in designing and testing web applications (as it does in applications of all kinds). We can test handler functions completely in isolation using simple Clojure maps as inputs and outputs and then expect things to work when plugged into, say, Ring or Compojure. As you saw, Java interop also helps considerably, because it enables us to use battle-tested Java web servers to run our web applications. Clojure makes this easy as well.

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

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