From 0e06738ba060abe31581d58faaed0ab6d250ed6d Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Thu, 18 Apr 2024 15:29:28 +0200 Subject: [PATCH 1/5] yo --- API.md | 31 ++++++++++++----------- src/babashka/http_client.clj | 7 ++--- src/babashka/http_client/interceptors.clj | 15 ++++++----- src/babashka/http_client/internal.clj | 17 ++++++++----- test/babashka/http_client_test.clj | 16 +++++++++--- 5 files changed, 52 insertions(+), 34 deletions(-) diff --git a/API.md b/API.md index 3857449..deec7eb 100644 --- a/API.md +++ b/API.md @@ -180,7 +180,7 @@ Options used to create the (implicit) default client. ``` Convenience wrapper for [`request`](#babashka.http-client/request) with method `:delete` -

Source

+

Source

## `get` ``` clojure @@ -190,7 +190,7 @@ Convenience wrapper for [`request`](#babashka.http-client/request) with method ` ``` Convenience wrapper for [`request`](#babashka.http-client/request) with method `:get` -

Source

+

Source

## `head` ``` clojure @@ -200,7 +200,7 @@ Convenience wrapper for [`request`](#babashka.http-client/request) with method ` ``` Convenience wrapper for [`request`](#babashka.http-client/request) with method `:head` -

Source

+

Source

## `patch` ``` clojure @@ -210,7 +210,7 @@ Convenience wrapper for [`request`](#babashka.http-client/request) with method ` ``` Convenience wrapper for [`request`](#babashka.http-client/request) with method `:patch` -

Source

+

Source

## `post` ``` clojure @@ -220,7 +220,7 @@ Convenience wrapper for [`request`](#babashka.http-client/request) with method ` ``` Convenience wrapper for [`request`](#babashka.http-client/request) with method `:post` -

Source

+

Source

## `put` ``` clojure @@ -230,7 +230,7 @@ Convenience wrapper for [`request`](#babashka.http-client/request) with method ` ``` Convenience wrapper for [`request`](#babashka.http-client/request) with method `:put` -

Source

+

Source

## `request` ``` clojure @@ -247,7 +247,9 @@ Perform request. Returns map with at least `:body`, `:status` * `:headers` - a map of headers * `:method` - the request method: `:get`, `:post`, `:head`, `:delete`, `:patch` or `:put` * `:interceptors` - custom interceptor chain - * `:client` - a client as produced by [`client`](#babashka.http-client/client). If not provided a default client will be used. + * `:client` - a client as produced by [`client`](#babashka.http-client/client) or a clojure function. If not provided a default client will be used. + When providing :client with a a clojure function, it will be called with the Clojure representation of + the request which can be useful for testing. * `:query-params` - a map of query params. The values can be a list to send multiple params with the same key. * `:form-params` - a map of form params to send in the request body. * `: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` * `:timeout` - request timeout in milliseconds * `:throw` - throw on exceptional status codes, all other than `#{200 201 202 203 204 205 206 207 300 301 302 303 304 307}` * `:version` - the HTTP version: `:http1.1` or `:http2`. - -

Source

+

Source

----- # babashka.http-client.interceptors @@ -293,7 +294,7 @@ Request: adds `:authorization` header based on `:basic-auth` (a map Request: construct uri from map -

Source

+

Source

## `decode-body` @@ -301,7 +302,7 @@ Request: construct uri from map Response: based on the value of `:as` in request, decodes as `:string`, `:stream` or `:bytes`. Defaults to `:string`. -

Source

+

Source

## `decompress-body` @@ -317,7 +318,7 @@ Response: decompresses body based on "content-encoding" header. Valid values: ` Default interceptor chain. Interceptors are called in order for request and in reverse order for response. -

Source

+

Source

## `form-params` @@ -333,7 +334,7 @@ Request: encodes `:form-params` map and adds `:body`. Adds appropriate body and header if making a multipart request. -

Source

+

Source

## `oauth-token` @@ -358,13 +359,13 @@ Request: encodes `:query-params` map and appends to `:uri`. Response: throw on exceptional status codes -

Source

+

Source

## `unexceptional-statuses` -

Source

+

Source

## `uri-with-query` ``` clojure diff --git a/src/babashka/http_client.clj b/src/babashka/http_client.clj index e714283..36dbb84 100644 --- a/src/babashka/http_client.clj +++ b/src/babashka/http_client.clj @@ -107,7 +107,9 @@ * `:headers` - a map of headers * `:method` - the request method: `:get`, `:post`, `:head`, `:delete`, `:patch` or `:put` * `:interceptors` - custom interceptor chain - * `:client` - a client as produced by `client`. If not provided a default client will be used. + * `:client` - a client as produced by `client` or a clojure function. If not provided a default client will be used. + When providing :client with a a clojure function, it will be called with the Clojure representation of + the request which can be useful for testing. * `:query-params` - a map of query params. The values can be a list to send multiple params with the same key. * `:form-params` - a map of form params to send in the request body. * `:body` - a file, inputstream or string to send as the request body. @@ -118,8 +120,7 @@ * `:async-catch` - a function that is called on the async result if exceptional * `:timeout` - request timeout in milliseconds * `:throw` - throw on exceptional status codes, all other than `#{200 201 202 203 204 205 206 207 300 301 302 303 304 307}` - * `:version` - the HTTP version: `:http1.1` or `:http2`. - " + * `:version` - the HTTP version: `:http1.1` or `:http2`." [opts] (i/request opts)) diff --git a/src/babashka/http_client/interceptors.clj b/src/babashka/http_client/interceptors.clj index 01052f7..c270588 100644 --- a/src/babashka/http_client/interceptors.clj +++ b/src/babashka/http_client/interceptors.clj @@ -211,10 +211,12 @@ :response (fn [resp] (let [as (or (-> resp :request :as) :string) body (:body resp) - body (case as - :string (slurp body) - :stream body - :bytes (stream-bytes body))] + body (if (instance? java.io.InputStream body) + (case as + :string (slurp body) + :stream body + :bytes (stream-bytes body)) + body)] (assoc resp :body body)))}) (def construct-uri @@ -232,11 +234,12 @@ "Response: throw on exceptional status codes" {:name ::throw-on-exceptional-status-code :response (fn [resp] - (let [status (:status resp)] + (if-let [status (:status resp)] (if (or (false? (some-> resp :request :throw)) (contains? unexceptional-statuses status)) resp - (throw (ex-info (str "Exceptional status code: " status) resp)))))}) + (throw (ex-info (str "Exceptional status code: " status) resp))) + resp))}) (def multipart "Adds appropriate body and header if making a multipart request." diff --git a/src/babashka/http_client/internal.clj b/src/babashka/http_client/internal.clj index 56bbe05..0726074 100644 --- a/src/babashka/http_client/internal.clj +++ b/src/babashka/http_client/internal.clj @@ -301,19 +301,24 @@ [{:keys [client raw] :as req}] (let [client (or client @default-client) request-defaults (:request client) - ^HttpClient client (or (:client client) client) + client* (or (:client client) client) + ^HttpClient client client* + ring-client (when (ifn? client*) + client*) req (merge-with merge-opts request-defaults req) req (update req :headers aux/prefer-string-keys) request-interceptors (or (:interceptors req) interceptors/default-interceptors) req (apply-interceptors req request-interceptors :request) - req' (ring->HttpRequest req) + req' (when-not ring-client (ring->HttpRequest req)) async (:async req) - resp (if async - (.sendAsync client req' (HttpResponse$BodyHandlers/ofInputStream)) - (.send client req' (HttpResponse$BodyHandlers/ofInputStream)))] + resp (if ring-client + (ring-client req) + (if async + (.sendAsync client req' (HttpResponse$BodyHandlers/ofInputStream)) + (.send client req' (HttpResponse$BodyHandlers/ofInputStream))))] (if raw resp - (let [resp (then resp response->map) + (let [resp (if ring-client resp (then resp response->map)) resp (then resp (fn [resp] (assoc resp :request req))) resp (reduce (fn [resp interceptor] diff --git a/test/babashka/http_client_test.clj b/test/babashka/http_client_test.clj index 00893ee..eacab4d 100644 --- a/test/babashka/http_client_test.clj +++ b/test/babashka/http_client_test.clj @@ -501,16 +501,16 @@ (nil? (.getProtocols params))))) (let [params (http/->SSLParameters {:ciphers ["SSL_NULL_WITH_NULL_NULL"]})] (is (and (instance? javax.net.ssl.SSLParameters params) - (= (first (.getCipherSuites params)) "SSL_NULL_WITH_NULL_NULL")))) + (= "SSL_NULL_WITH_NULL_NULL" (first (.getCipherSuites params)))))) (let [params (http/->SSLParameters {:protocols ["TLSv1"]})] (is (and (instance? javax.net.ssl.SSLParameters params) - (= (first (.getProtocols params)) "TLSv1")))) + (= "TLSv1" (first (.getProtocols params)))))) (let [params-from-opts (http/->SSLParameters {:ciphers ["SSL_NULL_WITH_NULL_NULL"] :protocols ["TLSv1"]}) params-from-params (http/->SSLParameters params-from-opts)] (is (and (instance? javax.net.ssl.SSLParameters params-from-params) - (= (first (.getCipherSuites params-from-params)) "SSL_NULL_WITH_NULL_NULL") - (= (first (.getProtocols params-from-params)) "TLSv1"))))) + (= "SSL_NULL_WITH_NULL_NULL" (first (.getCipherSuites params-from-params))) + (= "TLSv1" (first (.getProtocols params-from-params))))))) (deftest executor-test (testing "nil passthrough" @@ -534,6 +534,14 @@ (str (#'i/uri-with-query (java.net.URI. "https://borkdude:foobar@foobar.net:80/?q=1#/dude") "q=%26moo")))))) +(deftest ring-client-test + (let [resp (http/get "https://clojure.org" + {:client (fn [req] + {:body (java.io.ByteArrayInputStream. (.getBytes "Hello")) + :clojure (= "https://clojure.org" (str (:uri req)))})})] + (is (:clojure resp)) + (is (= "Hello" (:body resp))))) + (comment (run-server) (stop-server)) From 108fda27e46fa4843d1692ae0146663c62467be5 Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Thu, 18 Apr 2024 15:40:13 +0200 Subject: [PATCH 2/5] add another test --- test/babashka/http_client_test.clj | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/babashka/http_client_test.clj b/test/babashka/http_client_test.clj index eacab4d..db9ac49 100644 --- a/test/babashka/http_client_test.clj +++ b/test/babashka/http_client_test.clj @@ -535,12 +535,18 @@ "q=%26moo")))))) (deftest ring-client-test - (let [resp (http/get "https://clojure.org" - {:client (fn [req] - {:body (java.io.ByteArrayInputStream. (.getBytes "Hello")) - :clojure (= "https://clojure.org" (str (:uri req)))})})] - (is (:clojure resp)) - (is (= "Hello" (:body resp))))) + (testing "string body" + (let [resp (http/get "https://clojure.org" + {:client (fn [_req] + {:body "Hello"})})] + (is (= "Hello" (:body resp))))) + (testing "inputstring body" + (let [resp (http/get "https://clojure.org" + {:client (fn [req] + {:body (java.io.ByteArrayInputStream. (.getBytes "Hello")) + :clojure (= "https://clojure.org" (str (:uri req)))})})] + (is (:clojure resp)) + (is (= "Hello" (:body resp)))))) (comment (run-server) From 7df8f56ed297a007d0103ef574807ca1fd9cd52f Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Thu, 18 Apr 2024 16:19:20 +0200 Subject: [PATCH 3/5] yo --- src/babashka/http_client/interceptors.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/babashka/http_client/interceptors.clj b/src/babashka/http_client/interceptors.clj index c270588..789278c 100644 --- a/src/babashka/http_client/interceptors.clj +++ b/src/babashka/http_client/interceptors.clj @@ -211,7 +211,7 @@ :response (fn [resp] (let [as (or (-> resp :request :as) :string) body (:body resp) - body (if (instance? java.io.InputStream body) + body (if (not (string? body)) (case as :string (slurp body) :stream body From 8723c671a0b245e7eb25227f52ef4a9a7e6f613f Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Thu, 18 Apr 2024 16:26:49 +0200 Subject: [PATCH 4/5] Add test for stringreader --- test/babashka/http_client_test.clj | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/test/babashka/http_client_test.clj b/test/babashka/http_client_test.clj index db9ac49..d55aa42 100644 --- a/test/babashka/http_client_test.clj +++ b/test/babashka/http_client_test.clj @@ -535,16 +535,19 @@ "q=%26moo")))))) (deftest ring-client-test - (testing "string body" - (let [resp (http/get "https://clojure.org" - {:client (fn [_req] - {:body "Hello"})})] - (is (= "Hello" (:body resp))))) (testing "inputstring body" - (let [resp (http/get "https://clojure.org" - {:client (fn [req] - {:body (java.io.ByteArrayInputStream. (.getBytes "Hello")) - :clojure (= "https://clojure.org" (str (:uri req)))})})] + (doseq [resp [(http/get "https://clojure.org" + {:client (fn [_req] + {:body "Hello" + :clojure true})}) + (http/get "https://clojure.org" + {:client (fn [req] + {:body (java.io.ByteArrayInputStream. (.getBytes "Hello")) + :clojure (= "https://clojure.org" (str (:uri req)))})}) + (http/get "https://clojure.org" + {:client (fn [_req] + {:body (java.io.StringReader. "Hello") + :clojure true})})]] (is (:clojure resp)) (is (= "Hello" (:body resp)))))) From 2b8ca233de503d007e88643d763ed55e094eb003 Mon Sep 17 00:00:00 2001 From: Michiel Borkent Date: Thu, 18 Apr 2024 16:45:56 +0200 Subject: [PATCH 5/5] Testing interceptors --- CHANGELOG.md | 4 ++++ README.md | 13 ++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa8bbef..d5fc456 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ Babashka [http-client](https://github.com/babashka/http-client): HTTP client for Clojure and babashka built on java.net.http +## 0.4.18 (2024-04-18) + +- Support a Clojure function as `:client` option, mostly useful for testing + ## 0.4.17 (2024-04-12) - [#49](https://github.com/babashka/http-client/issues/49): add `::oauth-token` interceptor diff --git a/README.md b/README.md index c193828..ca31023 100644 --- a/README.md +++ b/README.md @@ -335,6 +335,17 @@ function is executed on the response. Default interceptors are in configured on the level of requests by passing a modified `:interceptors` chain. +#### Testing interceptors + +For testing interceptors it can be useful to use the `:client` option in combination with a +Clojure function. When passing a function, the request won't be converted to a +`java.net.http.Request` but just passed as a ring request to the function. The +function is expected to return a ring response: + +``` clojure +(http/get "https://clojure.org" {:client (fn [req] {:body 200})}) +``` + ### Async To execute request asynchronously, use `:async true`. The response will be a @@ -375,7 +386,7 @@ Here is a code snippet for `deps.edn` {:jvm-opts [;; enable logging for java.net.http "-Djdk.httpclient.HttpClient.log=errors,requests,headers,frames[:control:data:window:all..],content,ssl,trace,channel"]} -}} +}} ``` ## Test