Skip to content

Commit e7c4631

Browse files
authored
Support clojure function in :client option (#54)
1 parent 9a71681 commit e7c4631

File tree

7 files changed

+77
-35
lines changed

7 files changed

+77
-35
lines changed

API.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ Options used to create the (implicit) default client.
180180
```
181181

182182
Convenience wrapper for [`request`](#babashka.http-client/request) with method `:delete`
183-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L133-L138">Source</a></sub></p>
183+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L134-L139">Source</a></sub></p>
184184

185185
## <a name="babashka.http-client/get">`get`</a><a name="babashka.http-client/get"></a>
186186
``` clojure
@@ -190,7 +190,7 @@ Convenience wrapper for [`request`](#babashka.http-client/request) with method `
190190
```
191191

192192
Convenience wrapper for [`request`](#babashka.http-client/request) with method `:get`
193-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L126-L131">Source</a></sub></p>
193+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L127-L132">Source</a></sub></p>
194194

195195
## <a name="babashka.http-client/head">`head`</a><a name="babashka.http-client/head"></a>
196196
``` clojure
@@ -200,7 +200,7 @@ Convenience wrapper for [`request`](#babashka.http-client/request) with method `
200200
```
201201

202202
Convenience wrapper for [`request`](#babashka.http-client/request) with method `:head`
203-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L140-L145">Source</a></sub></p>
203+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L141-L146">Source</a></sub></p>
204204

205205
## <a name="babashka.http-client/patch">`patch`</a><a name="babashka.http-client/patch"></a>
206206
``` clojure
@@ -210,7 +210,7 @@ Convenience wrapper for [`request`](#babashka.http-client/request) with method `
210210
```
211211

212212
Convenience wrapper for [`request`](#babashka.http-client/request) with method `:patch`
213-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L154-L161">Source</a></sub></p>
213+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L155-L162">Source</a></sub></p>
214214

215215
## <a name="babashka.http-client/post">`post`</a><a name="babashka.http-client/post"></a>
216216
``` clojure
@@ -220,7 +220,7 @@ Convenience wrapper for [`request`](#babashka.http-client/request) with method `
220220
```
221221

222222
Convenience wrapper for [`request`](#babashka.http-client/request) with method `:post`
223-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L147-L152">Source</a></sub></p>
223+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L148-L153">Source</a></sub></p>
224224

225225
## <a name="babashka.http-client/put">`put`</a><a name="babashka.http-client/put"></a>
226226
``` clojure
@@ -230,7 +230,7 @@ Convenience wrapper for [`request`](#babashka.http-client/request) with method `
230230
```
231231

232232
Convenience wrapper for [`request`](#babashka.http-client/request) with method `:put`
233-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L163-L170">Source</a></sub></p>
233+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L164-L171">Source</a></sub></p>
234234

235235
## <a name="babashka.http-client/request">`request`</a><a name="babashka.http-client/request"></a>
236236
``` clojure
@@ -247,7 +247,9 @@ Perform request. Returns map with at least `:body`, `:status`
247247
* `:headers` - a map of headers
248248
* `:method` - the request method: `:get`, `:post`, `:head`, `:delete`, `:patch` or `:put`
249249
* `:interceptors` - custom interceptor chain
250-
* `:client` - a client as produced by [`client`](#babashka.http-client/client). If not provided a default client will be used.
250+
* `:client` - a client as produced by [`client`](#babashka.http-client/client) or a clojure function. If not provided a default client will be used.
251+
When providing :client with a a clojure function, it will be called with the Clojure representation of
252+
the request which can be useful for testing.
251253
* `:query-params` - a map of query params. The values can be a list to send multiple params with the same key.
252254
* `:form-params` - a map of form params to send in the request body.
253255
* `:body` - a file, inputstream or string to send as the request body.
@@ -259,8 +261,7 @@ Perform request. Returns map with at least `:body`, `:status`
259261
* `:timeout` - request timeout in milliseconds
260262
* `:throw` - throw on exceptional status codes, all other than `#{200 201 202 203 204 205 206 207 300 301 302 303 304 307}`
261263
* `:version` - the HTTP version: `:http1.1` or `:http2`.
262-
263-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L100-L124">Source</a></sub></p>
264+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client.clj#L100-L125">Source</a></sub></p>
264265

265266
-----
266267
# <a name="babashka.http-client.interceptors">babashka.http-client.interceptors</a>
@@ -293,15 +294,15 @@ Request: adds `:authorization` header based on `:basic-auth` (a map
293294

294295

295296
Request: construct uri from map
296-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L220-L226">Source</a></sub></p>
297+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L222-L228">Source</a></sub></p>
297298

298299
## <a name="babashka.http-client.interceptors/decode-body">`decode-body`</a><a name="babashka.http-client.interceptors/decode-body"></a>
299300

300301

301302

302303

303304
Response: based on the value of `:as` in request, decodes as `:string`, `:stream` or `:bytes`. Defaults to `:string`.
304-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L208-L218">Source</a></sub></p>
305+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L208-L220">Source</a></sub></p>
305306

306307
## <a name="babashka.http-client.interceptors/decompress-body">`decompress-body`</a><a name="babashka.http-client.interceptors/decompress-body"></a>
307308

@@ -317,7 +318,7 @@ Response: decompresses body based on "content-encoding" header. Valid values: `
317318

318319

319320
Default interceptor chain. Interceptors are called in order for request and in reverse order for response.
320-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L253-L264">Source</a></sub></p>
321+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L256-L267">Source</a></sub></p>
321322

322323
## <a name="babashka.http-client.interceptors/form-params">`form-params`</a><a name="babashka.http-client.interceptors/form-params"></a>
323324

@@ -333,7 +334,7 @@ Request: encodes `:form-params` map and adds `:body`.
333334

334335

335336
Adds appropriate body and header if making a multipart request.
336-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L241-L251">Source</a></sub></p>
337+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L244-L254">Source</a></sub></p>
337338

338339
## <a name="babashka.http-client.interceptors/oauth-token">`oauth-token`</a><a name="babashka.http-client.interceptors/oauth-token"></a>
339340

@@ -358,13 +359,13 @@ Request: encodes `:query-params` map and appends to `:uri`.
358359

359360

360361
Response: throw on exceptional status codes
361-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L231-L239">Source</a></sub></p>
362+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L233-L242">Source</a></sub></p>
362363

363364
## <a name="babashka.http-client.interceptors/unexceptional-statuses">`unexceptional-statuses`</a><a name="babashka.http-client.interceptors/unexceptional-statuses"></a>
364365

365366

366367

367-
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L228-L229">Source</a></sub></p>
368+
<p><sub><a href="https://github.com/babashka/http-client/blob/main/src/babashka/http_client/interceptors.clj#L230-L231">Source</a></sub></p>
368369

369370
## <a name="babashka.http-client.interceptors/uri-with-query">`uri-with-query`</a><a name="babashka.http-client.interceptors/uri-with-query"></a>
370371
``` clojure

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
Babashka [http-client](https://github.com/babashka/http-client): HTTP client for Clojure and babashka built on java.net.http
44

5+
## 0.4.18 (2024-04-18)
6+
7+
- Support a Clojure function as `:client` option, mostly useful for testing
8+
59
## 0.4.17 (2024-04-12)
610

711
- [#49](https://github.com/babashka/http-client/issues/49): add `::oauth-token` interceptor

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,17 @@ function is executed on the response. Default interceptors are in
335335
configured on the level of requests by passing a modified `:interceptors`
336336
chain.
337337
338+
#### Testing interceptors
339+
340+
For testing interceptors it can be useful to use the `:client` option in combination with a
341+
Clojure function. When passing a function, the request won't be converted to a
342+
`java.net.http.Request` but just passed as a ring request to the function. The
343+
function is expected to return a ring response:
344+
345+
``` clojure
346+
(http/get "https://clojure.org" {:client (fn [req] {:body 200})})
347+
```
348+
338349
### Async
339350
340351
To execute request asynchronously, use `:async true`. The response will be a
@@ -375,7 +386,7 @@ Here is a code snippet for `deps.edn`
375386
{:jvm-opts
376387
[;; enable logging for java.net.http
377388
"-Djdk.httpclient.HttpClient.log=errors,requests,headers,frames[:control:data:window:all..],content,ssl,trace,channel"]}
378-
}}
389+
}}
379390
```
380391
381392
## Test

src/babashka/http_client.clj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@
107107
* `:headers` - a map of headers
108108
* `:method` - the request method: `:get`, `:post`, `:head`, `:delete`, `:patch` or `:put`
109109
* `:interceptors` - custom interceptor chain
110-
* `:client` - a client as produced by `client`. If not provided a default client will be used.
110+
* `:client` - a client as produced by `client` or a clojure function. If not provided a default client will be used.
111+
When providing :client with a a clojure function, it will be called with the Clojure representation of
112+
the request which can be useful for testing.
111113
* `:query-params` - a map of query params. The values can be a list to send multiple params with the same key.
112114
* `:form-params` - a map of form params to send in the request body.
113115
* `:body` - a file, inputstream or string to send as the request body.
@@ -118,8 +120,7 @@
118120
* `:async-catch` - a function that is called on the async result if exceptional
119121
* `:timeout` - request timeout in milliseconds
120122
* `:throw` - throw on exceptional status codes, all other than `#{200 201 202 203 204 205 206 207 300 301 302 303 304 307}`
121-
* `:version` - the HTTP version: `:http1.1` or `:http2`.
122-
"
123+
* `:version` - the HTTP version: `:http1.1` or `:http2`."
123124
[opts]
124125
(i/request opts))
125126

src/babashka/http_client/interceptors.clj

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -211,10 +211,12 @@
211211
:response (fn [resp]
212212
(let [as (or (-> resp :request :as) :string)
213213
body (:body resp)
214-
body (case as
215-
:string (slurp body)
216-
:stream body
217-
:bytes (stream-bytes body))]
214+
body (if (not (string? body))
215+
(case as
216+
:string (slurp body)
217+
:stream body
218+
:bytes (stream-bytes body))
219+
body)]
218220
(assoc resp :body body)))})
219221

220222
(def construct-uri
@@ -232,11 +234,12 @@
232234
"Response: throw on exceptional status codes"
233235
{:name ::throw-on-exceptional-status-code
234236
:response (fn [resp]
235-
(let [status (:status resp)]
237+
(if-let [status (:status resp)]
236238
(if (or (false? (some-> resp :request :throw))
237239
(contains? unexceptional-statuses status))
238240
resp
239-
(throw (ex-info (str "Exceptional status code: " status) resp)))))})
241+
(throw (ex-info (str "Exceptional status code: " status) resp)))
242+
resp))})
240243

241244
(def multipart
242245
"Adds appropriate body and header if making a multipart request."

src/babashka/http_client/internal.clj

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -301,19 +301,24 @@
301301
[{:keys [client raw] :as req}]
302302
(let [client (or client @default-client)
303303
request-defaults (:request client)
304-
^HttpClient client (or (:client client) client)
304+
client* (or (:client client) client)
305+
^HttpClient client client*
306+
ring-client (when (ifn? client*)
307+
client*)
305308
req (merge-with merge-opts request-defaults req)
306309
req (update req :headers aux/prefer-string-keys)
307310
request-interceptors (or (:interceptors req)
308311
interceptors/default-interceptors)
309312
req (apply-interceptors req request-interceptors :request)
310-
req' (ring->HttpRequest req)
313+
req' (when-not ring-client (ring->HttpRequest req))
311314
async (:async req)
312-
resp (if async
313-
(.sendAsync client req' (HttpResponse$BodyHandlers/ofInputStream))
314-
(.send client req' (HttpResponse$BodyHandlers/ofInputStream)))]
315+
resp (if ring-client
316+
(ring-client req)
317+
(if async
318+
(.sendAsync client req' (HttpResponse$BodyHandlers/ofInputStream))
319+
(.send client req' (HttpResponse$BodyHandlers/ofInputStream))))]
315320
(if raw resp
316-
(let [resp (then resp response->map)
321+
(let [resp (if ring-client resp (then resp response->map))
317322
resp (then resp (fn [resp]
318323
(assoc resp :request req)))
319324
resp (reduce (fn [resp interceptor]

test/babashka/http_client_test.clj

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -501,16 +501,16 @@
501501
(nil? (.getProtocols params)))))
502502
(let [params (http/->SSLParameters {:ciphers ["SSL_NULL_WITH_NULL_NULL"]})]
503503
(is (and (instance? javax.net.ssl.SSLParameters params)
504-
(= (first (.getCipherSuites params)) "SSL_NULL_WITH_NULL_NULL"))))
504+
(= "SSL_NULL_WITH_NULL_NULL" (first (.getCipherSuites params))))))
505505
(let [params (http/->SSLParameters {:protocols ["TLSv1"]})]
506506
(is (and (instance? javax.net.ssl.SSLParameters params)
507-
(= (first (.getProtocols params)) "TLSv1"))))
507+
(= "TLSv1" (first (.getProtocols params))))))
508508
(let [params-from-opts (http/->SSLParameters {:ciphers ["SSL_NULL_WITH_NULL_NULL"]
509509
:protocols ["TLSv1"]})
510510
params-from-params (http/->SSLParameters params-from-opts)]
511511
(is (and (instance? javax.net.ssl.SSLParameters params-from-params)
512-
(= (first (.getCipherSuites params-from-params)) "SSL_NULL_WITH_NULL_NULL")
513-
(= (first (.getProtocols params-from-params)) "TLSv1")))))
512+
(= "SSL_NULL_WITH_NULL_NULL" (first (.getCipherSuites params-from-params)))
513+
(= "TLSv1" (first (.getProtocols params-from-params)))))))
514514

515515
(deftest executor-test
516516
(testing "nil passthrough"
@@ -534,6 +534,23 @@
534534
(str (#'i/uri-with-query (java.net.URI. "https://borkdude:[email protected]:80/?q=1#/dude")
535535
"q=%26moo"))))))
536536

537+
(deftest ring-client-test
538+
(testing "inputstring body"
539+
(doseq [resp [(http/get "https://clojure.org"
540+
{:client (fn [_req]
541+
{:body "Hello"
542+
:clojure true})})
543+
(http/get "https://clojure.org"
544+
{:client (fn [req]
545+
{:body (java.io.ByteArrayInputStream. (.getBytes "Hello"))
546+
:clojure (= "https://clojure.org" (str (:uri req)))})})
547+
(http/get "https://clojure.org"
548+
{:client (fn [_req]
549+
{:body (java.io.StringReader. "Hello")
550+
:clojure true})})]]
551+
(is (:clojure resp))
552+
(is (= "Hello" (:body resp))))))
553+
537554
(comment
538555
(run-server)
539556
(stop-server))

0 commit comments

Comments
 (0)