Skip to content

Instantly share code, notes, and snippets.

@mszajna
Last active May 20, 2020 11:39
Show Gist options
  • Save mszajna/7d5d7ed8924dd62f84a981c6fc18ed1c to your computer and use it in GitHub Desktop.
Save mszajna/7d5d7ed8924dd62f84a981c6fc18ed1c to your computer and use it in GitHub Desktop.
(defn async? [response]
(instance? java.util.concurrent.CompletionStage response))
(defn- cs-handle [cs f]
(.handle ^java.util.concurrent.CompletionStage cs
(reify java.util.function.BiFunction
(apply [_ v t] (f v t)))))
(defn wrap-completable-future
"Adapts a 1-arity handler, returning either a response map or one wrapped
in CompletionStage, to a 3-arity handler."
[handler]
(fn [request respond raise]
(let [response (handler request)]
(if (async? response)
(cs-handle response
(fn [response-map ex]
(if ex (raise ex) (respond response-map))))
(respond response)))))
(defn wrap-3arity
"Turns an asynchronous 3-arity handler into a CompletionStage based 1-arity
one."
[handler]
(fn [request]
(let [cf (new java.util.concurrent.CompletableFuture)]
; exposed to help catch callback exceptions
(handler (assoc request :completable-future cf)
(fn [response] (.complete cf response))
(fn [ex] (.completeExceptionally cf ex)))
cf)))
(defn wrap-callback-exceptions
"Handles bubbling callback exceptions. Note that logically, this is inverse of
try-catch - it catches exceptions from middleware that was applied after this,
not before."
[handler on-callback-exception]
(fn [request respond raise]
(handler request
(fn [response] (try (respond response)
(catch Throwable t
(on-callback-exception t request))))
(fn [ex] (try (raise ex)
(catch Throwable t
(on-callback-exception t request)))))))
(defn adapt
"Adapts the asynchronous 3-arity middleware to one that supports
CompletionStage, and applies it to the handler with args.
Exceptions thrown in middleware's callbacks propagate correctly."
[handler middleware & args]
(-> handler
wrap-completable-future
(wrap-callback-exceptions (fn [t req]
(when-let [cf (:completable-future req)]
(.completeExceptionally cf t))))
(#(apply %2 %1 %3) middleware args)
wrap-3arity))
(comment
(defn a-handler [request]
(java.util.concurrent.CompletableFuture/completedFuture
{:status 200 :body (get-in request [:query-params "q"])}))
(def app-handler
(-> a-handler ; 1-arity CF-returning handler
(adapt ring.middleware.head/wrap-head) ; adapt wrap-head to return a CF
ring.middleware.params/wrap-params ; use compatible 1-arity directly
wrap-completable-future)) ; adapt for use with ring-jetty
(app-handler {:query-string "q=test"} println println))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment