Mike Flynn
Mike Flynn

Reputation: 2223

How to read a binary file's contents in to a string in Clojure?

I'm creating a static file server in Clojure with Compojure and I'm stuck on reading an image from the filesystem and displaying that image through a Compojure route.

slurp unfortunately doesn't handle binary data very well, and I've tried this 100 different ways since, but this is my latest failed attempt:

(defn image-output [filepath]
  (try
    (let [contents (apply str (with-open [rdr (io/reader filepath)]
      (into #{} (line-seq rdr))))]
      {
        :status 200
        :headers 
        {
          "Content-Type" "image/jpeg", 
          "Content-Length" "",
          "Cache-Control" "",
          "Expires" ""
        }
        :body contents
      }))
    (catch Exception e {:status 404})))

(defn endpoint_view [params]
  (if (contains? params :bucket)
    (image-output (join "/" [data_path (:bucket params) (:dir params) (:filename params)]))))

(defroutes main-routes
  (GET "/view/:bucket/:dir/:filename" {params :params} (endpoint_view params))
  (route/files "/")
  (route/resources "/s" {:root "./public/s"})
  (route/not-found "Page not found"))

It seems this current attempt suffers the same fate as using slurp, where I can echo the contents string and it's and encoded string, but when I change the content-type to image/jpeg it's a broken image.

I spent all day yesterday Google searching, but none of the examples accomplished the same goal, and while they helped me understand a little more about Java IO, they weren't clear enough to help me get where I needed to go, or produced the same results I was already getting (example: Best way to read contents of file into a set in Clojure).

(Imaginary bonus points if you can tell me how to get the content type from the filepath as well as that's my next question!)

Upvotes: 6

Views: 1143

Answers (1)

amalloy
amalloy

Reputation: 91857

Just make the body be (io/file filepath) - Ring is perfectly happy to serve files for you.

Edit for bonus points: you can use ring.middleware.file-info/wrap-file-info to get file metadata for the files you return. Or, you could just serve a whole directory with (compojure.route/files "/public"), which does all this mess for you.

Upvotes: 7

Related Questions