Each Bishop resource is defined with two hash-maps. The first provides
a map of content types provided by the resource and the functions that
accept a Ring request map and return data in that format. The second
hash-map contains the key for a callback function and the actual
function, these are invoked by Bishop when processing each
request. You can view the complete list of callback functions here.
We can now work on developing the Bishop resources that will provide
our web service. We’ll be using the “/todos” URL to provide both a
list of to-do items (with a “GET”) and an end-point for requests to
create new items (via “POST” or “PUT”). While this application deals
with JSON data we also want to present a simple HTML interface for
viewing the data. Listed below is our resource definition.
(def todo-group-resource
(bishop/resource
{;; JSON handler
"application/json"
(fn [request]
(cond
;; return a sequence of to-do items
(= :get (:request-method request))
{:body (generate-string
(map (partial add-resource-links URI-BASE)
(app/todos)))}
;; create the new to-do item
(= :put (:request-method request))
(let [todo-in (parse-string (slurp (:body request)) true)
todo (app/todo-add todo-in)]
{:headers {"Location" (str URI-BASE "/" (:id todo))}})))
;; HTML handler
"text/html"
(fn [request]
{:body (xhtml {:lange "en" }
[:body
[:h1 "To-Do List"]
[:ul (map #(todo-short-html
(add-resource-links URI-BASE %))
(app/todos))]])})}
{;; the request methods supported by this resource
:allowed-methods (fn [request] [:get :head :post :put])
;; POSTs with new data will be handled like a PUT
:post-is-create? (fn [request] true)
;; we use the modification date on the most recently modified
;; to-do item as the modification date for the list of items
:last-modified (fn [request]
(app/most-recent-todo-modified))}))
The first map defines the content types our resource accepts and the
functions that handle each type of request. Our first handler accepts
JSON data and can handle either a “GET” or a “PUT” request. You can
see that when the client fetches data we add resource links to each
item and then use the resulting hash-map to create our JSON
output. This output is then placed under the “:body” key of the
response object. It the client is providing new to-do item data with a
“PUT” request, we’ll parse that JSON data into a hash-map and add that
item to our database. We’ll then redirect the client to the URL for
the new to-do item.
Our second entry handles HTML content. When the client makes a “GET”
request for HTML to our resource, we’ll provide an HTML listing of the
items (including a link to the resource for each item). We don’t have
to worry about parsing out the client’s headers and figuring out which
type of content the client wants. Bishop handles that all of that for
us.
We define our callback functions for our resource in the second
hash-map, we provide the information that Bishop needs to handle
incoming requests. We specify the methods that our resource can handle
and instruct it to treat “POST” requests as if the client was doing a
“PUT” for a new item. We also tell Bishop how to correctly generate
the “Last-Modified” header.
There’s much more that Bishop can do for you, take a look at the rest of the code for this service. For instance, when you tell Bishop how
to generate an “ETag” for your resource you make it easy for clients
to fetch the entire resource only when it actually changes. If you run
this sample application and point Google Chrome at it, you’ll notice
that the first time you view a to-do item in the browser it fetches
the item from the server. Subsequent fetches are served from Chrome’s
local cache: Chrome uses the ETag header to do a conditional fetch and
as long as the resource isn’t updated, Bishop responds with a “304 Not
Modified.”
Comments
blog comments powered by Disqus