Skip to content

Commit

Permalink
Add option :async-future to return a future-wrapped response
Browse files Browse the repository at this point in the history
The existing :async option returns a future of org.apache.http.HttpResponse,
which is not the ideal type for Clojurists.

This new option :async-future returns a future of a clj-http response.
  • Loading branch information
rymndhng committed May 16, 2020
1 parent 52cde30 commit c6729a4
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 1 deletion.
13 changes: 12 additions & 1 deletion README.org
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
- [[#post][POST]]
- [[#delete][DELETE]]
- [[#async-http-request][Async HTTP Request]]
- [[#async-futures][Async Futures]]
- [[#cancelling-requests][Cancelling Requests]]
- [[#coercions][Coercions]]
- [[#input-coercion][Input coercion]]
Expand Down Expand Up @@ -445,12 +446,22 @@ start an async request is easy, for example:

All exceptions thrown during the request will be passed to the raise callback.

*** Async Futures
Alternatively, if you prefer working with Futures, you can get the async response in the shape of a Future:

#+begin_src clojure
(def fut (client/get "http://example.com" {:async-future? true}))
@fut
#+end_src

Deref-ing the future will return the response map or throw a [[https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutionException.html][ExecutionException]] on error.

*** Cancelling Requests
:PROPERTIES:
:CUSTOM_ID: cancelling-requests
:END:

Calls to the http methods with =:async true= return an Apache [[https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/concurrent/BasicFuture.html][BasicFuture]] that you can call =.get=
Calls to the http methods with =:async true or :async-future true= return an Apache [[https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/concurrent/BasicFuture.html][BasicFuture]] that you can call =.get=
or =.cancel= on. See the Javadocs for =BasicFuture= [[https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/concurrent/BasicFuture.html][here]]. For instance:

#+BEGIN_SRC clojure
Expand Down
12 changes: 12 additions & 0 deletions src/clj_http/client.clj
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,15 @@
Automatically bound when `with-middleware` is used."
default-middleware)

(defn- async-future [client req]
(let [fut (org.apache.http.concurrent.BasicFuture. nil)
respond #(.completed fut %)
raise #(.failed fut %)
cancel #(.cancel fut)
req (assoc req :async true :oncancel cancel)]
(client req respond raise)
fut))

(defn- async-transform
[client]
(fn
Expand All @@ -1123,6 +1132,9 @@
(throw (IllegalArgumentException. "If :async? is true, you must pass respond and raise")))
(client req respond raise))

(opt req :async-future)
(async-future client (dissoc req :async-future :async-future?))

:else
(client req)))

Expand Down
39 changes: 39 additions & 0 deletions test/clj_http/test/client_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,45 @@
(is (= params (read-fn (:body @resp))))
(is (not (realized? exception)))))))))

(deftest ^:integration roundtrip-async-future
(run-server)
;; roundtrip with scheme as a keyword
(let [resp (request {:uri "/get" :method :get
:async-future? true})]
(is (= 200 (:status @resp)))
(is (= "close" (get-in @resp [:headers "connection"])))
(is (= "get" (:body @resp))))
;; roundtrip with scheme as a string
(let [resp (request {:uri "/get" :method :get
:scheme "http"
:async-future? true})]
(is (= 200 (:status @resp)))
(is (= "close" (get-in @resp [:headers "connection"])))
(is (= "get" (:body @resp))))
;; error handling
(let [resp (request {:uri "/error" :method :get
:async-future? true})]
(is (thrown? java.util.concurrent.ExecutionException
@resp)))

(let [params {:a "1" :b "2"}]
(doseq [[content-type read-fn]
[[nil (comp parse-form-params slurp)]
[:x-www-form-urlencoded (comp parse-form-params slurp)]
[:edn (comp read-string slurp)]
[:transit+json #(client/parse-transit % :json)]
[:transit+msgpack #(client/parse-transit % :msgpack)]]]
(let [resp (request {:uri "/post"
:as :stream
:method :post
:content-type content-type
:flatten-nested-keys []
:form-params params
:async-future? true})]
(is (= 200 (:status @resp)))
(is (= "close" (get-in @resp [:headers "connection"])))
(is (= params (read-fn (:body @resp))))))))

(def ^:dynamic *test-dynamic-var* nil)

(deftest ^:integration async-preserves-dynamic-variable-bindings
Expand Down

0 comments on commit c6729a4

Please sign in to comment.