Skip to content

Commit 52cde30

Browse files
committed
Improve consistency of async client arguments
Ensures arguments for request & method-functions (get/put/post etc.) have similarly. Fixes #520.
1 parent ab3a85d commit 52cde30

File tree

3 files changed

+187
-138
lines changed

3 files changed

+187
-138
lines changed

README.org

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -428,12 +428,19 @@ start an async request is easy, for example:
428428

429429
#+BEGIN_SRC clojure
430430
;; :async? in options map need to be true
431-
(client/get "http://example.com"
432-
{:async? true}
433-
;; respond callback
434-
(fn [response] (println "response is:" response))
435-
;; raise callback
436-
(fn [exception] (println "exception message is: " (.getMessage exception))))
431+
(client/get "http://example.com"
432+
{:async true }
433+
;; respond callback
434+
(fn [response] (println "response is:" response))
435+
;; raise callback
436+
(fn [exception] (println "exception message is: " (.getMessage exception))))
437+
438+
;; equivalent using request
439+
(client/request {:method :get
440+
:url "http://example.com"
441+
:async true
442+
:respond (fn [response] (println "response is:" response))
443+
:raise (fn [exception] println "exception message is: " (.getMessage exception))})
437444
#+END_SRC
438445

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

src/clj_http/client.clj

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

1115+
(defn- async-transform
1116+
[client]
1117+
(fn
1118+
([req]
1119+
(cond
1120+
(opt req :async)
1121+
(let [{:keys [respond raise]} req]
1122+
(when (some nil? [respond raise])
1123+
(throw (IllegalArgumentException. "If :async? is true, you must pass respond and raise")))
1124+
(client req respond raise))
1125+
1126+
:else
1127+
(client req)))
1128+
1129+
;; In versions of clj-http older than 3.11, the 3-arity invocation implied the
1130+
;; request should be handled as async
1131+
([req respond raise]
1132+
(client (assoc req :async true) respond raise))))
1133+
11151134
(defn wrap-request
11161135
"Returns a batteries-included HTTP request function corresponding to the given
11171136
core client. See default-middleware for the middleware wrappers that are used
11181137
by default"
1119-
[request]
1120-
(reduce (fn wrap-request* [request middleware]
1121-
(middleware request))
1122-
request
1123-
default-middleware))
1138+
([request]
1139+
(wrap-request request default-middleware))
1140+
([request middleware]
1141+
(async-transform
1142+
(reduce #(%2 %1) request middleware))))
11241143

11251144
(def ^:dynamic request
11261145
"Executes the HTTP request corresponding to the given map and returns
@@ -1149,74 +1168,43 @@
11491168
option."
11501169
(wrap-request #'core/request))
11511170

1171+
(alter-meta! #'request assoc :arglists '([req] [req respond raise]))
1172+
11521173
;; Inline function to throw a slightly more readable exception when
11531174
;; the URL is nil
11541175
(definline check-url! [url]
11551176
`(when (nil? ~url)
11561177
(throw (IllegalArgumentException. "Host URL cannot be nil"))))
11571178

1158-
(defn- request*
1159-
[req [respond raise]]
1160-
(if (opt req :async)
1161-
(if (some nil? [respond raise])
1162-
(throw (IllegalArgumentException.
1163-
"If :async? is true, you must pass respond and raise"))
1164-
(request (dissoc req :respond :raise) respond raise))
1165-
(request req)))
1166-
1167-
(defn get
1168-
"Like #'request, but sets the :method and :url as appropriate."
1169-
[url & [req & r]]
1170-
(check-url! url)
1171-
(request* (merge req {:method :get :url url}) r))
1172-
1173-
(defn head
1174-
"Like #'request, but sets the :method and :url as appropriate."
1175-
[url & [req & r]]
1176-
(check-url! url)
1177-
(request* (merge req {:method :head :url url}) r))
1178-
1179-
(defn post
1180-
"Like #'request, but sets the :method and :url as appropriate."
1181-
[url & [req & r]]
1182-
(check-url! url)
1183-
(request* (merge req {:method :post :url url}) r))
1184-
1185-
(defn put
1186-
"Like #'request, but sets the :method and :url as appropriate."
1187-
[url & [req & r]]
1188-
(check-url! url)
1189-
(request* (merge req {:method :put :url url}) r))
1190-
1191-
(defn delete
1192-
"Like #'request, but sets the :method and :url as appropriate."
1193-
[url & [req & r]]
1194-
(check-url! url)
1195-
(request* (merge req {:method :delete :url url}) r))
1196-
1197-
(defn options
1198-
"Like #'request, but sets the :method and :url as appropriate."
1199-
[url & [req & r]]
1200-
(check-url! url)
1201-
(request* (merge req {:method :options :url url}) r))
1202-
1203-
(defn copy
1204-
"Like #'request, but sets the :method and :url as appropriate."
1205-
[url & [req & r]]
1206-
(check-url! url)
1207-
(request* (merge req {:method :copy :url url}) r))
1208-
1209-
(defn move
1210-
"Like #'request, but sets the :method and :url as appropriate."
1211-
[url & [req & r]]
1212-
(check-url! url)
1213-
(request* (merge req {:method :move :url url}) r))
1214-
1215-
(defn patch
1216-
"Like #'request, but sets the :method and :url as appropriate."
1217-
[url & [req & r]]
1218-
(check-url! url)
1219-
(request* (merge req {:method :patch :url url}) r))
1179+
(defn- request-method
1180+
([method url]
1181+
(check-url! url)
1182+
(request {:method method :url url}))
1183+
([method url req]
1184+
(check-url! url)
1185+
(request (merge req {:method method :url url})))
1186+
([method url req respond raise]
1187+
(check-url! url)
1188+
(request (merge req {:method method :url url :async true :respond respond :raise raise}))))
1189+
1190+
(defmacro ^:private def-http-method [method]
1191+
`(do
1192+
(def ~method (partial request-method ~(keyword method)))
1193+
(alter-meta! (resolve '~method) assoc
1194+
:doc ~(str "Like #'request, but sets the :method and :url as appropriate.")
1195+
:arglists '([~(symbol "url")]
1196+
[~(symbol "url") ~(symbol "req")]
1197+
[~(symbol "url") ~(symbol "req") ~(symbol "respond") ~(symbol "raise")]))))
1198+
1199+
(def-http-method get)
1200+
(def-http-method head)
1201+
(def-http-method post)
1202+
(def-http-method put)
1203+
(def-http-method delete)
1204+
(def-http-method options)
1205+
(def-http-method copy)
1206+
(def-http-method move)
1207+
(def-http-method patch)
12201208

12211209
(defmacro with-middleware
12221210
"Perform the body of the macro with a custom middleware list.
@@ -1229,9 +1217,7 @@
12291217
[middleware & body]
12301218
`(let [m# ~middleware]
12311219
(binding [*current-middleware* m#
1232-
clj-http.client/request (reduce #(%2 %1)
1233-
clj-http.core/request
1234-
m#)]
1220+
clj-http.client/request (wrap-request clj-http.core/request m#)]
12351221
~@body)))
12361222

12371223
(defmacro with-additional-middleware

test/clj_http/test/client_test.clj

Lines changed: 118 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -40,76 +40,132 @@
4040

4141
(deftest ^:integration roundtrip
4242
(run-server)
43-
;; roundtrip with scheme as a keyword
44-
(let [resp (request {:uri "/get" :method :get})]
45-
(is (= 200 (:status resp)))
46-
(is (= "close" (get-in resp [:headers "connection"])))
47-
(is (= "get" (:body resp))))
48-
;; roundtrip with scheme as a string
49-
(let [resp (request {:uri "/get" :method :get
50-
:scheme "http"})]
51-
(is (= 200 (:status resp)))
52-
(is (= "close" (get-in resp [:headers "connection"])))
53-
(is (= "get" (:body resp))))
54-
(let [params {:a "1" :b "2"}]
55-
(doseq [[content-type read-fn]
56-
[[nil (comp parse-form-params slurp)]
57-
[:x-www-form-urlencoded (comp parse-form-params slurp)]
58-
[:edn (comp read-string slurp)]
59-
[:transit+json #(client/parse-transit % :json)]
60-
[:transit+msgpack #(client/parse-transit % :msgpack)]]]
61-
(let [resp (request {:uri "/post"
62-
:as :stream
63-
:method :post
64-
:content-type content-type
65-
:form-params params})]
66-
(is (= 200 (:status resp)))
67-
(is (= "close" (get-in resp [:headers "connection"])))
68-
(is (= params (read-fn (:body resp)))
69-
(str "failed with content-type [" content-type "]"))))))
43+
(testing "roundtrip with scheme as a keyword"
44+
(let [resp (request {:uri "/get" :method :get})]
45+
(is (= 200 (:status resp)))
46+
(is (= "close" (get-in resp [:headers "connection"])))
47+
(is (= "get" (:body resp)))))
48+
(testing "roundtrip with scheme as string"
49+
(let [resp (request {:uri "/get" :method :get
50+
:scheme "http"})]
51+
(is (= 200 (:status resp)))
52+
(is (= "close" (get-in resp [:headers "connection"])))
53+
(is (= "get" (:body resp)))))
54+
(testing "roundtrip with response parsing"
55+
(let [params {:a "1" :b "2"}]
56+
(doseq [[content-type read-fn]
57+
[[nil (comp parse-form-params slurp)]
58+
[:x-www-form-urlencoded (comp parse-form-params slurp)]
59+
[:edn (comp read-string slurp)]
60+
[:transit+json #(client/parse-transit % :json)]
61+
[:transit+msgpack #(client/parse-transit % :msgpack)]]]
62+
(let [resp (request {:uri "/post"
63+
:as :stream
64+
:method :post
65+
:content-type content-type
66+
:form-params params})]
67+
(is (= 200 (:status resp)))
68+
(is (= "close" (get-in resp [:headers "connection"])))
69+
(is (= params (read-fn (:body resp)))
70+
(str "failed with content-type [" content-type "]")))))))
7071

7172
(deftest ^:integration roundtrip-async
7273
(run-server)
73-
;; roundtrip with scheme as a keyword
74-
(let [resp (promise)
75-
exception (promise)
76-
_ (request {:uri "/get" :method :get
77-
:async? true} resp exception)]
78-
(is (= 200 (:status @resp)))
79-
(is (= "close" (get-in @resp [:headers "connection"])))
80-
(is (= "get" (:body @resp)))
81-
(is (not (realized? exception))))
82-
;; roundtrip with scheme as a string
83-
(let [resp (promise)
84-
exception (promise)
85-
_ (request {:uri "/get" :method :get
86-
:scheme "http"
87-
:async? true} resp exception)]
88-
(is (= 200 (:status @resp)))
89-
(is (= "close" (get-in @resp [:headers "connection"])))
90-
(is (= "get" (:body @resp)))
91-
(is (not (realized? exception))))
92-
93-
(let [params {:a "1" :b "2"}]
94-
(doseq [[content-type read-fn]
95-
[[nil (comp parse-form-params slurp)]
96-
[:x-www-form-urlencoded (comp parse-form-params slurp)]
97-
[:edn (comp read-string slurp)]
98-
[:transit+json #(client/parse-transit % :json)]
99-
[:transit+msgpack #(client/parse-transit % :msgpack)]]]
74+
(testing "roundtrip with scheme as keyword"
75+
(testing "with async callback arguments"
10076
(let [resp (promise)
10177
exception (promise)
102-
_ (request {:uri "/post"
103-
:as :stream
104-
:method :post
105-
:content-type content-type
106-
:flatten-nested-keys []
107-
:form-params params
78+
_ (request {:uri "/get" :method :get
10879
:async? true} resp exception)]
10980
(is (= 200 (:status @resp)))
11081
(is (= "close" (get-in @resp [:headers "connection"])))
111-
(is (= params (read-fn (:body @resp))))
112-
(is (not (realized? exception)))))))
82+
(is (= "get" (:body @resp)))
83+
(is (not (realized? exception)))))
84+
(testing "with respond and raise attributes"
85+
(let [resp (promise)
86+
exception (promise)
87+
_ (request {:uri "/get" :method :get
88+
:async? true
89+
:respond resp
90+
:raise exception
91+
})]
92+
(is (= 200 (:status @resp)))
93+
(is (= "close" (get-in @resp [:headers "connection"])))
94+
(is (= "get" (:body @resp)))
95+
(is (not (realized? exception))))))
96+
(testing "round trip with scheme as string"
97+
(let [resp (promise)
98+
exception (promise)
99+
_ (request {:uri "/get" :method :get
100+
:scheme "http"
101+
:async? true} resp exception)]
102+
(is (= 200 (:status @resp)))
103+
(is (= "close" (get-in @resp [:headers "connection"])))
104+
(is (= "get" (:body @resp)))
105+
(is (not (realized? exception)))))
106+
(testing "roundtrip with error handling"
107+
(testing "with async callback arguments"
108+
(let [resp (promise)
109+
exception (promise)
110+
_ (request {:uri "/error" :method :get
111+
:scheme "http"
112+
:async? true} resp exception)]
113+
(is (instance? Exception @exception))))
114+
(testing "with respond and raise attributes"
115+
(let [resp (promise)
116+
exception (promise)
117+
_ (request {:uri "/error" :method :get
118+
:scheme "http"
119+
:async? true
120+
:respond resp
121+
:raise exception})]
122+
(is (instance? Exception @exception)))))
123+
(testing "roundtrip with response parsing"
124+
(testing "with async callback arguments"
125+
(let [params {:a "1" :b "2"}]
126+
(doseq [[content-type read-fn]
127+
[[nil (comp parse-form-params slurp)]
128+
[:x-www-form-urlencoded (comp parse-form-params slurp)]
129+
[:edn (comp read-string slurp)]
130+
[:transit+json #(client/parse-transit % :json)]
131+
[:transit+msgpack #(client/parse-transit % :msgpack)]]]
132+
(let [resp (promise)
133+
exception (promise)
134+
_ (request {:uri "/post"
135+
:as :stream
136+
:method :post
137+
:content-type content-type
138+
:flatten-nested-keys []
139+
:form-params params
140+
:async? true} resp exception)]
141+
(is (= 200 (:status @resp)))
142+
(is (= "close" (get-in @resp [:headers "connection"])))
143+
(is (= params (read-fn (:body @resp))))
144+
(is (not (realized? exception)))))))
145+
146+
(testing "with respond and raise attributes"
147+
(let [params {:a "1" :b "2"}]
148+
(doseq [[content-type read-fn]
149+
[[nil (comp parse-form-params slurp)]
150+
[:x-www-form-urlencoded (comp parse-form-params slurp)]
151+
[:edn (comp read-string slurp)]
152+
[:transit+json #(client/parse-transit % :json)]
153+
[:transit+msgpack #(client/parse-transit % :msgpack)]]]
154+
(let [resp (promise)
155+
exception (promise)
156+
_ (request {:uri "/post"
157+
:as :stream
158+
:method :post
159+
:content-type content-type
160+
:flatten-nested-keys []
161+
:form-params params
162+
:async? true
163+
:respond resp
164+
:raise exception})]
165+
(is (= 200 (:status @resp)))
166+
(is (= "close" (get-in @resp [:headers "connection"])))
167+
(is (= params (read-fn (:body @resp))))
168+
(is (not (realized? exception)))))))))
113169

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

0 commit comments

Comments
 (0)