Skip to content

Commit c6729a4

Browse files
committed
Add option :async-future to return a future-wrapped response
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.
1 parent 52cde30 commit c6729a4

File tree

3 files changed

+63
-1
lines changed

3 files changed

+63
-1
lines changed

README.org

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
- [[#post][POST]]
3030
- [[#delete][DELETE]]
3131
- [[#async-http-request][Async HTTP Request]]
32+
- [[#async-futures][Async Futures]]
3233
- [[#cancelling-requests][Cancelling Requests]]
3334
- [[#coercions][Coercions]]
3435
- [[#input-coercion][Input coercion]]
@@ -445,12 +446,22 @@ start an async request is easy, for example:
445446

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

449+
*** Async Futures
450+
Alternatively, if you prefer working with Futures, you can get the async response in the shape of a Future:
451+
452+
#+begin_src clojure
453+
(def fut (client/get "http://example.com" {:async-future? true}))
454+
@fut
455+
#+end_src
456+
457+
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.
458+
448459
*** Cancelling Requests
449460
:PROPERTIES:
450461
:CUSTOM_ID: cancelling-requests
451462
:END:
452463

453-
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=
464+
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=
454465
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:
455466

456467
#+BEGIN_SRC clojure

src/clj_http/client.clj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,15 @@
11121112
Automatically bound when `with-middleware` is used."
11131113
default-middleware)
11141114

1115+
(defn- async-future [client req]
1116+
(let [fut (org.apache.http.concurrent.BasicFuture. nil)
1117+
respond #(.completed fut %)
1118+
raise #(.failed fut %)
1119+
cancel #(.cancel fut)
1120+
req (assoc req :async true :oncancel cancel)]
1121+
(client req respond raise)
1122+
fut))
1123+
11151124
(defn- async-transform
11161125
[client]
11171126
(fn
@@ -1123,6 +1132,9 @@
11231132
(throw (IllegalArgumentException. "If :async? is true, you must pass respond and raise")))
11241133
(client req respond raise))
11251134

1135+
(opt req :async-future)
1136+
(async-future client (dissoc req :async-future :async-future?))
1137+
11261138
:else
11271139
(client req)))
11281140

test/clj_http/test/client_test.clj

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,45 @@
167167
(is (= params (read-fn (:body @resp))))
168168
(is (not (realized? exception)))))))))
169169

170+
(deftest ^:integration roundtrip-async-future
171+
(run-server)
172+
;; roundtrip with scheme as a keyword
173+
(let [resp (request {:uri "/get" :method :get
174+
:async-future? true})]
175+
(is (= 200 (:status @resp)))
176+
(is (= "close" (get-in @resp [:headers "connection"])))
177+
(is (= "get" (:body @resp))))
178+
;; roundtrip with scheme as a string
179+
(let [resp (request {:uri "/get" :method :get
180+
:scheme "http"
181+
:async-future? true})]
182+
(is (= 200 (:status @resp)))
183+
(is (= "close" (get-in @resp [:headers "connection"])))
184+
(is (= "get" (:body @resp))))
185+
;; error handling
186+
(let [resp (request {:uri "/error" :method :get
187+
:async-future? true})]
188+
(is (thrown? java.util.concurrent.ExecutionException
189+
@resp)))
190+
191+
(let [params {:a "1" :b "2"}]
192+
(doseq [[content-type read-fn]
193+
[[nil (comp parse-form-params slurp)]
194+
[:x-www-form-urlencoded (comp parse-form-params slurp)]
195+
[:edn (comp read-string slurp)]
196+
[:transit+json #(client/parse-transit % :json)]
197+
[:transit+msgpack #(client/parse-transit % :msgpack)]]]
198+
(let [resp (request {:uri "/post"
199+
:as :stream
200+
:method :post
201+
:content-type content-type
202+
:flatten-nested-keys []
203+
:form-params params
204+
:async-future? true})]
205+
(is (= 200 (:status @resp)))
206+
(is (= "close" (get-in @resp [:headers "connection"])))
207+
(is (= params (read-fn (:body @resp))))))))
208+
170209
(def ^:dynamic *test-dynamic-var* nil)
171210

172211
(deftest ^:integration async-preserves-dynamic-variable-bindings

0 commit comments

Comments
 (0)