Skip to content

Commit 28efb3f

Browse files
committed
Wrap the response in a future when the property :async-future is set
1 parent 4f61aeb commit 28efb3f

File tree

3 files changed

+90
-1
lines changed

3 files changed

+90
-1
lines changed

README.org

Lines changed: 17 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]]
@@ -444,12 +445,27 @@ start an async request is easy, for example:
444445

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

448+
*** Async Futures
449+
Alternatively, if you prefer working with Futures over callbacks for async
450+
requests, there's an option to wrap the response in 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:
458+
459+
1. on success, returns the response map
460+
2. on error, throws an ExecutionException
461+
3. on cancellation, throws a CancellationException
462+
447463
*** Cancelling Requests
448464
:PROPERTIES:
449465
:CUSTOM_ID: cancelling-requests
450466
:END:
451467

452-
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=
468+
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=
453469
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:
454470

455471
#+BEGIN_SRC clojure

src/clj_http/client.clj

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
[slingshot.slingshot :refer [throw+]])
1414
(:import (java.io InputStream File ByteArrayOutputStream ByteArrayInputStream EOFException BufferedReader)
1515
(java.net URL UnknownHostException)
16+
(java.util.concurrent Future ExecutionException CancellationException)
1617
(org.apache.http.entity BufferedHttpEntity ByteArrayEntity
1718
InputStreamEntity FileEntity StringEntity)
1819
(org.apache.http.impl.conn PoolingHttpClientConnectionManager)
@@ -1135,6 +1136,30 @@
11351136
(throw (IllegalArgumentException. "If :async? is true, you must pass respond and raise")))
11361137
(client req respond raise))
11371138

1139+
(opt req :async-future)
1140+
(let [result (promise)
1141+
unwrap (fn [[type response-or-error]]
1142+
(case type
1143+
:respond response-or-error
1144+
:raise (throw (ExecutionException. response-or-error))
1145+
:cancelled (throw (CancellationException. "User cancelled request"))))
1146+
respond #(deliver result [:respond %])
1147+
raise #(deliver result [:raise %])
1148+
oncancel #(deliver result [:cancelled])
1149+
basic-future (client (-> req
1150+
(dissoc :async-future :async-future?)
1151+
(assoc :async true
1152+
:oncancel oncancel))
1153+
respond
1154+
raise)]
1155+
(reify
1156+
Future
1157+
(get [_] (unwrap (deref result)))
1158+
(get [_ timeout unit] (unwrap (deref result timeout unit)))
1159+
(isCancelled [_] (.isCancelled basic-future))
1160+
(isDone [_] (.isRealized result))
1161+
(cancel [_ interrupt?] (.cancel basic-future interrupt?))))
1162+
11381163
:else
11391164
(client req)))
11401165

@@ -1173,6 +1198,9 @@
11731198
* :respond
11741199
* :raise
11751200
1201+
To make an async HTTP request and wrap the result in a future, set the
1202+
key :async-future to true.
1203+
11761204
The following additional behaviors are also automatically enabled:
11771205
* Exceptions are thrown for status codes other than 200-207, 300-303, or 307
11781206
* Gzip and deflate responses are accepted and decompressed

test/clj_http/test/client_test.clj

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

170+
(deftest ^:integration roundtrip-async-future
171+
(run-server)
172+
(testing "roundtrip with scheme as 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+
(testing "roundtrip with scheme as 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+
(testing "response parsing"
186+
(let [params {:a "1" :b "2"}]
187+
(doseq [[content-type read-fn]
188+
[[nil (comp parse-form-params slurp)]
189+
[:x-www-form-urlencoded (comp parse-form-params slurp)]
190+
[:edn (comp read-string slurp)]
191+
[:transit+json #(client/parse-transit % :json)]
192+
[:transit+msgpack #(client/parse-transit % :msgpack)]]]
193+
(let [resp (request {:uri "/post"
194+
:as :stream
195+
:method :post
196+
:content-type content-type
197+
:flatten-nested-keys []
198+
:form-params params
199+
:async-future? true})]
200+
(is (= 200 (:status @resp)))
201+
(is (= "close" (get-in @resp [:headers "connection"])))
202+
(is (= params (read-fn (:body @resp))))))))
203+
(testing "error handling"
204+
(let [resp (request {:uri "/error" :method :get
205+
:async-future? true})]
206+
(is (thrown? java.util.concurrent.ExecutionException
207+
@resp))))
208+
(testing "can be cancelled"
209+
(let [resp (request {:uri "/timeout" :method :get
210+
:async-future? true})]
211+
(.cancel resp false)
212+
(is (thrown? java.util.concurrent.CancellationException
213+
@resp)))))
214+
170215
(def ^:dynamic *test-dynamic-var* nil)
171216

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

0 commit comments

Comments
 (0)