4

I'm looking for a way to let a ring server - by request - grab an image from an URL, process it and serve the modified version.

This is how I got so far:

(require '[clj-http.client :as client]
         '[ring.adapter.jetty :refer [run-jetty])
(import javax.imageio.ImageIO)

(def handler (-> (client/get "http://.../some-img.jpg" {:as :stream})
                 :body
                 ImageIO/read
                 .. ;; do some processing with the BufferedImage here
                 .. ;; and serve the modified version))

(run-jetty handler {:port 55555})

Especially I'm having troubles performing the last step inside the threading macro.

Anton Harald
  • 5,772
  • 4
  • 27
  • 61
  • Could this be addressed by adding some middleware that, finding a function as the :body part of the response, attempts to call that function passing it an OutputStream (i.e. the stream to write the image to)? I have minimal experience with Ring (less with middleware) but I do something similar using an in-house BufferedImage pool library and (an also in-house) Ring-inspired web-tier - only it is all in Java. In my case it is often the same image, which is copied to the pooled image, 'drawn on', and then streamed to the client, never having existed as a file. – Paul Jun 06 '16 at 16:01

1 Answers1

5

To return bytes as the response in Ring you need to provide either java.io.File or java.io.InputStream as the body content:

(defn jpeg-response [image-data]
  (-> image-data
    (ring.util.response/response)
    (ring.util.response/content-type "image/jpeg")))

I haven't found a way to obtain an InputStream from BufferedImage directly without creating an intermediate byte arrays. Maybe this is a limitation of the Java Image API due to complexity required to implement a "pull" approach to get a stream of image bytes in the desired format.

Instead there is a "push" API where ImageIO.write method requires some kind of output for image bytes. It might be a java.io.File or java.io.OutputStream. Thus you need to first store the bytes somewhere (java.io.File or java.io.ByteArrayOutputStream) and then use them as your response body:

With file (so storing the image on the disk first - you need to remember to clean it up):

(let [image (ImageIO/read "...")
      image-file (java.io.File. "...")]
  (ImageIO/write image "jpg" image-file)
  (jpeg-response image-file))

In-memory byte array:

(let [image (ImageIO/read "...")
      image-output-stream (ByteArrayOutputStream.)]
  (ImageIO/write image "jpg" image-output-stream)
  (jpeg-response (ByteArrayInputStream. (.toByteArray image-output-stream))))
Piotrek Bzdyl
  • 12,965
  • 1
  • 31
  • 49