Compojure trouble displaying an image

571 views Asked by At

I've been struggling with this for over two days now and don't seem to find a solution.

So all I am trying to do is to display an image in the browser, but when I am calling the endpoint this is what happens:

Cannot JSON encode object of class: class java.io.File

Endpoint

(context "/servers" []
           :datomic true

           (GET "/:id/graph/:panel-type" {db :db user :user}
                :summary "Return a server graph image"
                :path-params [id :- Long
                              panel-type :- String]
                (let [file-path (str panel-type ".png")

                      result (-> (response/response (clojure.java.io/file file-path))
                                 (response/content-type "image/png")))]

                  (ok result)))) ;; ring.util.http-response

I am new to compojure API, but something makes me think there is a problem with middleware implementation - custom formats?

Middleware

(defn wrap-formats [handler]
  (let [wrapped (wrap-restful-format
                  handler
                  {:formats [:json-kw :transit-json :transit-msgpack]})]
    (fn [request]
      ;; disable wrap-formats for websockets
      ;; since they're not compatible with this middleware
      ((if (:websocket? request) handler wrapped) request))))

Stacktrace

com.fasterxml.jackson.core.JsonGenerationException: Cannot JSON encode object of class: class java.io.File: cpu.png
at cheshire.generate$generate.invokeStatic(generate.clj:152)
at cheshire.generate$generate.invoke(generate.clj:116)
at cheshire.generate$generate.invokeStatic(generate.clj:122)
at cheshire.generate$generate.invoke(generate.clj:116)
at cheshire.core$generate_string.invokeStatic(core.clj:74)
at cheshire.core$generate_string.invoke(core.clj:49)
at ring.middleware.format_response$make_json_encoder$fn__20198.invoke(format_response.clj:221)
at ring.middleware.format_response$wrap_format_response$fn__20186.invoke(format_response.clj:204)
at ring.middleware.keyword_params$wrap_keyword_params$fn__4302.invoke(keyword_params.clj:36)
at ring.middleware.nested_params$wrap_nested_params$fn__20350.invoke(nested_params.clj:89)
at ring.middleware.params$wrap_params$fn__4376.invoke(params.clj:67)
at compojure.api.middleware$wrap_options$fn__23551.invoke(middleware.clj:74)
at compojure.api.routes.Route.invoke(routes.clj:74)
at clojure.lang.Var.invoke(Var.java:381)
at compojure.core$routing$fn__4157.invoke(core.clj:185)
at clojure.core$some.invokeStatic(core.clj:2674)
at clojure.core$some.invoke(core.clj:2665)
at compojure.core$routing.invokeStatic(core.clj:185)
at compojure.core$routing.doInvoke(core.clj:182)
at clojure.lang.RestFn.applyTo(RestFn.java:139)
at clojure.core$apply.invokeStatic(core.clj:659)
at clojure.core$apply.invoke(core.clj:652)
at compojure.core$routes$fn__4161.invoke(core.clj:192)
at clojure.lang.Var.invoke(Var.java:381)
at ring.middleware.reload$wrap_reload$fn__46696.invoke(reload.clj:39)
at selmer.middleware$wrap_error_page$fn__46709.invoke(middleware.clj:9)
at prone.middleware$wrap_exceptions$fn__46879.invoke(middleware.clj:126)
at buddy.auth.middleware$wrap_authentication$fn__44919.invoke(middleware.clj:42)
at buddy.auth.middleware$wrap_authorization$fn__44927.invoke(middleware.clj:94)
at ring.middleware.cors$handle_cors.invokeStatic(cors.clj:146)
at ring.middleware.cors$handle_cors.invoke(cors.clj:135)
at ring.middleware.cors$wrap_cors$fn__45217.invoke(cors.clj:160)
at ring.middleware.flash$wrap_flash$fn__45283.invoke(flash.clj:39)
at ring.middleware.session$wrap_session$fn__45465.invoke(session.clj:108)
at ring.middleware.keyword_params$wrap_keyword_params$fn__4302.invoke(keyword_params.clj:36)
at ring.middleware.nested_params$wrap_nested_params$fn__20350.invoke(nested_params.clj:89)
at ring.middleware.multipart_params$wrap_multipart_params$fn__45551.invoke(multipart_params.clj:172)
at ring.middleware.params$wrap_params$fn__4376.invoke(params.clj:67)
at ring.middleware.cookies$wrap_cookies$fn__45416.invoke(cookies.clj:175)
at ring.middleware.absolute_redirects$wrap_absolute_redirects$fn__45638.invoke(absolute_redirects.clj:47)
at ring.middleware.resource$wrap_resource$fn__45567.invoke(resource.clj:37)
at ring.middleware.content_type$wrap_content_type$fn__25387.invoke(content_type.clj:34)
at ring.middleware.default_charset$wrap_default_charset$fn__45610.invoke(default_charset.clj:31)
at ring.middleware.not_modified$wrap_not_modified$fn__25417.invoke(not_modified.clj:53)
at ring.middleware.x_headers$wrap_x_header$fn__45246.invoke(x_headers.clj:22)
at ring.middleware.x_headers$wrap_x_header$fn__45246.invoke(x_headers.clj:22)
at ring.middleware.x_headers$wrap_x_header$fn__45246.invoke(x_headers.clj:22)
at voltage.middleware$wrap_bearer_token$fn__46930.invoke(middleware.clj:72)
at ring.middleware.webjars$wrap_webjars$fn__45738.invoke(webjars.clj:40)
at voltage.middleware$wrap_internal_error$fn__46919.invoke(middleware.clj:37)
at immutant.web.internal.undertow$create_http_handler$reify__57634.handleRequest(undertow.clj:239)
at org.projectodd.wunderboss.web.undertow.async.websocket.UndertowWebsocket$2.handleRequest(UndertowWebsocket.java:107)
at io.undertow.server.session.SessionAttachmentHandler.handleRequest(SessionAttachmentHandler.java:68)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:202)
at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:802)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

Any clue on the above is highly appreciated.

2

There are 2 answers

1
Vebjorn Ljosa On BEST ANSWER

The problem is that you're calling both ring.util.response/response and ring.util.http-response/ok:

(ok (ring.util.response/response "foo"))
=> {:status 200, :headers {}, :body {:status 200, :headers {}, :body "foo"}}

Using either of them separately works:

(ns compojure.api.examples.handler
  (:require [clojure.java.io :as io]
            [compojure.api.sweet :refer :all]
            [ring.middleware.format :refer [wrap-restful-format]]
            [ring.util.http-response :refer :all]
            [ring.util.response :as response]
            [compojure.api.examples.domain :refer :all]
            [schema.core :as s]))

(defn wrap-formats [handler]
  (let [wrapped (wrap-restful-format
                  handler
                  {:formats [:json-kw :transit-json :transit-msgpack]})]
    (fn [request]
      ;; disable wrap-formats for websockets
      ;; since they're not compatible with this middleware
      ((if (:websocket? request) handler wrapped) request))))

(def app
  (wrap-formats
   (api
    (GET "/lisplogo" []
         (-> (response/response (io/file "lisplogo_256.png"))
             (response/content-type "image/png")))

    (GET "/lisplogo2" []
         (assoc-in (ok (io/file "lisplogo_256.png"))
                   [:headers "Content-Type"]
                   "image/png")))))
0
fevgenym On

If you want to display image, you better return HTML page with <img> linked to your dir with static assets (usually resources/).

If you want to download file, here is an example of Compojure-api handler:

(ns example.app
  (:require [clojure.java.io :as io]
            [ring.util.response :as r]))

(defn file-download [name]
  (let [filename (str "resources/" name)
        bin (io/file filename)]
  (-> (r/response (io/input-stream bin))
      (r/header "Content-Type" "application/octet-stream")
      (r/header "Content-Disposition" "attachment")
      (r/header "Content-Length" (.length bin))
      (r/status 200))))

Adapt and use this function in place where your let goes in endpoint, hope it helps.