Compojure is a routing library built on top of Ring. It provides an easy way to associate handler functions with a URL and an HTTP method. A Compojure route might look like this:
| (GET "/:id" [id] (str "<p>the id is: " id "</p>" )) |
The route name maps to an HTTP method name, such as GET, POST, PUT, DELETE, or HEAD. There’s also a route called ANY that matches any method the client supplies. The URI can contain keys denoted by using a colon, and their values can be used as parameters to the route. This feature was inspired by a similar mechanism used in Rails and Sinatra.[27][28] The route’s response will be automatically wrapped in the Ring response described earlier.
Since we’re likely to have more than a single route in our application, Compojure provides the routes function that creates a Ring handler from multiple routes. For example, if we had routes /all-items and item/:id, then we could combine these into a single handler as follows:
| (defn foo-handler [] |
| "foo called") |
| |
| (defn bar-handler [id] |
| (str "bar called, id is: " id)) |
| |
| (def handler |
| (routes |
| (GET "/all-items" [] (foo-handler)) |
| (GET "/item/:id" [id] (bar-handler id)))) |
Since defining routes is a very common operation, Compojure also provides the defroutes macro that generates a Ring handler from the supplied routes:
| (defroutes handler |
| (GET "/foo" [] (foo-handler)) |
| (GET "/bar/:id" [id] (bar-handler id))) |
Using Compojure routes, we can easily map functionality to each URL of our site, and provide much of the core functionality needed in a web application. We can then group these routes together using the defroutes macro as we did previously. Compojure, in turn, takes care of creating the Ring handlers for us.
Compojure also provides a powerful mechanism for filtering out common routes in the application based on the shared path elements. Let’s say we have several routes that handle operations for a specific user:
| (defn display-profile [id] |
| ;;TODO: display user profile |
| ) |
| (defn display-settings [id] |
| ;;TODO: display user account settings |
| ) |
| (defn change-password [id] |
| ;;TODO: display the page for setting a new password |
| ) |
| (defroutes user-routes |
| (GET "/user/:id/profile" [id] (display-profile id)) |
| (GET "/user/:id/settings" [id] (display-settings id)) |
| (GET "/user/:id/change-password" [id] (change-password-page id)) |
There’s a lot of repetition in that code, where each route starts with the /user/:id segment. We can use the context macro to factor out the common portion of these routes:
| (def user-routes |
| (context "/user/:id" [id] |
| (GET "/profile" [] (display-profile id)) |
| (GET "/settings" [] (display-settings id)) |
| (GET "/change-password" [] (change-password-page id)))) |
In that code the routes defined in the context of /user/:id will behave exactly the same as the previous version and have access to the id parameter. The context macro exploits the fact that handlers are closures. When the outer context handler closes over the common parameters, they are also available to handlers defined inside it.
For some routes, we’ll need to access the request map to access the request parameters. We do this by declaring the map as the second argument to the route.
| (GET "/foo" request (interpose ", " (keys request))) |
That route reads out all the keys from the request map and displays them. The output will look like the following.
| :ssl-client-cert, :remote-addr, :scheme, :query-params, :session, :form-params, |
| :multipart-params, :request-method, :query-string, :route-params, :content-type, |
| :cookies, :uri, :server-name, :params, :headers, :content-length, :server-port, |
| :character-encoding, :body, :flash |
Compojure also provides some useful functionality for handling the request maps and the form parameters. For example, in the guestbook application, which we created in Chapter 1, Getting Your Feet Wet, we saw the following route defined:
| (POST "/" [name message] (save-message name message)) |
This route extracts the :name and :message keys from the request params, then binds them to variables of the same name. We can now use them as any other declared variable within the route’s scope.
It’s also possible to use the regular Clojure destructuring inside the route. Given a request map containing the following parameters…
| {:params {"name" "some value"}} |
…we can extract the parameter with the key "name" as follows:
| (GET "/:foo" {{value "name"} :params} |
| (str "The value of name is " value)) |
Furthermore, Compojure lets you destructure a subset of form parameters and create a map from the rest:
| [x y & z] |
| x -> "foo" |
| y -> "bar" |
| z -> {:v "baz", :w "qux"} |
In the preceding code, parameters x and y have been bound to variables, while parameters v and w remain in a map called z. Finally, if we need to get at the complete request along with the parameters, we can do the following:
| (GET "/" [x y :as r] (str x y r)) |
Here we bind the form parameters x and y, and bind the complete request map to the variable r.
Armed with the functionality that Ring and Compojure provide, we can easily create pages and routes for our site. However, any nontrivial application requires many other features, such as page templating, session management, and input validation. For these tasks we’ll use the libraries best adapted for each task.
3.145.186.6