While we can precompile ClojureScript and load the generated JavaScript files as static assets, we'll often want to combine the dynamic charts with dynamic pages. For instance, we might want to provide a search form to filter the data that's graphed.
In this recipe, we'll get started with a typical Clojure web stack. Even if we don't use ClojureScript, this system is useful for creating web applications. We'll use Jetty (http://jetty.codehaus.org/jetty/) to serve the requests, Ring (https://github.com/ring-clojure/ring) to connect the server to the different parts of our web application, and Compojure (http://compojure.org) to define the routes and handlers.
We'll first need to include Jetty, Ring, and Compojure in our Leiningen project.clj
file. We'll also want to use Ring as a development plugin for this project, so let's include it in the project.clj
file under the :plugins
key. The following is the full Leiningen project file:
(defproject web-viz "0.1.0-SNAPSHOT" :dependencies [[org.clojure/clojure "1.6.0"] [ring/ring-core "1.3.1"] [ring/ring-jetty-adapter "1.3.1"] [compojure "1.2.0"]] :plugins [[lein-ring "0.8.3"]] :ring {:handler web-viz.web/app})
Also, we'll need data to serve. For this recipe, we'll serve the 2010 US census race data that we've been using throughout this book. I've converted it to JSON, though, so we can load it into D3 more easily. You can download this file from http://www.ericrochester.com/clj-data-analysis/data/census-race.json.
Setting up a web application is relatively simple, but a number of steps are involved. We'll create the namespace and configuration, then we'll set up the application, and finally we'll add resources.
The code for the web application will need to be run again and again, so we'll need to make sure that our code makes it into files. Let's create a namespace inside our Leiningen project for it. Right now, my Leiningen project is named web-viz
, so I'll use it in this example, but you should replace it with whatever your project is named. To do this, perform the following steps:
src
directory, let's create a file named web.clj
. It's full path will be src/web_viz/web.clj
. This will contain the web application and routes.web.clj
. Note that you'll need to change the namespace to match your project:(ns web-viz.web (:require [compojure.route :as route] [compojure.handler :as handler]) (:use compojure.core ring.adapter.jetty [ring.middleware.content-type :only (wrap-content-type)] [ring.middleware.file :only (wrap-file)] [ring.middleware.file-info :only (wrap-file-info)] [ring.middleware.stacktrace :only (wrap-stacktrace)] [ring.util.response :only (redirect)] ))
project.clj
file, you'll find this line::ring {:handler web-viz.web/app}
For this recipe, we'll serve the JSON datafile statically. By default, Ring serves static files out of the /resources
directory of our project. In this case, create the /resources/data
directory and put the datafile that you downloaded from http://www.ericrochester.com/clj-data-analysis/data/census-race.json into it.
Now, we can connect the IBM dataset to the Internet. To do this, perform the following steps:
src/web_viz/web.clj
file, we'll define the routes using Compojure's defroutes
macro, as shown here:(defroutes site-routes (GET "/" [] (redirect "/data/census-race.json")) (route/resources "/") (route/not-found "Page not found"))
This creates a GET
request that redirects to our datafile. It also defines routes to serve any static resources from the classpath—in this case, to serve the resources directory—and a 404 (resource missing) page.
(def app (-> (handler/site site-routes) (wrap-file "resources") (wrap-file-info) (wrap-content-type)))
Along with serving the routes we defined, this function adds more functionality to our web app. We serve static files from the resources directory (wrap-file
). We add content-type and other HTTP headers whenever we serve files (wrap-file-info
). Also, we make sure to always include a content-type header, even if it's just an application/octet-stream (wrap-content-type
).
Starting the server for development is a Leiningen task handled by the Ring plugin, as shown here:
$ lein ring server 2013-01-16 09:01:47.630:INFO:oejs.Server:jetty-7.6.1.v20120215 Started server on port 3000 2013-01-16 09:01:47.687:INFO:oejs.AbstractConnector:Started [email protected]:3000
This starts the server on port 3000 and opens a web browser to the home page of the site at http://localhost:3000/
. In this case, that just redirects you to the census data, so your browser should resemble the following screenshot, unless your browser attempts to download JSON and save it to your drive (Although, your text editor should still be able to open it):
The first part of this system, and the foundation of it, is Ring (https://github.com/ring-clojure/ring). Ring is an abstraction over HTTP. This makes it easier to write web applications that will work in a variety of settings and that run under a variety of servers. You can write the web application to work with the Ring specification, and you can connect Ring to the web server using an adapter.
There are a number of central concepts in Ring, which are described as follows:
Ring's a good option to connect a web application to a web server, but it's a little too close to the metal. You wouldn't want to write your web application to talk to Ring directly. Compojure (https://github.com/weavejester/compojure/wiki) steps into the gap. It provides a simple DSL to define routes and response functions and composes them into a single Ring application handler function. It allows you to define the type of HTTP request that each route function can accept (GET
, POST
, and so on) and what parameters each expect.
Here's how the preceding example breaks down into the components we just discussed:
site-routes
: This is composed of Compojure routes, which act as the Ring handlerapp
: This is composed of the Ring handler function with some middlewareIn our project.clj
file, we define connect Ring to our handler function with this line:
:ring {:handler web-viz.web/app}