3. Creating a REST Server in Compojure

In this chapter we will build the first of two REST servers. This is a simple one to do in Compojure. The REST server we build will receive a REST call over http. We will test it using a command line tool, curl.

Assumptions

In this chapter we assume the following:

Image You have Leiningen installed and on your path on the command line.

Image You know how to use curl (whether on a Mac or a PC) and have it on your path.

Image You understand the concept of http parameters being passed via a GET request URI and via a POST form parameters request.

Benefits

IT organizations now are filled with demands to build backend services implemented in JavaScript for mobile devices like iPhones and rich web clients. Both mobile and rich JavaScript clients work very well with services that implement REST. A great opportunity for you to get Clojure into your organization is by whipping up a REST service in Clojure.

The Recipe—Code

1. Create the project using Leiningen and the Compojure template:

lein new compojure rest-demo

2. Modify the project.clj to have the following contents:

(defproject rest-demo "0.1.0-SNAPSHOT"
  :min-lein-version "2.0.0"
  :dependencies [[org.clojure/clojure "1.7.0-beta2"]
                 [compojure "1.3.4"]
                 [ring/ring-defaults "0.1.5"]]
  :plugins [[lein-ring "0.9.5"]]
  :ring {:handler rest-demo.handler/app}
  :profiles
  {:dev {:dependencies [[javax.servlet/servlet-api "2.5"]
                        [ring/ring-mock "0.2.0"]]}})

3. Ensure that the file rest-demo/src/rest_demo/handler.clj looks like this:

(ns rest-demo.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults
            :refer [wrap-defaults site-defaults]]))

(defn handle-http []
  (context "/:id" [id]
    (defroutes api-routes
      (GET    "/" [] (str "get called: " id " "))
      (POST   "/" {form-params :form-params}
           (str "post called: " id " " form-params " "))
      (PUT    "/" req (str "put called with params: " req))
      (DELETE "/" [] (str "delete called: " id " ")))))

(defroutes app-routes
  (handle-http)
  (route/not-found (str
   "This is the default page - try "
   "<a href='http://localhost:4000/33'>this</a> ")))

(def app
  (wrap-defaults app-routes
    (assoc-in site-defaults [:security :anti-forgery] false)))

Testing the Solution

Let’s give it a run.

1. In the command prompt, change back to the parent directory of your project and start the server using Leiningen:

lein ring server-headless 4000

Note that we are using the server-headless parameter to the lein ring command. This starts the server without opening a web browser. Had we merely run (as a hypothetical) lein ring server 4000, a new web browser would have opened. (But because we’re about to use curl to interact with the website, a browser would have gotten in the way.)

2. Open a new command window and type:

curl http://localhost:4000/1

You should get a response like:

get called: 1

3. Now enter:

curl -X DELETE http://localhost:4000/4

You should get a response like:

delete called: 4

4. Now enter:

curl -X POST -d "id=2" http://localhost:4000/3

You should get a response like:

post called: 3
{"id" "2"}

5. Now enter:

curl -X PUT -d "id=2" http://localhost:4000/3

You should now get something similar to:

put called with params: {:ssl-client-cert nil, :remote-addr
"0:0:0:0:0:0:0:1%0", :scheme :http, :query-params {}, :context "/3",
:form-params {"id" "2"}, :request-method :put, :query-string nil,
:route-params {:id "3"}, :content-type "application/x-www-form-urlencoded",
:path-info "/", :uri "/3", :server-name "localhost", :params {:id "3", "id"
"2"}, :headers {"user-agent" "curl/7.27.0", "content-type" "application/
x-www-form-urlencoded", "content-length" "4", "accept" "*/*", "host"
"localhost:4000"}, :content-length 4, :server-port 4000, :character-encoding
nil, :body #<HttpInput org.eclipse.jetty.server.HttpInput@cfefc0>}
Julians-MacBook-Pro:~

You can see a large amount of information is in the parameter map req. This also demonstrates the powerful, dynamic, interactive nature of running Compojure with the Leiningen plug-in. You can modify the file and save it, and your changes are accessible instantly to the web browser. (We could also have done all of this on the REPL—but we’ll save that for another day.)

Notes on the Recipe

The Compojure library is designed to make RESTful URIs easy to work with. Note in particular the project.clj file:

            [ring/ring-defaults "0.1.5"]]
  :plugins [[lein-ring "0.9.5"]]
  :ring {:handler rest-demo.handler/app}

Notice the ring-defaults library. We’ll use this when we examine parameters passed in. Also note the lein-ring plug-in. This enables us to start the app from the command line. It will also enable us to modify the app when it is running and to see the results without restarting the server.

Also note the :ring {:handler... syntax. This points to the part of the application that will handle the incoming requests.

Now look at the file handler.clj, in particular the namespace:

(ns rest-demo.handler
  (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults
            :refer [wrap-defaults site-defaults]]))

Note that we load the Compojure libraries for handling routes, and we use the ring.middleware for parameter handling.

Now observe the handle-http function definition:

(defn handle-http []
  (context "/:id" [id]
    (defroutes api-routes
      (GET    "/" [] (str "get called: " id " "))
      (POST   "/" {form-params :form-params}
        (str "post called: " id " " form-params " "))
      (PUT    "/" req (str "put called with params: " req))
      (DELETE "/" [] (str "delete called: " id " ")))))

This function handles the parameters for the particular http request type. Here we just do simple handlers for the different http request types. This handles requests in the format:

http://servername/2

Now note the following function:

(defroutes app-routes
  (handle-http)
  (route/not-found (str
  "This is the default page - try "
  "<a href='http://localhost:4000/33'>this</a> ")))

This is the main route-handler function. It delegates most of its responsibilities to our handle-http function above. If that returns nil, then it displays a "Not Found" response.

Now note the following symbol:

(def app
  (wrap-defaults app-routes
    (assoc-in site-defaults [:security :anti-forgery] false)))

This is the application hook. We point to this in the project.clj file. It takes the route definitions in app-routes and feeds that into the function result of wrap-defaults. The wrap-defaults function adds middleware to the route to enable URI parameter input. We switch off the anti-forgery middleware so our simple curl tests will work. You shouldn’t do this in your production application.

Conclusion

We’ve seen an example of generating RESTful HTTP requests from the command line using the curl command, and we implemented handlers for these requests in Compojure.

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

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