diff --git a/feature-httpkit-client/babashka/impl/httpkit_client.clj b/feature-httpkit-client/babashka/impl/httpkit_client.clj index bf623f6a..310c9a42 100644 --- a/feature-httpkit-client/babashka/impl/httpkit_client.clj +++ b/feature-httpkit-client/babashka/impl/httpkit_client.clj @@ -1,6 +1,9 @@ (ns babashka.impl.httpkit-client - {:no-doc true} - (:require [org.httpkit.client :as client] + {:no-doc true + :clj-kondo/config '{:lint-as {babashka.impl.httpkit-client/defreq clojure.core/declare}}} + (:refer-clojure :exclude [get]) + (:require [clojure.string :as str] + [org.httpkit.client :as client] [org.httpkit.sni-client :as sni-client] [sci.core :as sci :refer [copy-var]])) @@ -10,6 +13,7 @@ (def cns (sci/create-ns 'org.httpkit.client nil)) (def default-client (sci/new-dynamic-var '*default-client* sni-client {:ns cns})) +(alter-var-root #'client/*default-client* (constantly sni-client)) (defn request ([req] @@ -19,26 +23,57 @@ (binding [client/*default-client* @default-client] (client/request req cb)))) +(defmacro ^:private defreq [method] + `(defn ~method + ~(str "Issues an async HTTP " (str/upper-case method) " request. " + "See `request` for details.") + ~'{:arglists '([url & [opts callback]] [url & [callback]])} + ~'[url & [s1 s2]] + (if (or (instance? clojure.lang.MultiFn ~'s1) (fn? ~'s1) (keyword? ~'s1)) + (request {:url ~'url :method ~(keyword method)} ~'s1) + (request (merge ~'s1 {:url ~'url :method ~(keyword method)}) ~'s2)))) + +(defreq get) +(defreq delete) +(defreq head) +(defreq post) +(defreq put) +(defreq options) +(defreq patch) +(defreq propfind) +(defreq proppatch) +(defreq lock) +(defreq unlock) +(defreq report) +(defreq acl) +(defreq copy) +(defreq move) + (def httpkit-client-namespace {'request (sci/new-var 'request request {:doc (:doc (meta #'client/request)) - :ns cns}) - 'get (copy-var client/get cns) - 'options (copy-var client/options cns) - 'put (copy-var client/put cns) - 'lock (copy-var client/lock cns) - 'report (copy-var client/report cns) - 'proppatch (copy-var client/proppatch cns) - 'copy (copy-var client/copy cns) - 'patch (copy-var client/patch cns) + :name 'request + :ns cns}) + 'get (copy-var get cns) + 'options (copy-var options cns) + 'put (copy-var put cns) + 'lock (copy-var lock cns) + 'report (copy-var report cns) + 'proppatch (copy-var proppatch cns) + 'copy (copy-var copy cns) + 'patch (copy-var patch cns) 'make-ssl-engine (copy-var client/make-ssl-engine cns) - 'move (copy-var client/move cns) - 'delete (copy-var client/delete cns) + 'move (copy-var move cns) + 'delete (copy-var delete cns) 'make-client (copy-var client/make-client cns) - 'head (copy-var client/head cns) - 'propfind (copy-var client/propfind cns) + 'head (copy-var head cns) + 'propfind (copy-var propfind cns) 'max-body-filter (copy-var client/max-body-filter cns) - 'post (copy-var client/post cns) - 'acl (copy-var client/acl cns) - 'unlock (copy-var client/unlock cns) + 'post (copy-var post cns) + 'acl (copy-var acl cns) + 'unlock (copy-var unlock cns) 'default-client (copy-var client/default-client cns) '*default-client* default-client}) + +(def sni-client-namespace + {'ssl-configurer (copy-var sni-client/ssl-configurer sns) + 'default-client (sci/new-var 'sni-client sni-client {:ns sns})}) diff --git a/sci b/sci index b0e7d64b..0fd6c474 160000 --- a/sci +++ b/sci @@ -1 +1 @@ -Subproject commit b0e7d64b723daa371d1fd87edd74310f06d0bbfe +Subproject commit 0fd6c4747b8e93d6ead785afdd89e087ebb5a3fb diff --git a/src/babashka/main.clj b/src/babashka/main.clj index 677826ac..d8491fdb 100644 --- a/src/babashka/main.clj +++ b/src/babashka/main.clj @@ -403,7 +403,8 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that features/csv? (assoc 'clojure.data.csv @(resolve 'babashka.impl.csv/csv-namespace)) features/transit? (assoc 'cognitect.transit @(resolve 'babashka.impl.transit/transit-namespace)) features/datascript? (assoc 'datascript.core @(resolve 'babashka.impl.datascript/datascript-namespace)) - features/httpkit-client? (assoc 'org.httpkit.client @(resolve 'babashka.impl.httpkit-client/httpkit-client-namespace)))) + features/httpkit-client? (assoc 'org.httpkit.client @(resolve 'babashka.impl.httpkit-client/httpkit-client-namespace) + 'org.httpkit.sni-client @(resolve 'babashka.impl.httpkit-client/sni-client-namespace)))) (def bindings {'java.lang.System/exit exit ;; override exit, so we have more control diff --git a/test-resources/lib_tests/babashka/run_all_libtests.clj b/test-resources/lib_tests/babashka/run_all_libtests.clj index 70cfc370..75a0d3b4 100644 --- a/test-resources/lib_tests/babashka/run_all_libtests.clj +++ b/test-resources/lib_tests/babashka/run_all_libtests.clj @@ -187,6 +187,10 @@ 'version-clj.split-test 'version-clj.via-use-test) +;;;; httpkit client + +(test-namespaces 'httpkit.client-test) + ;;;; final exit code (let [{:keys [:test :fail :error] :as m} @status] diff --git a/test-resources/lib_tests/httpkit/client_test.clj b/test-resources/lib_tests/httpkit/client_test.clj new file mode 100644 index 00000000..32727306 --- /dev/null +++ b/test-resources/lib_tests/httpkit/client_test.clj @@ -0,0 +1,64 @@ +(ns httpkit.client-test + (:require [cheshire.core :as json] + [clojure.java.io :as io] + [clojure.string :as str] + #_:clj-kondo/ignore + [clojure.test :refer [deftest is testing #_*report-counters*]] + [org.httpkit.client :as client]) + (:import (clojure.lang ExceptionInfo))) + +(defmethod clojure.test/report :begin-test-var [m] + (println "===" (-> m :var meta :name)) + (println)) + +#_(defmethod clojure.test/report :end-test-var [_m] + (let [{:keys [:fail :error]} @*report-counters*] + (when (and (= "true" (System/getenv "BABASHKA_FAIL_FAST")) + (or (pos? fail) (pos? error))) + (println "=== Failing fast") + (System/exit 1)))) + +(deftest get-test + (is (str/includes? (:status @(client/get "https://postman-echo.com/get")) + "200")) + (is (= "https://postman-echo.com/get" + (-> @(client/get "https://postman-echo.com/get" + {:headers {"Accept" "application/json"}}) + :body + (json/parse-string true) + :url))) + (testing "query params" + (is (= {:foo1 "bar1", :foo2 "bar2"} + (-> @(client/get "https://postman-echo.com/get" {:query-params {"foo1" "bar1" "foo2" "bar2"}}) + :body + (json/parse-string true) + :args))))) + +(deftest delete-test + (is (= 200 (:status @(client/delete "https://postman-echo.com/delete"))))) + +(deftest head-test + (is (= 200 (:status @(client/head "https://postman-echo.com/head"))))) + +(deftest basic-auth-test + (is (re-find #"authenticated.*true" + (:body + @(client/get "https://postman-echo.com/basic-auth" + {:basic-auth ["postman" "password"]}))))) + +(deftest get-response-object-test + (let [response @(client/get "https://httpstat.us/200")] + (is (map? response)) + (is (= 200 (:status response))) + (is (string? (get-in response [:headers :server])))) + + (testing "response object as stream" + (let [response @(client/get "https://httpstat.us/200" {:as :stream})] + (is (map? response)) + (is (= 200 (:status response))) + (is (instance? java.io.InputStream (:body response)))))) + +(require '[org.httpkit.sni-client :as sni]) + +(deftest alter-var-root-test + (is (alter-var-root (var client/*default-client*) (constantly sni/default-client))))