Skip to content

Commit 4f61aeb

Browse files
committed
Refactor request to support respond raise
This refactor pushes the complexity of request* inside request so that the behaviour between request and (get/put/post etc) behave the same. The docstrings of the HTTP methods are also cleared up to be more explicit around async callback arguments.
1 parent 1cdbe35 commit 4f61aeb

File tree

3 files changed

+181
-132
lines changed

3 files changed

+181
-132
lines changed

README.org

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@
7272
- [[#other-libraries-providing-middleware][Other Libraries Providing Middleware]]
7373
- [[#license][License]]
7474

75-
7675
* Branches
7776
:PROPERTIES:
7877
:CUSTOM_ID: h-e390585c-cbd8-4e94-b36b-4e9c27c16720
@@ -428,12 +427,19 @@ start an async request is easy, for example:
428427

429428
#+BEGIN_SRC clojure
430429
;; :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))))
430+
(client/get "http://example.com"
431+
{:async true }
432+
;; respond callback
433+
(fn [response] (println "response is:" response))
434+
;; raise callback
435+
(fn [exception] (println "exception message is: " (.getMessage exception))))
436+
437+
;; equivalent using request
438+
(client/request {:method :get
439+
:url "http://example.com"
440+
:async true
441+
:respond (fn [response] (println "response is:" response))
442+
:raise (fn [exception] println "exception message is: " (.getMessage exception))})
437443
#+END_SRC
438444

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

src/clj_http/client.clj

Lines changed: 50 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1124,14 +1124,34 @@
11241124
Automatically bound when `with-middleware` is used."
11251125
default-middleware)
11261126

1127+
(defn- async-transform
1128+
[client]
1129+
(fn
1130+
([req]
1131+
(cond
1132+
(opt req :async)
1133+
(let [{:keys [respond raise]} req]
1134+
(when (some nil? [respond raise])
1135+
(throw (IllegalArgumentException. "If :async? is true, you must pass respond and raise")))
1136+
(client req respond raise))
1137+
1138+
:else
1139+
(client req)))
1140+
1141+
;; In versions of clj-http older than 3.11, the 3-arity invocation implied the
1142+
;; request should be handled as async
1143+
([req respond raise]
1144+
(client (assoc req :async true) respond raise))))
1145+
11271146
(defn wrap-request
11281147
"Returns a batteries-included HTTP request function corresponding to the given
11291148
core client. See default-middleware for the middleware wrappers that are used
11301149
by default"
11311150
([request]
11321151
(wrap-request request default-middleware))
11331152
([request middleware]
1134-
(reduce #(%2 %1) request middleware)))
1153+
(async-transform
1154+
(reduce #(%2 %1) request middleware))))
11351155

11361156
(def ^:dynamic request
11371157
"Executes the HTTP request corresponding to the given map and returns
@@ -1166,68 +1186,35 @@
11661186
`(when (nil? ~url)
11671187
(throw (IllegalArgumentException. "Host URL cannot be nil"))))
11681188

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

12321219
(defmacro with-middleware
12331220
"Perform the body of the macro with a custom middleware list.

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)