diff --git a/README.org b/README.org index a767d5c0..93bc7d0f 100644 --- a/README.org +++ b/README.org @@ -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]] @@ -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 diff --git a/src/clj_http/client.clj b/src/clj_http/client.clj index 7370e35f..7a09611d 100644 --- a/src/clj_http/client.clj +++ b/src/clj_http/client.clj @@ -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 @@ -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))) diff --git a/test/clj_http/test/client_test.clj b/test/clj_http/test/client_test.clj index f284572d..3431c46c 100644 --- a/test/clj_http/test/client_test.clj +++ b/test/clj_http/test/client_test.clj @@ -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