Serving data with Ring and Compojure

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.

Getting ready

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.

How to do it…

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.

Configuring and setting up the web application

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:

  1. Inside the project's 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.
  2. We'll use this namespace declaration at the top of 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)] ))
  3. Now that we have a namespace for it, we need to tell Ring where our web application is. We've actually already done this. If you look toward the bottom of the project.clj file, you'll find this line:
      :ring {:handler web-viz.web/app}

Serving data

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.

Defining routes and handlers

Now, we can connect the IBM dataset to the Internet. To do this, perform the following steps:

  1. In the 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.

  2. We use these routes as the basis of our web app:
    (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).

Running the server

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):

Running the server

How it works…

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:

  • Handlers: These are the core of a web application. Handlers are Clojure functions that take a map with information about the request and return a map with the response. Each web application has one handler function.
  • Middleware: This transforms incoming requests or outgoing responses. Generally, middleware wraps an existing handler to provide extra functionality. For instance, authentication can be handled through middleware. The project Friend (https://github.com/cemerick/friend) does this. Middleware can also handle requests for static resources, cookies, and many other things.
  • Adapters: These connect Ring to web servers. Ring has an adapter for the Jetty web server (http://jetty.codehaus.org/jetty/) and this is what we use in this recipe.

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 handler
  • app: This is composed of the Ring handler function with some middleware

In our project.clj file, we define connect Ring to our handler function with this line:

:ring {:handler web-viz.web/app}

There's more…

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

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