Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

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

## Unreleased

- [#80](https://github.com/babashka/http-client/issues/80): add idiomatic proxy configuration functionality

## 0.4.23 (2025-06-06)

- [#75](https://github.com/babashka/http-client/issues/75): override existing content type header in multipart request
Expand Down
24 changes: 21 additions & 3 deletions src/babashka/http_client.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,30 @@
i/default-client-opts)

(defn ->ProxySelector
"Constructs a `java.net.ProxySelector`.
"Constructs a `java.net.ProxySelector`. Can either take a map of `:host` and `:port` actions, or a proxy
creation function that takes a java.net.URI and returns a proxy configuration for ->Proxy.

Options:
* `:host` - string
* `:port` - long
Proxy function:
* `fn`: A one-argument function receiving a java.net.URI argument and returning a proxy configuration for ->Proxy or nil
(meaning don't use a proxy)."
[opts-or-fn]
(cond
(instance? java.net.ProxySelector opts-or-fn) opts-or-fn
(map? opts-or-fn) (i/->ProxySelector opts-or-fn)
:else (i/fn->ProxySelector opts-or-fn)))

(defn ->Proxy
"Constructs a `java.net.Proxy`.

Options:
* `:host` - string
* `:port` - long"
* `:port` - long
* `:type` - One of `:direct` (no proxy), `:socks` (socks proxy) or `:http` (application level proxy)."
[opts]
(i/->ProxySelector opts))
(i/->Proxy opts))

(defn ->SSLContext
"Constructs a `javax.net.ssl.SSLContext`.
Expand Down
27 changes: 27 additions & 0 deletions src/babashka/http_client/internal.clj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@
:never HttpClient$Redirect/NEVER
:normal HttpClient$Redirect/NORMAL))

(defn- ->proxy-type [type]
(case type
:direct java.net.Proxy$Type/DIRECT
:socks java.net.Proxy$Type/SOCKS
:http java.net.Proxy$Type/HTTP))

(defn- version-keyword->version-enum [version]
(case version
:http1.1 HttpClient$Version/HTTP_1_1
Expand Down Expand Up @@ -105,6 +111,27 @@
(cond (and host port)
(java.net.ProxySelector/of (java.net.InetSocketAddress. ^String host ^long port))))))

(defn ->Proxy
[opts]
(if (instance? java.net.Proxy opts)
opts
(let [{:keys [host port type]} opts]
(cond
(= type :direct)
java.net.Proxy/NO_PROXY
(and host port type)
(java.net.Proxy. (->proxy-type type) (java.net.InetSocketAddress. ^String host ^long port))))))

(defn fn->ProxySelector [proxy-fn]
(proxy [java.net.ProxySelector] []
(connectFailed [_ _ _])
(select [^URI uri]
;; Only allow the proxy function to return a single proxy.
;; I don't really see the use case for multiple.
[(if-let [proxy-opts (proxy-fn uri)]
(->Proxy proxy-opts)
java.net.Proxy/NO_PROXY)])))

(defn ->Authenticator
[v]
(if (instance? Authenticator v)
Expand Down
2 changes: 1 addition & 1 deletion test/babashka/http_client/internal/helpers_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@
[clojure.test :as t]))

(t/deftest ->uri-tests
(let [uri (h/->uri {:scheme "https" :host "example.com" :path "/foo"})]
(let [^java.net.URI uri (h/->uri {:scheme "https" :host "example.com" :path "/foo"})]
(t/is (= (.getPort uri) -1))))
19 changes: 18 additions & 1 deletion test/babashka/http_client_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[babashka.fs :as fs]
[babashka.http-client :as http]
[babashka.http-client.interceptors :as i]
[babashka.http-client.internal :as internal]
[babashka.http-client.internal.version :as iv]
[borkdude.deflet :refer [deflet]]
[cheshire.core :as json]
Expand Down Expand Up @@ -473,7 +474,23 @@
(deftest proxy-selector
(is (instance? java.net.ProxySelector
(http/->ProxySelector {:host "https://clojure.org"
:port 1337}))))
:port 1337})))
;; Check passthrough behavior.
(is (instance? java.net.ProxySelector
(http/->ProxySelector
(http/->ProxySelector {:host "https://clojure.org"
:port 1337}))))
(let [^java.net.ProxySelector complex-proxy-selector (http/->ProxySelector (fn [^java.net.URI uri]
(when (= (.getScheme uri) "http")
{:host "http://www.example.org"
:port 128
:type :http})))]
(is (instance? java.net.ProxySelector complex-proxy-selector))
(let [proxies-for-http (.select complex-proxy-selector (java.net.URI. "http://www.example.org"))
proxies-for-https (.select complex-proxy-selector (java.net.URI. "https://www.example.org"))]
(is (= (count proxies-for-http) (count proxies-for-https) 1))
(is (= (.type (get proxies-for-http 0)) java.net.Proxy$Type/HTTP))
(is (= (.type (get proxies-for-https 0)) java.net.Proxy$Type/DIRECT)))))

(deftest cookie-handler-test
(testing "nil passthrough"
Expand Down