Skip to content
Closed
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
13 changes: 9 additions & 4 deletions src/babashka/http_client.clj
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@
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` and `type` options or a proxy
creation function that takes a java.net.URI and returns a map of these parameters for creating a proxy dynamically.

Options:
* `:host` - string
* `:port` - long"
[opts]
(i/->ProxySelector opts))
* `:port` - long
* `:type` - `:direct` (don't use a proxy, host and port will be ignored), `:socks` (socks proxy) or `:http` (HTTP proxy, default value)."
[opts-or-fn]
(if (instance? java.net.ProxySelector opts-or-fn)
Comment thread
jeeger marked this conversation as resolved.
opts-or-fn
(i/->ProxySelector opts-or-fn)))

(defn ->SSLContext
"Constructs a `javax.net.ssl.SSLContext`.
Expand Down
44 changes: 39 additions & 5 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 @@ -97,13 +103,41 @@
trust-managers
(SecureRandom.))))))

(defn ->ProxySelector
(defn ->Proxy
[opts]
(if (instance? java.net.ProxySelector opts)
(if (instance? java.net.Proxy opts)
opts
(let [{:keys [host port]} opts]
(cond (and host port)
(java.net.ProxySelector/of (java.net.InetSocketAddress. ^String host ^long port))))))
(let [{:keys [host port type] :or {type :http}} 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))
:else
(throw (ex-info "Don't know how to create proxy from options." {:opts opts}))))))

(defn ->ProxySelector
[opts-or-fn]
(cond
(instance? java.net.ProxySelector opts-or-fn)
opts-or-fn
(fn? opts-or-fn)
;; Create a dynamic proxy selector.
(proxy [java.net.ProxySelector] []
(connectFailed [_ _ _])
(select [^URI uri]
;; Only allow the proxy function to return a single proxy.
;; Returning multiple is currently not supported.
[(if-let [proxy-values (opts-or-fn uri)]
(->Proxy proxy-values)
java.net.Proxy/NO_PROXY)]))
:else
;; Return a static proxy configuration that always returns the same proxy.
(let [static-proxy (->Proxy opts-or-fn)]
(proxy [java.net.ProxySelector] []
(connectFailed [_ _ _])
(select [^URI _uri]
[static-proxy])))))

(defn ->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))))
27 changes: 26 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,31 @@
(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))))
(let [^java.net.ProxySelector static-proxy-selector (http/->ProxySelector
{:host "127.0.0.1"
:port 8081
:type :socks})]
(let [selected-proxies (.select static-proxy-selector (java.net.URI. "http://www.example.org"))]
(is (= (count selected-proxies) 1))
(is (= (.type (selected-proxies 0)) java.net.Proxy$Type/SOCKS))
(is (= (.address (selected-proxies 0)) (java.net.InetSocketAddress. "127.0.0.1" 8081))))))

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