Compojure/Core Libraries

From Wikibooks, open books for an open world
Jump to navigation Jump to search

Important note about up-to-date information[edit | edit source]

See the official Compojure wiki for up-to-date information. This page is severely outdated and should not be relied upon as a guide for recent versions.

compojure.http.*[edit | edit source]

The HTTP library provides Compojure with a RESTful and functional way to define Java servlets. Its syntax was inspired by the Ruby web framework, Sinatra.

To create a servlet, you pass a series of HTTP resource definitions to the servlet function:

(def #^{:doc "A simple greeter"} greet
  (servlet
    (GET "/greet" "Hello visitor!")
    (ANY "/*"     (page-not-found))))

Compojure also provides a defservlet macro:

(defservlet greet
  "A simple greeter"
  (GET "/greet" "Hello visitor!")
  (ANY "/*"     (page-not-found)))

The resource definitions passed to defservlet are Compojure’s way of associating a URL route with code that produces a useful HTTP response, such as a web page or image.

Resource Definitions[edit | edit source]

Resource definitions take the form:

(method route & body)

The method can be any one of the standard HTTP methods:

GET  POST  PUT  DELETE  HEAD

Or, if you wish to match any HTTP method, you can use

ANY

Faking PUT and DELETE for HTML Forms[edit | edit source]

By current standards HTML forms can only call the GET and POST methods, but in a RESTful web service you may want to implement PUT and DELETE methods for your resources. Compojure provides a work-around that allows you to implement REST's uniform interface once for browsers and less restricted clients with no explicit server-side handling. The client should send a parameter named "_method" with the value of the method you really want called. You can add a hidden field to HTML forms for this purpose, and other clients can simply call PUT or DELETE without the parameter. Compojure's handler infrastructure will replace the actual method with the "_method" parameter's value before calling your handlers.

See the section #Servlet Bindings for an example that uses this technique.

Route Parameters[edit | edit source]

The route can be a fixed string, like “/greet”, but often you’re going to want to assign certain parts of the route to parameters that affect the output:

(GET "/greet/:name" {params :params} 
  (str "Hello " (params :name)))

Here, the resource definition assigns the path after “/greet” to the parameter :name. Parameters from routes can be accessed via the route function.

Servlet Bindings[edit | edit source]

Along with route, there are several other bindings available by default in all resource declarations:

  • method - the HTTP method
  • params - a hash-map of HTTP parameters
  • headers - a hash-map of HTTP headers
  • cookies - a hash-map of HTTP cookies
  • session - a ref to a session-specific map
  • request - the HttpServletRequest object

Here's an example that uses the session and params bindings:

(GET "/name"
  (str "<p>Your current name is: " (@session :name) "</p>"
       "<form>Change name: <input name='name' type='text'>"
       "<input type='submit' value='Save'></form>"))
(POST "/name"
  (dosync
    (alter session assoc :name (params :name))
    (str "Your name was changed to " (@session :name))))

Here's the same example but with the update implemented as PUT with the "_method" work-around:

(GET "/name"
  (str "<p>Your current name is: " (@session :name) "</p>"
       "<form>Change name: <input name='name' type='text'>"
       "<input type='submit' value='Save'>"
       "<input type='hidden' name='_method' value='PUT'></form>"))
(PUT "/name"
  (dosync
    (alter session assoc :name (params :name))
    (str "Your name was changed to " (@session :name))))

Generating the Response[edit | edit source]

It is possible to modify the response through the response object, but this is almost never necessary. Instead, Compojure takes a functional approach, constructing the HTTP response from the return value of the resource.

In the previous examples, you can see how returning a string adds to the response body. Other standard Clojure types modify the response in different ways:

  • java.lang.String - adds to the response body
  • java.lang.Number - changes the status code
  • Clojure map - updates (merges) the response Map
  • Clojure seq - lazily adds to the response body
  • java.io.File - streams the file to the response body
  • java.io.InputStream - reads from the stream and adds to the response body
  • java.net.URL - streams the resource of the URL to the response body
  • java.servlet.http.Cookie - adds the cookie to the HTTP headers

These modifications can be chained together using a standard Clojure vector:

(GET "/text"
  [{:headers {"Content-Type" "text/plain"}}
   "This is plain text."
   "And some more text."])
(GET "/bad"
  [404 "<h1>This page does not exist!</h1>"])
(GET "/download"
  (file "public/compojure.tar.gz"))   ; 'file' is an alias to 'new java.io.File'

compojure.html[edit | edit source]

The HTML library provides a way of defining HTML or XML through a tree of vectors.

(html [:p [:em "Hello World"]])
<p><em>Hello World</em></p>

The tag name is taken from the first item of the vector, and can be a string, symbol or keyword. You can optionally specify attributes for the tag by providing a hash map as the second item of the vector:

(html [:div {:class "footer"} "Page 1"])
<div class="footer">Page 1</div>

The html function additionally offers syntax sugar for defining id and class attributes:

(html [:h1.first  "Foo"]
      [:h2#second "Bar"])
<h1 class="first">Foo</h1><h2 id="second">Bar</h2>

Any sequences will be expanded out into the containing vector:

(html [:em '("foo" "bar")])
<em>foobar</em>
(html [:ul
  (map (fn [x] [:li x])
       [1 2 3])])
<ul><li>1</li><li>2</li><li>3</li></ul>

compojure.server.jetty[edit | edit source]

The Jetty library provides a Clojure-friendly interface to the Jetty web server, so that you can easily create a web server with servlet mappings.

(def my-server
  (http-server {:port 8080}
    "/*"       my-main-servlet
    "/other/*" another-servlet
    ...))

You can also use the defserver macro:

(defserver my-server
  {:port 8080}
  "/*"       my-main-servlet
  "/other/*" another-servlet
  ...))

Once you’ve created your Jetty server, use (start my-server) and (stop my-server) to start and stop the web server.