Search code examples
httpclojureclj-http

How can I make an http call in clojure/ring?


My web client (written in cljs) connects to backend (written in clj) which needs to make a few third party API calls. It has to be done on the server and then the result should be transformed in a specific way and sent back to the client.

Here's my handler for one of the urls

(defn get-orders [req]
  (let [{:keys [sig uri]} (api-signature :get-orders)]
    (client/get uri
                {:async? true}
                (fn [response] {:body "something"})
                (fn [exception] {:body "error"}))))

Instead of returning {:body "something"}, it is returning the following error:

No implementation of method: :render of protocol: #'compojure.response/Renderable found for class: org.apache.http.impl.nio.client.FutureWrapper

What am I doing wrong?


Solution

  • When you specify {:async? true}, clj-http.client/get will return a future which is a FutureWrapper in the error message you got.

    So if you don't need async, don't use it. This is an example of a synchronous ring handler which calls a third-party url and returns the response that got back.

    (defn handler [request]
      (response {:result (client/get "http://example.com")}))
    

    If you really need async, use async version of ring handler.

    (defn handler [request respond raise]
      (client/get "http://example.com"
                  {:async? true}
                  (fn [response] (respond {:body "something"}))
                  (fn [exception] (raise {:body "error"}))))
    

    Don't forget to config webserver adapter to use the async handler. For example, for Jetty, set :async? flag to true like so

    (jetty/run-jetty app {:port 4000 :async? true :join? false})
    

    If you want to concurrently call to multiple third-party urls and return once to web client, use promise to help

    (defn handler [request]
      (let [result1 (promise)
            result2 (promise)]
        (client/get "http://example.com/"
                    {:async? true}
                    (fn [response] (deliver result1 {:success true :body "something"}))
                    (fn [exception] (deliver result1 {:success false :body "error"})))
        (client/get "http://example.com/"
                    {:async? true}
                    (fn [response] (deliver result2 {:success true :body "something"}))
                    (fn [exception] (deliver result2 {:success false :body "error"})))
        (cond
          (and (:success @result1)
               (:success @result2))
          (response {:result1 (:body @result1)
                     :result2 (:body @result2)})
    
          (not (:success @result1))
          (throw (ex-info "fail1" (:body @result1)))
    
          (not (:success @result2))
          (throw (ex-info "fail2" (:body @result2))))))