Scott Feeney writes about Liberator

(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: lawrence@krubner.com, or follow me on Twitter.

This is a very good write-up about Liberator, the Clojure library for RESTful APIs. This example he gives was a revelation to me:

Here’s a longer example, a post on a blogging site. Anyone can read a blog post (with a GET), but only the user who wrote it can edit it (with a PUT) or DELETE it.

(defresource blog-post-resource [id]
:allowed-methods [:get :put :delete]

;; Return 503 Service Unavailable if DB is down.
:service-available? (fn [_] (is-the-database-up?))

;; Return 401 Unauthorized if method is not GET and a correct
;; authorization header isn’t included.
:authorized?
(fn [ctx]
(if (= (get-in ctx :request :request-method) :get)
true ; Anyone can simply read the post, no auth required.

;; If we’re editing or deleting, then first decode the HTTP
;; Authorization header, parsing the username and
;; password…
(when-let [[user pass]
(some-> ctx
(get-in [:request :headers “authorization”])
parse-basic-auth)]
;; Then check that they’re correct.
(when (password-correct? user pass)
{:logged-in-user user}))))

;; Return 403 Forbidden if method is not GET and the user who logged
;; in didn’t write this blog post.
:allowed?
(fn [ctx]
(or (= (get-in ctx :request :request-method) :get)
(= (:author (lookup-blog-post id)) (:logged-in-user ctx))))

:handle-ok (fn [ctx] “render the blog post here”)
:put! (fn [ctx] “update the blog post with edited content”)
:delete! (fn [ctx] “delete the blog post”))
That’s a lot at once. I’ll go through bit by bit.

First, service-available? is one of a number of decision functions Liberator will use if you define them. Whether it returns a truthy or a falsy value determines which path Liberator will take through its HTTP state machine. If you look at the transition graph (huge image — requires panning and zooming), you’ll see that service-available? is the first decision point. If false, Liberator immediately returns a “503 Service Unavailable” error and won’t call our other functions.

Next we check authorized?, then allowed?. These are easily confused. Basically, “authorized” means the server knows who you are, and “allowed” means you’re permitted to do the thing you’re trying to do.

If it’s a GET request, we figure the post is public and anyone can read it, so we simply return true in both functions.

Otherwise, authorized? looks for “Authorization” in the request headers, and upon finding it, tries to parse it as HTTP basic authentication, using a utility function parse-basic-auth that I omit here. That produces a username and password. We send them to a hypothetical password-correct? function.

Recall that when returns nil if its condition is untrue, so if the auth header is missing, or the password isn’t correct, we return nil and the request is considered not authorized.

In case the user/password combination is correct, we don’t return true, but rather a map:

{:logged-in-user “username”}
This is key to understanding Liberator. I fibbed when I said decision functions return truthy or falsy values. They can also return maps. If so, it’s treated as a truthy value and the map is merged into the request context (the ctx argument passed into each function).

So in all future decision, action and handler functions that execute after allowed?, we can refer to (:logged-in-user ctx) and this will evaluate to the username we’re returning.

Much further on in the state graph, after a bunch of decisions I didn’t implement (which will therefore use the default behavior), we eventually get to the side-effecting function put! or delete! which actually makes a change. Or, if it was a GET, we proceed to handle-ok which renders the blog post. In my example code, these are obviously stubs.

Post external references

  1. 1
    https://scott.mn/2014/01/26/first_thoughts_on_liberator_clojure/
Source