Serving binary files from the database with compoj

2019-04-30 01:47发布

问题:

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?

回答1:

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/*" [*] ...)


回答2:

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)))))