ring-clojure/ring

Exception thrown by Ring when using muuntaja and Ring >=1.9.0 with Jetty and async handlers

Closed this issue · 0 comments

The following code creates an async handler which responds with the given static EDN response to any request, using muuntaja to transform the response (our actual code does more in the transform, but this is the minimal case to cause the error):

(require '[clojure.core.async :as a]
         '[muuntaja.core :as m]
         '[muuntaja.middleware :as mm]
         '[ring.adapter.jetty :as jetty])

(defn async-handler []
  (fn [request respond raise]
    (println request)
    (let [respond-ch (a/chan 1)]
      (a/put! respond-ch {:status 200 :body {:foo :bar}})
      (a/go
        (let [response (a/<! respond-ch)]
          (if response
            (respond response)
            (respond {:status 500})))))))

(def muuntaja-options
  (assoc m/default-options :default-format "application/edn"
                           :return :bytes))

(defn handler-chain []
  (-> (async-handler)
      (mm/wrap-format-response muuntaja-options)))

(def j (jetty/run-jetty (handler-chain) {:host "127.0.0.1"
                                         :port 8080
                                         :daemon? false
                                         :async? true
                                         :join? false}))

curl http://localhost:8080 of this when running Ring 1.8.2 results in a 200 OK response with the body content {:foo :bar}, as expected. Move to Ring 1.9.0 or above, and you get the following stacktrace spat out in the REPL where the server is running, and an empty (but still 200 OK) response:

Exception in thread "async-dispatch-1" clojure.lang.ArityException: Wrong number of args (2) passed to: ring.util.servlet/make-output-stream/fn--1683
	at clojure.lang.AFn.throwArity(AFn.java:429)
	at clojure.lang.AFn.invoke(AFn.java:36)
	at ring.util.servlet.proxy$java.io.FilterOutputStream$ff19274a.write(Unknown Source)
	at muuntaja.protocols$eval1577$fn__1578.invoke(form-init12551137701039320095.clj:21)
	at ring.core.protocols$fn__23532$G__23527__23541.invoke(protocols.clj:8)
	at ring.util.servlet$update_servlet_response.invokeStatic(servlet.clj:108)
	at ring.util.servlet$update_servlet_response.invoke(servlet.clj:93)
	at ring.adapter.jetty$async_jetty_respond$fn__1726.invoke(jetty.clj:38)
	at muuntaja.middleware$wrap_format_response$fn__1625$fn__1626.invoke(middleware.clj:134)
	at user$async_handler$fn__1762$fn__1782$state_machine__8465__auto____1791$fn__1793.invoke(form-init12551137701039320095.clj:14)
	at user$async_handler$fn__1762$fn__1782$state_machine__8465__auto____1791.invoke(form-init12551137701039320095.clj:11)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invokeStatic(ioc_macros.clj:978)
	at clojure.core.async.impl.ioc_macros$run_state_machine.invoke(ioc_macros.clj:977)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invokeStatic(ioc_macros.clj:982)
	at clojure.core.async.impl.ioc_macros$run_state_machine_wrapped.invoke(ioc_macros.clj:980)
	at user$async_handler$fn__1762$fn__1782.invoke(form-init12551137701039320095.clj:11)
	at clojure.lang.AFn.run(AFn.java:22)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
	at clojure.core.async.impl.concurrent$counted_thread_factory$reify__3851$fn__3852.invoke(concurrent.clj:29)
	at clojure.lang.AFn.run(AFn.java:22)
	at java.base/java.lang.Thread.run(Thread.java:835)

This would seem to be an error introduced in the 1.9.x codebase with the way it handles and processes the response body in an async handler before passing it to muuntaja, which also wants to mess with the body. I don't know if other body-processing response handlers would also be affected.

Many thanks to @weavejester for doing a lot of the work diagnosing this on a Reddit thread yesterday.