I have the following routes definition:
(require '[compojure.core :as ccore]
'[ring.util.response :as response])
(def *main-routes*
(ccore/defroutes avalanche-routes
(ccore/GET "/" [] "Hello World 2")
(ccore/GET "/images/:id" [id] (get-image-response id))))
In this example, requesting /
works like a charm and returns the expected Hello World 2
.
The get-images-response method is defined like this:
(defn get-image-response
[id]
(let [record (db/get-image id false)]
(-> (response/response (:data record))
(response/content-type (:content-type record))
(response/header "Content-Length" (:size record)))))
I get a 404 though, so the serving of binary files doesn't quite work yet. Any ideas why?
Edit:
Ok, the issue relates to the fact that images are being requested on /images/name.jpg
. As soon as I remove the .jpg
the handler gets called. So the question becomes how do I match on anything but the extension?
Compojure uses clout for route matching. The dot character has a special meaning in clout routes. It represents a token separator, similarly to the slash character. The following characters all have this meaning in clout: / . , ; ?
.
That means that a route like "/images/:id"
will not match a uri of the form /images/name.jpg
since images
, name
and jpg
each represent a separate token in clout.
In order to match it, you could compose your route in a number of different ways, depending on your need.
If all your images have the .jpg
extension, the easiest thing to do would be:
(GET "/images/:id.jpg" [id] ...)
If the extension varies you could do the following:
(GET "/images/:name.:extension" [name extension] ...)
If you want to restrict the extension, you can pass compojure/clout a regular expression:
(GET ["/images/:name.:ext", :ext #"(jpe?g|png|gif)"] [name ext] ...)
You could also go with a wildcard, which is less precise and would match any uri starting with /images/
:
(GET "/images/*" [*] ...)
The real answer in this case was that there was a bug in the clojure-couchdb library. The patch is available on github here.
It boils down to adding the {:as :byte-array} map parameter and value to the request sent via clj-http to couch's api.
The other issue in my code was that ring
doesn't really know what to do with byte-arrays when it's rendering them. Rather than patching ring, I just wrapped the byte-array into a java.io.ByteArrayInputStream
. Here is the complete code for handling the download:
(defn get-image-response
[id]
(let [record (db/get-image id false)]
(-> (response/response (new java.io.ByteArrayInputStream (:data record)))
(response/content-type (:content-type (:content-type record)))
(response/header "Content-Length" (:size record)))))