Presenting liberator-transit
As part of my prepartion for the core.async workshop I’ll be giving at Strange Loop, I have come across the need for a RESTful API for a web application I am creating. Naturally, I thought this would be a good chance to try out Liberator and Transit. I was pleased to see how easy Liberator is to use. Moreover, it was nearly trivial to add Transit serialisation support to Liberator. Nonetheless, I thought it would be good to wrap it all up in a library ready for reuse, which I have unimaginatively called liberator-transit.
How to use it
Using liberator-transit is straightforward:
- Add it to your project as a dependency.
- Require the
io.clojure.liberator-transit
namespace - Accept the
application/transit+json
and/orapplication/transit+msgpack
media types.
For example, given the following project.clj
:
(defproject liberator-transit-demo "0.1.0-SNAPSHOT" :dependencies [[org.clojure/clojure "1.6.0"] [compojure "1.1.8"] [com.cognitect/transit-clj "0.8.247"] [liberator "0.12.0"] [ring/ring-core "1.3.0"] [io.clojure/liberator-transit "0.1.0"]] :plugins [[lein-ring "0.8.11"]] :ring {:handler liberator-transit-demo.core/app})
And the given liberator_transit_demo/core.clj
:
(ns liberator-transit-demo.core (:require [compojure.core :refer [ANY defroutes]] [io.clojure.liberator-transit] [liberator.core :refer [defresource]] [ring.middleware.params :refer [wrap-params]])) (defresource hello [name] :available-media-types ["text/plain" "application/edn" "application/json" "application/transit+json" "application/transit+msgpack"] :handle-ok {:time (System/currentTimeMillis) :greeting (str "Hello, " name \!)}) (defroutes routes (ANY "/:name" [name] (hello name))) (def app (wrap-params routes))
You can run the server using lein-ring:
$ lein ring server-headless 2014-08-08 23:12:23.330:INFO:oejs.Server:jetty-7.6.8.v20121106 2014-08-08 23:12:23.349:INFO:oejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:3000 Started server on port 3000
Now, it is possible to see what the different encodings look like:
$ curl http://localhost:3000/text time=1407557612822 greeting=Hello, text! $ curl -H "Accept: application/edn" http://localhost:3000/edn {:time 1407558307759, :greeting "Hello, edn!"} $ curl -H "Accept: application/json" http://localhost:3000/json {"time":1407558370116,"greeting":"Hello, json!"} $ curl -H "Accept: application/transit+json" http://localhost:3000/transit-json ["^ ","~:time",1407558488590,"~:greeting","Hello, transit-json!"] $ curl -H "Accept: application/transit+json;verbose" http://localhost:3000/transit-json-verbose {"~:time":1407558554647,"~:greeting":"Hello, transit-json-verbose!"} $ curl -s -H "Accept: application/transit+msgpack" http://localhost:3000/transit-msgpack | xxd 0000000: 82a6 7e3a 7469 6d65 cf00 0001 47b9 08d1 ..~:time....G... 0000010: 52aa 7e3a 6772 6565 7469 6e67 b748 656c R.~:greeting.Hel 0000020: 6c6f 2c20 7472 616e 7369 742d 6d73 6770 lo, transit-msgp 0000030: 6163 6b21 ack!
There’s just a couple of things to note:
- The default Transit/JSON encoding is the non-verbose format. By adding the “verbose” to the “Accept” header, liberator-transit emits the verbose JSON encoding.
- Since the MessagePack encoding is a binary format, I pipe the output through
xxd
. Otherwise, unprintable characters are output.
How it works
Liberator has a built-in mechanism for formatting output of sequences and maps
automatically depending on the headers in the request, as seen above.
Moreover, it’s possible to extend this easily by adding new methods to the
render-map-generic
and render-seq-generic
multimethods in
liberator.representation
. These methods dispatch on the media type that has
been negotiated and take two arguments: the data to render and the context.
Getting the verbose output to work was just a touch trickier. The parameters to the media type are stripped by Liberator. As a result, it is necessary to actually examine the request headers that were placed in the Ring map. Fortunately, this is easy to do as it is a part of the incoming context.
Here is an example:
(defmethod render-map-generic "application/transit+json" [data context] (let [accept-header (get-in context [:request :headers "accept"])] (if (pos? (.indexOf accept-header "verbose")) (render-as-transit data :json-verbose) (render-as-transit data :json))))
Further work
This library is really quite simple. I spent far more time creating
test.check
generators for the various Transit types than I did on the library
itself. It mostly exists to provide an even easier way to add Transit to
Liberator.
Nonetheless, if other people find a need for it, there is possibly room for improvement, such as:
- Is there a better way to handle the request for verbose JSON?
- Should the library be configurable? If so, what should be configured and what is the best way to do it?
TrackBacks
No trackbacks, yet.
Trackbacks are closed for this story.
Comments
No comments, yet.
Comments are closed for this story.