Search code examples
clojurering

Intermittent error serving a binary file with Clojure/Ring


I am building an event collector in Clojure for Snowplow (using Ring/Compojure) and am having some trouble serving a transparent pixel with Ring. This is my code for sending the pixel:

(ns snowplow.clojure-collector.responses
  (:import (org.apache.commons.codec.binary Base64)
           (java.io ByteArrayInputStream)))

(def pixel-bytes (Base64/decodeBase64 (.getBytes "R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==")))
(def pixel (ByteArrayInputStream. pixel-bytes))

(defn send-pixel
   []
    {:status  200
     :headers {"Content-Type"   "image/gif"}
     :body    pixel})

When I start up my server, the first time I hit the path for send-pixel, the pixel is successfully delivered to my browser. But the second time - and every time afterwards - Ring sends no body (and content-length 0). Restart the server and it's the same pattern.

A few things it's not:

  1. I have replicated this using wget, to confirm the intermittent-ness isn't a browser caching issue
  2. I generated the "R01GOD..." base64 string at the command-line (cat original.gif | base64) so know there is no issue there
  3. When the pixel is successfully sent, I have verified its contents are correct (diff original.gif received-pixel.gif)

I'm new to Clojure - my guess is there's some embarrassing dynamic gremlin in my code, but I need help spotting it!


Solution

  • I figured out the problem in the REPL shortly after posting:

    user=> (import (org.apache.commons.codec.binary Base64) (java.io ByteArrayInputStream))
    java.io.ByteArrayInputStream
    user=> (def pixel-bytes (Base64/decodeBase64 (.getBytes "R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==")))
    #'user/pixel-bytes
    user=> (def pixel (ByteArrayInputStream. pixel-bytes))
    #'user/pixel
    user=> (slurp pixel-bytes)
    "GIF89a!�\n,L;"
    user=> (slurp pixel-bytes)
    "GIF89a!�\n,L;"
    user=> (slurp pixel)
    "GIF89a!�\n,L;"
    user=> (slurp pixel)
    ""
    

    So basically the problem was that the ByteArrayInputStream was getting emptied after the first call. Mutable data structures!

    I fixed the bug by generating a new ByteArrayInputStream for each response, with:

        :body    (ByteArrayInputStream. pixel-bytes)}))