Java http insecure context (#994)

* Add support for creating insecure context.

* Add BABASHKA_FEATURE_JAVA_NET_HTTP flag.

* Clean up java.net.http tests
This commit is contained in:
Michael Glaesemann 2021-08-31 10:37:11 -05:00 committed by GitHub
parent b71278cc68
commit 125e227976
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 278 additions and 228 deletions

View file

@ -20,6 +20,7 @@ ARG BABASHKA_LEAN=
ARG BABASHKA_MUSL= ARG BABASHKA_MUSL=
ARG BABASHKA_FEATURE_CORE_ASYNC= ARG BABASHKA_FEATURE_CORE_ASYNC=
ARG BABASHKA_FEATURE_CSV= ARG BABASHKA_FEATURE_CSV=
ARG BABASHKA_FEATURE_JAVA_NET_HTTP=
ARG BABASHKA_FEATURE_JAVA_NIO= ARG BABASHKA_FEATURE_JAVA_NIO=
ARG BABASHKA_FEATURE_JAVA_TIME= ARG BABASHKA_FEATURE_JAVA_TIME=
ARG BABAHSKA_FEATURE_TRANSIT= ARG BABAHSKA_FEATURE_TRANSIT=
@ -37,6 +38,7 @@ ARG BABASHKA_STATIC=
ENV BABASHKA_LEAN=$BABASHKA_LEAN ENV BABASHKA_LEAN=$BABASHKA_LEAN
ENV BABASHKA_FEATURE_CORE_ASYNC=$BABASHKA_FEATURE_CORE_ASYNC ENV BABASHKA_FEATURE_CORE_ASYNC=$BABASHKA_FEATURE_CORE_ASYNC
ENV BABASHKA_FEATURE_CSV=$BABASHKA_FEATURE_CSV ENV BABASHKA_FEATURE_CSV=$BABASHKA_FEATURE_CSV
ENV BABASHKA_FEATURE_JAVA_NET_HTTP=$BABASHKA_FEATURE_JAVA_NET_HTTP
ENV BABASHKA_FEATURE_JAVA_NIO=$BABASHKA_FEATURE_JAVA_NIO ENV BABASHKA_FEATURE_JAVA_NIO=$BABASHKA_FEATURE_JAVA_NIO
ENV BABASHKA_FEATURE_JAVA_TIME=$BABASHKA_FEATURE_JAVA_TIME ENV BABASHKA_FEATURE_JAVA_TIME=$BABASHKA_FEATURE_JAVA_TIME
ENV BABAHSKA_FEATURE_TRANSIT=$BABAHSKA_FEATURE_TRANSIT ENV BABAHSKA_FEATURE_TRANSIT=$BABAHSKA_FEATURE_TRANSIT

View file

@ -95,6 +95,7 @@ Babashka supports the following feature flags:
|--------|----------------------------------------------|----------| |--------|----------------------------------------------|----------|
| `BABASHKA_FEATURE_CORE_ASYNC` | Includes the [clojure.core.async](https://github.com/clojure/core.async) library | `true` | | `BABASHKA_FEATURE_CORE_ASYNC` | Includes the [clojure.core.async](https://github.com/clojure/core.async) library | `true` |
| `BABASHKA_FEATURE_CSV` | Includes the [clojure.data.csv](https://github.com/clojure/data.csv) library | `true` | | `BABASHKA_FEATURE_CSV` | Includes the [clojure.data.csv](https://github.com/clojure/data.csv) library | `true` |
| `BABASHKA_FEATURE_JAVA_NET_HTTP` | Includes commonly used classes from the `java.net.http` package | `true` |
| `BABASHKA_FEATURE_JAVA_NIO` | Includes commonly used classes from the `java.nio` package | `true` | | `BABASHKA_FEATURE_JAVA_NIO` | Includes commonly used classes from the `java.nio` package | `true` |
| `BABASHKA_FEATURE_JAVA_TIME` | Includes commonly used classes from the `java.time` package | `true` | | `BABASHKA_FEATURE_JAVA_TIME` | Includes commonly used classes from the `java.time` package | `true` |
| `BABASHKA_FEATURE_TRANSIT` | Includes the [transit-clj](https://github.com/cognitect/transit-clj) library | `true` | | `BABASHKA_FEATURE_TRANSIT` | Includes the [transit-clj](https://github.com/cognitect/transit-clj) library | `true` |

View file

@ -86,6 +86,7 @@ then
export BABASHKA_FEATURE_CSV="${BABASHKA_FEATURE_CSV:-false}" export BABASHKA_FEATURE_CSV="${BABASHKA_FEATURE_CSV:-false}"
export BABAHSKA_FEATURE_TRANSIT="${BABAHSKA_FEATURE_TRANSIT:-false}" export BABAHSKA_FEATURE_TRANSIT="${BABAHSKA_FEATURE_TRANSIT:-false}"
export BABASHKA_FEATURE_JAVA_TIME="${BABASHKA_FEATURE_JAVA_TIME:-false}" export BABASHKA_FEATURE_JAVA_TIME="${BABASHKA_FEATURE_JAVA_TIME:-false}"
export BABASHKA_FEATURE_JAVA_NET_HTTP="${BABASHKA_FEATURE_JAVA_NET_HTTP:-false}"
export BABASHKA_FEATURE_JAVA_NIO="${BABASHKA_FEATURE_JAVA_NIO:-false}" export BABASHKA_FEATURE_JAVA_NIO="${BABASHKA_FEATURE_JAVA_NIO:-false}"
export BABASHKA_FEATURE_HTTPKIT_CLIENT="${BABASHKA_FEATURE_HTTPKIT_CLIENT:-false}" export BABASHKA_FEATURE_HTTPKIT_CLIENT="${BABASHKA_FEATURE_HTTPKIT_CLIENT:-false}"
export BABASHKA_FEATURE_HTTPKIT_SERVER="${BABASHKA_FEATURE_HTTPKIT_SERVER:-false}" export BABASHKA_FEATURE_HTTPKIT_SERVER="${BABASHKA_FEATURE_HTTPKIT_SERVER:-false}"

View file

@ -18,6 +18,7 @@ then
export BABASHKA_FEATURE_CSV="${BABASHKA_FEATURE_CSV:-false}" export BABASHKA_FEATURE_CSV="${BABASHKA_FEATURE_CSV:-false}"
export BABAHSKA_FEATURE_TRANSIT="${BABAHSKA_FEATURE_TRANSIT:-false}" export BABAHSKA_FEATURE_TRANSIT="${BABAHSKA_FEATURE_TRANSIT:-false}"
export BABASHKA_FEATURE_JAVA_TIME="${BABASHKA_FEATURE_JAVA_TIME:-false}" export BABASHKA_FEATURE_JAVA_TIME="${BABASHKA_FEATURE_JAVA_TIME:-false}"
export BABASHKA_FEATURE_JAVA_NET_HTTP="${BABASHKA_FEATURE_JAVA_NET_HTTP:-false}"
export BABASHKA_FEATURE_JAVA_NIO="${BABASHKA_FEATURE_JAVA_NIO:-false}" export BABASHKA_FEATURE_JAVA_NIO="${BABASHKA_FEATURE_JAVA_NIO:-false}"
export BABASHKA_FEATURE_HTTPKIT_CLIENT="${BABASHKA_FEATURE_HTTPKIT_CLIENT:-false}" export BABASHKA_FEATURE_HTTPKIT_CLIENT="${BABASHKA_FEATURE_HTTPKIT_CLIENT:-false}"
export BABASHKA_FEATURE_HTTPKIT_SERVER="${BABASHKA_FEATURE_HTTPKIT_SERVER:-false}" export BABASHKA_FEATURE_HTTPKIT_SERVER="${BABASHKA_FEATURE_HTTPKIT_SERVER:-false}"

View file

@ -158,19 +158,12 @@
java.math.BigInteger java.math.BigInteger
java.math.MathContext java.math.MathContext
java.math.RoundingMode java.math.RoundingMode
java.net.Authenticator
java.net.ConnectException java.net.ConnectException
java.net.CookieHandler
java.net.CookieManager
java.net.CookieStore
java.net.DatagramSocket java.net.DatagramSocket
java.net.DatagramPacket java.net.DatagramPacket
java.net.HttpCookie
java.net.HttpURLConnection java.net.HttpURLConnection
java.net.InetAddress java.net.InetAddress
java.net.InetSocketAddress java.net.InetSocketAddress
java.net.PasswordAuthentication
java.net.ProxySelector
java.net.ServerSocket java.net.ServerSocket
java.net.Socket java.net.Socket
java.net.SocketException java.net.SocketException
@ -180,28 +173,41 @@
java.net.URLEncoder java.net.URLEncoder
java.net.URLDecoder java.net.URLDecoder
;; java.net.http ;; java.net.http
jdk.internal.net.http.HttpClientBuilderImpl ~@(when features/java-net-http?
jdk.internal.net.http.HttpClientFacade '[java.net.Authenticator
jdk.internal.net.http.HttpRequestBuilderImpl java.net.CookieHandler
jdk.internal.net.http.HttpResponseImpl java.net.CookieManager
jdk.internal.net.http.common.MinimalFuture java.net.CookieStore
jdk.internal.net.http.websocket.BuilderImpl java.net.HttpCookie
jdk.internal.net.http.websocket.WebSocketImpl java.net.PasswordAuthentication
java.net.http.HttpClient java.net.ProxySelector
java.net.http.HttpClient$Builder java.net.http.HttpClient
java.net.http.HttpClient$Redirect java.net.http.HttpClient$Builder
java.net.http.HttpClient$Version java.net.http.HttpClient$Redirect
java.net.http.HttpHeaders java.net.http.HttpClient$Version
java.net.http.HttpRequest java.net.http.HttpHeaders
java.net.http.HttpRequest$BodyPublisher java.net.http.HttpRequest
java.net.http.HttpRequest$BodyPublishers java.net.http.HttpRequest$BodyPublisher
java.net.http.HttpRequest$Builder java.net.http.HttpRequest$BodyPublishers
java.net.http.HttpResponse java.net.http.HttpRequest$Builder
java.net.http.HttpResponse$BodyHandler java.net.http.HttpResponse
java.net.http.HttpResponse$BodyHandlers java.net.http.HttpResponse$BodyHandler
java.net.http.WebSocket java.net.http.HttpResponse$BodyHandlers
java.net.http.WebSocket$Builder java.net.http.WebSocket
java.net.http.WebSocket$Listener java.net.http.WebSocket$Builder
java.net.http.WebSocket$Listener
java.security.cert.X509Certificate
javax.net.ssl.SSLContext
javax.net.ssl.SSLParameters
javax.net.ssl.TrustManager
javax.net.ssl.X509TrustManager
jdk.internal.net.http.HttpClientBuilderImpl
jdk.internal.net.http.HttpClientFacade
jdk.internal.net.http.HttpRequestBuilderImpl
jdk.internal.net.http.HttpResponseImpl
jdk.internal.net.http.common.MinimalFuture
jdk.internal.net.http.websocket.BuilderImpl
jdk.internal.net.http.websocket.WebSocketImpl])
~@(when features/java-nio? ~@(when features/java-nio?
'[java.nio.ByteBuffer '[java.nio.ByteBuffer
java.nio.ByteOrder java.nio.ByteOrder
@ -312,8 +318,6 @@
java.util.zip.ZipInputStream java.util.zip.ZipInputStream
java.util.zip.ZipOutputStream java.util.zip.ZipOutputStream
java.util.zip.ZipEntry java.util.zip.ZipEntry
javax.net.ssl.SSLContext
javax.net.ssl.SSLParameters
~(symbol "[B") ~(symbol "[B")
~(symbol "[I") ~(symbol "[I")
~(symbol "[Ljava.lang.Object;") ~(symbol "[Ljava.lang.Object;")

View file

@ -8,6 +8,7 @@
(def csv? (not= "false" (System/getenv "BABASHKA_FEATURE_CSV"))) (def csv? (not= "false" (System/getenv "BABASHKA_FEATURE_CSV")))
(def transit? (not= "false" (System/getenv "BABASHKA_FEATURE_TRANSIT"))) (def transit? (not= "false" (System/getenv "BABASHKA_FEATURE_TRANSIT")))
(def java-time? (not= "false" (System/getenv "BABASHKA_FEATURE_JAVA_TIME"))) (def java-time? (not= "false" (System/getenv "BABASHKA_FEATURE_JAVA_TIME")))
(def java-net-http? (not= "false" (System/getenv "BABASHKA_FEATURE_JAVA_NET_HTTP")))
(def java-nio? (not= "false" (System/getenv "BABASHKA_FEATURE_JAVA_NIO"))) (def java-nio? (not= "false" (System/getenv "BABASHKA_FEATURE_JAVA_NIO")))
(def httpkit-client? (not= "false" (System/getenv "BABASHKA_FEATURE_HTTPKIT_CLIENT"))) (def httpkit-client? (not= "false" (System/getenv "BABASHKA_FEATURE_HTTPKIT_CLIENT")))
(def httpkit-server? (not= "false" (System/getenv "BABASHKA_FEATURE_HTTPKIT_SERVER"))) (def httpkit-server? (not= "false" (System/getenv "BABASHKA_FEATURE_HTTPKIT_SERVER")))

View file

@ -168,4 +168,9 @@
{get [[this]]} {get [[this]]}
java.lang.Comparable java.lang.Comparable
{compareTo [[this other]]}})) {compareTo [[this other]]}
javax.net.ssl.X509TrustManager
{checkClientTrusted [[this chain auth-type]]
checkServerTrusted [[this chain auth-type]]
getAcceptedIssuers [[this]]}}))

View file

@ -1,4 +1,4 @@
(ns babashka.java-http-client-test (ns babashka.java-net-http-test
(:require (:require
[babashka.test-utils :as test-utils] [babashka.test-utils :as test-utils]
[clojure.edn :as edn] [clojure.edn :as edn]
@ -9,7 +9,9 @@
(defn bb [expr] (defn bb [expr]
(edn/read-string (apply test-utils/bb nil [(str expr)]))) (edn/read-string (apply test-utils/bb nil [(str expr)])))
(deftest java-http-client-test ;; HttpClient
(deftest send-test
(is (= [200 true] (is (= [200 true]
(bb (bb
'(do (ns net '(do (ns net
@ -24,48 +26,91 @@
(.GET) (.GET)
(.build))) (.build)))
(def client (def client (HttpClient/newHttpClient))
(-> (HttpClient/newBuilder)
(.build)))
(def resp (.send client req (HttpResponse$BodyHandlers/ofString))) (def res (.send client req (HttpResponse$BodyHandlers/ofString)))
[(.statusCode resp) (string? (.body resp))]))))) [(.statusCode res) (string? (.body res))])))))
(deftest redirect-test (deftest send-async-test
(let [redirect-prog (is (= [200 true]
(fn [redirect-kind] (bb
(str/replace (str '(do '(do
(ns net (ns net
(:import (:import
(java.net.http HttpClient (java.net URI)
HttpClient$Redirect (java.net.http HttpClient
HttpRequest HttpRequest
HttpRequest$BodyPublishers HttpResponse$BodyHandlers)
HttpResponse$BodyHandlers) (java.util.function Function)))
(java.net URI)))
(defn log [x] (.println System/err x)) (let [client (HttpClient/newHttpClient)
(let [req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com")) req (-> (HttpRequest/newBuilder (URI. "https://www.clojure.org"))
(.GET) (.GET)
(.timeout (java.time.Duration/ofSeconds 5)) (.build))]
(.build)) (-> (.sendAsync client req (HttpResponse$BodyHandlers/ofString))
client (-> (HttpClient/newBuilder) (.thenApply (reify Function (apply [_ res] [(.statusCode res) (string? (.body res))])))
(.followRedirects :redirect/kind) (deref))))))))
(.build))
handler (HttpResponse$BodyHandlers/discarding)] ;; HttpClient options
(.statusCode (.send client req handler)))))
":redirect/kind" (deftest authenticator-test
(case redirect-kind (is (= [401 200]
:never (bb
"HttpClient$Redirect/NEVER" '(do
:always (ns net
"HttpClient$Redirect/ALWAYS")))] (:import
;; TODO: make graalvm repro of never-ending request with redirect always on linux aarch64 (+ musl?) (java.net Authenticator
(when-not (and (= "aarch64" (System/getenv "BABASHKA_ARCH")) PasswordAuthentication
(= "linux" (System/getenv "BABASHKA_PLATFORM"))) URI)
(println "Testing redirect always") (java.net.http HttpClient
(is (= 200 (bb (redirect-prog :always))))) HttpRequest
(println "Testing redirect never") HttpResponse$BodyHandlers)))
(is (= 302 (bb (redirect-prog :never))))))
(let [no-auth-client (HttpClient/newHttpClient)
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/basic-auth"))
(.build))
handler (HttpResponse$BodyHandlers/discarding)
no-auth-res (.send no-auth-client req handler)
authenticator (proxy [Authenticator] []
(getPasswordAuthentication []
(PasswordAuthentication. "postman" (char-array "password"))))
auth-client (-> (HttpClient/newBuilder)
(.authenticator authenticator)
(.build))
auth-res (.send auth-client req handler)]
[(.statusCode no-auth-res) (.statusCode auth-res)]))))))
(deftest cookie-test
(is (= []
(bb '(do (ns net
(:import [java.net CookieManager]))
(-> (CookieManager.)
(.getCookieStore)
(.getCookies))))))
(is (= "www.postman-echo.com"
(bb '(do
(ns net
(:import
(java.net CookieManager
URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)))
(let [client (-> (HttpClient/newBuilder)
(.cookieHandler (CookieManager.))
(.build))
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/get"))
(.GET)
(.build))]
(.send client req (HttpResponse$BodyHandlers/discarding))
(-> client
(.cookieHandler)
(.get)
(.getCookieStore)
(.getCookies)
first
(.getDomain))))))))
(deftest connect-timeout-test (deftest connect-timeout-test
(is (= "java.net.http.HttpConnectTimeoutException" (is (= "java.net.http.HttpConnectTimeoutException"
@ -94,7 +139,7 @@
:type :type
name))))))))) name)))))))))
(deftest executor (deftest executor-test
(is (= 200 (is (= 200
(bb (bb
'(do '(do
@ -105,6 +150,7 @@
HttpRequest HttpRequest
HttpResponse$BodyHandlers) HttpResponse$BodyHandlers)
(java.util.concurrent Executors))) (java.util.concurrent Executors)))
(let [uri (URI. "https://www.postman-echo.com/get") (let [uri (URI. "https://www.postman-echo.com/get")
req (-> (HttpRequest/newBuilder uri) req (-> (HttpRequest/newBuilder uri)
(.GET) (.GET)
@ -115,7 +161,7 @@
res (.send client req (HttpResponse$BodyHandlers/discarding))] res (.send client req (HttpResponse$BodyHandlers/discarding))]
(.statusCode res))))))) (.statusCode res)))))))
(deftest client-proxy (deftest proxy-test
(is (= true (is (= true
(bb (bb
'(do '(do
@ -123,6 +169,7 @@
(:import (:import
(java.net ProxySelector) (java.net ProxySelector)
(java.net.http HttpClient))) (java.net.http HttpClient)))
(let [bespoke-proxy (proxy [ProxySelector] [] (let [bespoke-proxy (proxy [ProxySelector] []
(connectFailed [_ _ _]) (connectFailed [_ _ _])
(select [_ _])) (select [_ _]))
@ -142,6 +189,7 @@
(java.net.http HttpClient (java.net.http HttpClient
HttpRequest HttpRequest
HttpResponse$BodyHandlers))) HttpResponse$BodyHandlers)))
(let [uri (URI. "https://www.postman-echo.com/get") (let [uri (URI. "https://www.postman-echo.com/get")
req (-> (HttpRequest/newBuilder uri) req (-> (HttpRequest/newBuilder uri)
(.build)) (.build))
@ -151,50 +199,135 @@
res (.send client req (HttpResponse$BodyHandlers/discarding))] res (.send client req (HttpResponse$BodyHandlers/discarding))]
(.statusCode res))))))) (.statusCode res)))))))
(deftest ssl-test (deftest redirect-test
(is (= 200 (let [redirect-prog
(bb (fn [redirect-kind]
'(do (str/replace (str '(do
(ns net (ns net
(:import (:import
(java.net URI) (java.net.http HttpClient
(java.net.http HttpClient HttpClient$Redirect
HttpRequest HttpRequest
HttpResponse$BodyHandlers) HttpRequest$BodyPublishers
(javax.net.ssl SSLContext HttpResponse$BodyHandlers)
SSLParameters))) (java.net URI)))
(let [uri (URI. "https://www.postman-echo.com/get")
req (-> (HttpRequest/newBuilder uri)
(.build))
ssl-context (doto (SSLContext/getInstance "TLS")
(.init nil nil nil))
client (-> (HttpClient/newBuilder)
(.sslContext ssl-context)
(.build))
res (.send client req (HttpResponse$BodyHandlers/discarding))]
(.statusCode res)))))))
(deftest send-async-test (defn log [x] (.println System/err x))
(is (= 200 (let [req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com"))
(bb (.GET)
'(do (.timeout (java.time.Duration/ofSeconds 5))
(ns net (.build))
(:import client (-> (HttpClient/newBuilder)
(java.net ProxySelector (.followRedirects :redirect/kind)
URI) (.build))
(java.net.http HttpClient handler (HttpResponse$BodyHandlers/discarding)]
HttpRequest (.statusCode (.send client req handler)))))
HttpResponse$BodyHandlers) ":redirect/kind"
(java.time Duration) (case redirect-kind
(java.util.function Function))) :never
(let [client (-> (HttpClient/newBuilder) "HttpClient$Redirect/NEVER"
(.build)) :always
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/get")) "HttpClient$Redirect/ALWAYS")))]
(.GET) ;; TODO: make graalvm repro of never-ending request with redirect always on linux aarch64 (+ musl?)
(.build))] (when-not (and (= "aarch64" (System/getenv "BABASHKA_ARCH"))
(-> (.sendAsync client req (HttpResponse$BodyHandlers/discarding)) (= "linux" (System/getenv "BABASHKA_PLATFORM")))
(.thenApply (reify Function (apply [_ t] (.statusCode t)))) (println "Testing redirect always")
(deref)))))))) (is (= 200 (bb (redirect-prog :always)))))
(println "Testing redirect never")
(is (= 302 (bb (redirect-prog :never))))))
(deftest ssl-context-test
;; TODO: investigate aarch64 issue
(when-not
(and (= "aarch64" (System/getenv "BABASHKA_ARCH"))
(= "linux" (System/getenv "BABASHKA_PLATFORM")))
(is (= {:expired "java.security.cert.CertificateExpiredException"
:revoked 200 ;; TODO: fix, "sun.security.cert.CertificateRevokedException"
:self-signed "sun.security.provider.certpath.SunCertPathBuilderException"
:untrusted-root "sun.security.provider.certpath.SunCertPathBuilderException"
:wrong-host "sun.security.provider.certpath.SunCertPathBuilderException"}
(bb
'(do
(ns net
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)))
(defn send-and-catch [client req handler]
(try
(let [res (.send client req (HttpResponse$BodyHandlers/discarding))]
(.statusCode res))
(catch Throwable t
(-> (Throwable->map t) :via last :type name))))
(let [client (HttpClient/newHttpClient)
handler (HttpResponse$BodyHandlers/discarding)
reqs (->> [:expired
:self-signed
:revoked
:untrusted-root
:wrong-host]
(map (fn [k]
(let [req (-> (URI. (format "https://%s.badssl.com" (name k)))
(HttpRequest/newBuilder)
(.GET)
(.build))]
[k req])))
(into {}))]
(->> reqs
(map (fn [[k req]]
[k (send-and-catch client req handler)]))
(into {})))))))
(is (= {:expired 200
:self-signed 200
:untrusted-root 200}
(bb
'(do
(ns net
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)
(java.security SecureRandom)
(java.security.cert X509Certificate)
(javax.net.ssl SSLContext
TrustManager
X509TrustManager)))
(let [insecure-trust-manager (reify X509TrustManager
(checkClientTrusted [_ _ _])
(checkServerTrusted [_ _ _])
(getAcceptedIssuers [_] (into-array X509Certificate [])))
insecure-trust-managers (into-array TrustManager [insecure-trust-manager])
insecure-context (doto (SSLContext/getInstance "TLS")
(.init nil
insecure-trust-managers
(SecureRandom.)))
client (-> (HttpClient/newBuilder)
(.sslContext insecure-context)
(.build))
handler (HttpResponse$BodyHandlers/discarding)
reqs (->> [:expired
:self-signed
:untrusted-root]
(map (fn [k]
(let [req (-> (URI. (format "https://%s.badssl.com" (name k)))
(HttpRequest/newBuilder)
(.GET)
(.build))]
[k req])))
(into {}))]
(->> reqs
(map (fn [[k req]]
[k (-> (.send client req handler)
(.statusCode))]))
(into {})))))))))
;; HttpRequest
(deftest body-publishers-test (deftest body-publishers-test
(is (= true (is (= true
@ -212,15 +345,16 @@
HttpRequest$BodyPublishers HttpRequest$BodyPublishers
HttpResponse$BodyHandlers) HttpResponse$BodyHandlers)
(java.util.function Supplier))) (java.util.function Supplier)))
(let [bp (HttpRequest$BodyPublishers/ofFile (.toPath (io/file "README.md"))) (let [bp (HttpRequest$BodyPublishers/ofFile (.toPath (io/file "README.md")))
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/post")) req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/post"))
(.method "POST" bp) (.method "POST" bp)
(.build)) (.build))
client (-> (HttpClient/newBuilder) client (HttpClient/newHttpClient)
(.build))
res (.send client req (HttpResponse$BodyHandlers/ofString)) res (.send client req (HttpResponse$BodyHandlers/ofString))
body-data (-> (.body res) (json/parse-string true) :data)] body-data (-> (.body res) (json/parse-string true) :data)]
(str/includes? body-data "babashka")))))) (str/includes? body-data "babashka"))))))
(let [body "with love from java.net.http"] (let [body "with love from java.net.http"]
(is (= {:of-input-stream body (is (= {:of-input-stream body
:of-byte-array body :of-byte-array body
@ -238,6 +372,7 @@
HttpRequest$BodyPublishers HttpRequest$BodyPublishers
HttpResponse$BodyHandlers) HttpResponse$BodyHandlers)
(java.util.function Supplier))) (java.util.function Supplier)))
(let [body "with love from java.net.http" (let [body "with love from java.net.http"
publishers {:of-input-stream (HttpRequest$BodyPublishers/ofInputStream publishers {:of-input-stream (HttpRequest$BodyPublishers/ofInputStream
(reify Supplier (get [_] (io/input-stream (.getBytes body))))) (reify Supplier (get [_] (io/input-stream (.getBytes body)))))
@ -272,122 +407,19 @@
HttpResponse$BodyHandlers) HttpResponse$BodyHandlers)
(java.nio.charset Charset) (java.nio.charset Charset)
(java.util.function Supplier))) (java.util.function Supplier)))
(let [body "おはようございます!" (let [body "おはようございます!"
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/post")) req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/post"))
(.method "POST" (HttpRequest$BodyPublishers/ofString (.method "POST" (HttpRequest$BodyPublishers/ofString
body (Charset/forName "UTF-16"))) body (Charset/forName "UTF-16")))
(.header "Content-Type" "text/plain; charset=utf-16") (.header "Content-Type" "text/plain; charset=utf-16")
(.build)) (.build))
client (-> (HttpClient/newBuilder) client (HttpClient/newHttpClient)
(.build))
res (.send client req (HttpResponse$BodyHandlers/ofString))] res (.send client req (HttpResponse$BodyHandlers/ofString))]
(-> (.body res) (-> (.body res)
(json/parse-string true) (json/parse-string true)
:data))))))))) :data)))))))))
(deftest cookie-test
(is (= []
(bb '(do (ns net
(:import [java.net CookieManager]))
(-> (CookieManager.)
(.getCookieStore)
(.getCookies))))))
(is (= "www.postman-echo.com"
(bb '(do
(ns net
(:import
(java.net CookieManager
URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)))
(let [client (-> (HttpClient/newBuilder)
(.cookieHandler (CookieManager.))
(.build))
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/get"))
(.GET)
(.build))]
(.send client req (HttpResponse$BodyHandlers/discarding))
(-> client
(.cookieHandler)
(.get)
(.getCookieStore)
(.getCookies)
first
(.getDomain))))))))
(deftest authenticator-test
(is (= [401 200]
(bb
'(do
(ns net
(:import
(java.net Authenticator
PasswordAuthentication
URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)))
(let [no-auth-client (-> (HttpClient/newBuilder)
(.build))
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/basic-auth"))
(.build))
handler (HttpResponse$BodyHandlers/discarding)
no-auth-res (.send no-auth-client req handler)
authenticator (proxy [Authenticator] []
(getPasswordAuthentication []
(PasswordAuthentication. "postman" (char-array "password"))))
auth-client (-> (HttpClient/newBuilder)
(.authenticator authenticator)
(.build))
auth-res (.send auth-client req handler)]
[(.statusCode no-auth-res) (.statusCode auth-res)]))))))
(deftest cert-test
;; TODO: investigate aarch64 issue
(when-not
(and (= "aarch64" (System/getenv "BABASHKA_ARCH"))
(= "linux" (System/getenv "BABASHKA_PLATFORM")))
(is (= {:expired "java.security.cert.CertificateExpiredException"
:revoked 200 ;; TODO: fix, "sun.security.cert.CertificateRevokedException"
:self-signed "sun.security.provider.certpath.SunCertPathBuilderException"
:untrusted-root "sun.security.provider.certpath.SunCertPathBuilderException"
:wrong-host "sun.security.provider.certpath.SunCertPathBuilderException"}
(bb
'(do
(ns net
(:import
(java.net URI)
(java.net.http HttpClient
HttpRequest
HttpResponse$BodyHandlers)))
(defn send-and-catch [client req handler]
(try
(let [res (.send client req (HttpResponse$BodyHandlers/discarding))]
(.statusCode res))
(catch Throwable t
(-> (Throwable->map t) :via last :type name))))
(let [client (-> (HttpClient/newBuilder)
(.build))
handler (HttpResponse$BodyHandlers/discarding)
reqs (->> [:expired
:self-signed
:revoked
:untrusted-root
:wrong-host]
(map (fn [k]
(let [req (-> (URI. (format "https://%s.badssl.com" (name k)))
(HttpRequest/newBuilder)
(.GET)
(.build))]
[k req])))
(into {}))]
(->> reqs
(map (fn [[k req]]
[k (send-and-catch client req handler)]))
(into {})))))))))
(deftest request-timeout-test (deftest request-timeout-test
(is (= "java.net.http.HttpTimeoutException" (is (= "java.net.http.HttpTimeoutException"
@ -401,8 +433,7 @@
HttpResponse$BodyHandlers) HttpResponse$BodyHandlers)
(java.time Duration))) (java.time Duration)))
(let [client (-> (HttpClient/newBuilder) (let [client (HttpClient/newHttpClient)
(.build))
req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/delay/1")) req (-> (HttpRequest/newBuilder (URI. "https://www.postman-echo.com/delay/1"))
(.GET) (.GET)
(.timeout (Duration/ofMillis 200)) (.timeout (Duration/ofMillis 200))
@ -430,6 +461,7 @@
HttpResponse$BodyHandlers) HttpResponse$BodyHandlers)
(java.nio.file Files StandardOpenOption) (java.nio.file Files StandardOpenOption)
(java.nio.file.attribute FileAttribute))) (java.nio.file.attribute FileAttribute)))
(let [client (-> (HttpClient/newBuilder) (let [client (-> (HttpClient/newBuilder)
(.build)) (.build))
uri (URI. "https://raw.githubusercontent.com/babashka/babashka/master/README.md") uri (URI. "https://raw.githubusercontent.com/babashka/babashka/master/README.md")
@ -445,6 +477,8 @@
contents (slurp temp-file-path)] contents (slurp temp-file-path)]
(str/includes? contents "babashka"))))))) (str/includes? contents "babashka")))))))
;; WebSockets
(defn ws-handler [{:keys [init] :as opts} req] (defn ws-handler [{:keys [init] :as opts} req]
(when init (init req)) (when init (init req))
(httpkit.server/as-channel (httpkit.server/as-channel
@ -472,6 +506,7 @@
WebSocket$Listener) WebSocket$Listener)
(java.util.concurrent CompletableFuture) (java.util.concurrent CompletableFuture)
(java.util.function Function))) (java.util.function Function)))
(let [p (promise) (let [p (promise)
uri (URI. "ws://localhost:1234") uri (URI. "ws://localhost:1234")
listener (reify WebSocket$Listener listener (reify WebSocket$Listener