Compare commits
1 commit
master
...
java-11-cl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b52758820 |
4 changed files with 303 additions and 1 deletions
7
src/babashka/impl/http_client.clj
Normal file
7
src/babashka/impl/http_client.clj
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
(ns babashka.impl.http-client
|
||||
(:require [babashka.impl.http-client.core :as client]))
|
||||
|
||||
(def http-client-namespace
|
||||
{'get client/get
|
||||
'post client/post
|
||||
'send-async client/send-async})
|
||||
275
src/babashka/impl/http_client/core.clj
Normal file
275
src/babashka/impl/http_client/core.clj
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
(ns babashka.impl.http-client.core
|
||||
(:refer-clojure :exclude [send get])
|
||||
(:require [babashka.impl.http-client.util :as util :refer [add-docstring]]
|
||||
[clojure.string :as str])
|
||||
(:import [java.net CookieHandler ProxySelector URI]
|
||||
[java.net.http
|
||||
HttpClient
|
||||
HttpClient$Builder
|
||||
HttpClient$Redirect
|
||||
HttpClient$Version
|
||||
HttpRequest
|
||||
HttpRequest$BodyPublishers
|
||||
HttpRequest$Builder
|
||||
HttpResponse
|
||||
HttpResponse$BodyHandlers]
|
||||
[java.time Duration]
|
||||
[java.util.concurrent CompletableFuture Executor]
|
||||
[java.util.function Function Supplier]
|
||||
[javax.net.ssl SSLContext SSLParameters]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(defn- version-keyword->version-enum [version]
|
||||
(case version
|
||||
:http1.1 HttpClient$Version/HTTP_1_1
|
||||
:http2 HttpClient$Version/HTTP_2))
|
||||
|
||||
(defn- convert-follow-redirect [redirect]
|
||||
(case redirect
|
||||
:always HttpClient$Redirect/ALWAYS
|
||||
:never HttpClient$Redirect/NEVER
|
||||
:normal HttpClient$Redirect/NORMAL))
|
||||
|
||||
(defn client-builder
|
||||
(^HttpClient$Builder []
|
||||
(client-builder {}))
|
||||
(^HttpClient$Builder [opts]
|
||||
(let [{:keys [connect-timeout
|
||||
cookie-handler
|
||||
executor
|
||||
follow-redirects
|
||||
priority
|
||||
proxy
|
||||
ssl-context
|
||||
ssl-parameters
|
||||
version]} opts]
|
||||
(cond-> (HttpClient/newBuilder)
|
||||
connect-timeout (.connectTimeout (util/convert-timeout connect-timeout))
|
||||
cookie-handler (.cookieHandler cookie-handler)
|
||||
executor (.executor executor)
|
||||
follow-redirects (.followRedirects (convert-follow-redirect follow-redirects))
|
||||
priority (.priority priority)
|
||||
proxy (.proxy proxy)
|
||||
ssl-context (.sslContext ssl-context)
|
||||
ssl-parameters (.sslParameters ssl-parameters)
|
||||
version (.version (version-keyword->version-enum version))))))
|
||||
|
||||
(defn build-client
|
||||
(^HttpClient [] (.build (client-builder)))
|
||||
(^HttpClient [opts] (.build (client-builder opts))))
|
||||
|
||||
(def ^HttpClient default-client
|
||||
(delay (HttpClient/newHttpClient)))
|
||||
|
||||
(def ^:private byte-array-class
|
||||
(Class/forName "[B"))
|
||||
|
||||
(defn- input-stream-supplier [s]
|
||||
(reify Supplier
|
||||
(get [this] s)))
|
||||
|
||||
(defn- convert-body-publisher [body]
|
||||
(cond
|
||||
(nil? body)
|
||||
(HttpRequest$BodyPublishers/noBody)
|
||||
|
||||
(string? body)
|
||||
(HttpRequest$BodyPublishers/ofString body)
|
||||
|
||||
(instance? java.io.InputStream body)
|
||||
(HttpRequest$BodyPublishers/ofInputStream (input-stream-supplier body))
|
||||
|
||||
(instance? byte-array-class body)
|
||||
(HttpRequest$BodyPublishers/ofByteArray body)))
|
||||
|
||||
(def ^:private convert-headers-xf
|
||||
(mapcat
|
||||
(fn [[k v :as p]]
|
||||
(if (sequential? v)
|
||||
(interleave (repeat k) v)
|
||||
p))))
|
||||
|
||||
(defn- method-keyword->str [method]
|
||||
(str/upper-case (name method)))
|
||||
(defn request-builder ^HttpRequest$Builder [opts]
|
||||
(let [{:keys [expect-continue?
|
||||
headers
|
||||
method
|
||||
timeout
|
||||
uri
|
||||
version
|
||||
body]} opts]
|
||||
(cond-> (HttpRequest/newBuilder)
|
||||
(some? expect-continue?) (.expectContinue expect-continue?)
|
||||
(seq headers) (.headers (into-array String (eduction convert-headers-xf headers)))
|
||||
method (.method (method-keyword->str method) (convert-body-publisher body))
|
||||
timeout (.timeout (util/convert-timeout timeout))
|
||||
uri (.uri (URI/create uri))
|
||||
version (.version (version-keyword->version-enum version)))))
|
||||
|
||||
(defn build-request
|
||||
(^HttpRequest [] (.build (request-builder {})))
|
||||
(^HttpRequest [req-map] (.build (request-builder req-map))))
|
||||
|
||||
(def ^:private bh-of-string (HttpResponse$BodyHandlers/ofString))
|
||||
(def ^:private bh-of-input-stream (HttpResponse$BodyHandlers/ofInputStream))
|
||||
(def ^:private bh-of-byte-array (HttpResponse$BodyHandlers/ofByteArray))
|
||||
|
||||
(defn- convert-body-handler [mode]
|
||||
(case mode
|
||||
nil bh-of-string
|
||||
:string bh-of-string
|
||||
:input-stream bh-of-input-stream
|
||||
:byte-array bh-of-byte-array))
|
||||
|
||||
(defn- version-enum->version-keyword [^HttpClient$Version version]
|
||||
(case (.name version)
|
||||
"HTTP_1_1" :http1.1
|
||||
"HTTP_2" :http2))
|
||||
|
||||
(defn response->map [^HttpResponse resp]
|
||||
{:status (.statusCode resp)
|
||||
:body (.body resp)
|
||||
:version (-> resp .version version-enum->version-keyword)
|
||||
:headers (into {}
|
||||
(map (fn [[k v]] [k (if (> (count v) 1) (vec v) (first v))]))
|
||||
(.map (.headers resp)))})
|
||||
|
||||
|
||||
(def ^:private ^Function resp->ring-function
|
||||
(util/clj-fn->function response->map))
|
||||
|
||||
(defn- convert-request [req]
|
||||
(cond
|
||||
(map? req) (build-request req)
|
||||
(string? req) (build-request {:uri req})
|
||||
(instance? HttpRequest req) req))
|
||||
|
||||
(defn send
|
||||
([req]
|
||||
(send req {}))
|
||||
([req {:keys [as client raw?] :as opts}]
|
||||
(let [^HttpClient client (or client @default-client)
|
||||
req' (convert-request req)
|
||||
resp (.send client req' (convert-body-handler as))]
|
||||
(if raw? resp (response->map resp)))))
|
||||
|
||||
(defn send-async
|
||||
(^CompletableFuture [req]
|
||||
(send-async req {} nil nil))
|
||||
(^CompletableFuture [req opts]
|
||||
(send-async req opts nil nil))
|
||||
(^CompletableFuture [req {:keys [as client raw?] :as opts} callback ex-handler]
|
||||
(let [^HttpClient client (or client @default-client)
|
||||
req' (convert-request req)]
|
||||
(cond-> (.sendAsync client req' (convert-body-handler as))
|
||||
(not raw?) (.thenApply resp->ring-function)
|
||||
callback (.thenApply (util/clj-fn->function callback))
|
||||
ex-handler (.exceptionally (util/clj-fn->function ex-handler))))))
|
||||
|
||||
(defn- shorthand-docstring [method]
|
||||
(str "Sends a " (method-keyword->str method) " request to `uri`.
|
||||
|
||||
See [[send]] for a description of `req-map` and `opts`."))
|
||||
|
||||
(defn- defshorthand [method]
|
||||
`(defn ~(symbol (name method))
|
||||
~(shorthand-docstring method)
|
||||
(~['uri]
|
||||
(send ~{:uri 'uri :method method} {}))
|
||||
(~['uri 'req-map]
|
||||
(send (merge ~'req-map ~{:uri 'uri :method method}) {}))
|
||||
(~['uri 'req-map 'opts]
|
||||
(send (merge ~'req-map ~{:uri 'uri :method method}) ~'opts))))
|
||||
|
||||
(def ^:private shorthands [:get :head :post :put :delete])
|
||||
|
||||
(defmacro ^:private def-all-shorthands []
|
||||
`(do ~@(map defshorthand shorthands)))
|
||||
|
||||
(def-all-shorthands)
|
||||
|
||||
(add-docstring #'default-client
|
||||
"Used for requests unless a client is explicitly passed. Equal to [HttpClient.newHttpClient()](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html#newHttpClient%28%29).")
|
||||
|
||||
(add-docstring #'client-builder
|
||||
"Same as [[build-client]], but returns a [HttpClient.Builder](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.Builder.html) instead of a [HttpClient](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html).
|
||||
|
||||
See [[build-client]] for a description of `opts`.")
|
||||
|
||||
|
||||
(add-docstring #'build-client
|
||||
"Builds a client with the supplied options. See [HttpClient.Builder](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.Builder.html) for a more detailed description of the options.
|
||||
|
||||
The `opts` map takes the following keys:
|
||||
|
||||
- `:connect-timeout` - connection timeout in milliseconds or a `java.time.Duration`
|
||||
- `:cookie-handler` - a [java.net.CookieHandler](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/CookieHandler.html)
|
||||
- `:executor` - a [java.util.concurrent.Executor](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Executor.html)
|
||||
- `:follow-redirects` - one of `:always`, `:never` and `:normal`. Maps to the corresponding [HttpClient.Redirect](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.Redirect.html) enum.
|
||||
- `:priority` - the [priority](https://developers.google.com/web/fundamentals/performance/http2/#stream_prioritization) of the request (only used for HTTP/2 requests)
|
||||
- `:proxy` - a [java.net.ProxySelector](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/ProxySelector.html)
|
||||
- `:ssl-context` - a [javax.net.ssl.SSLContext](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/SSLContext.html)
|
||||
- `:ssl-parameters` - a [javax.net.ssl.SSLParameters](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/javax/net/ssl/SSLParameters.html)
|
||||
- `:version` - the HTTP protocol version, one of `:http1.1` or `:http2`
|
||||
|
||||
Equivalent to `(.build (client-builder opts))`.")
|
||||
|
||||
(add-docstring #'request-builder
|
||||
"Same as [[build-request]], but returns a [HttpRequest.Builder](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpRequest.Builder.html) instead of a [HttpRequest](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpRequest.html).")
|
||||
|
||||
(add-docstring #'build-request
|
||||
"Builds a [java.net.http.HttpRequest](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpRequest.html) from a map.
|
||||
|
||||
See [[send]] for a description of `req-map`.
|
||||
|
||||
Equivalent to `(.build (request-builder req-map))`.")
|
||||
|
||||
(add-docstring #'send
|
||||
"Sends a HTTP request and blocks until a response is returned or the request
|
||||
takes longer than the specified `timeout`. If the request times out, a [HttpTimeoutException](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpTimeoutException.html)
|
||||
is thrown.
|
||||
|
||||
The `req` parameter can be a either string URL, a request map, or a [java.net.http.HttpRequest](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpRequest.html).
|
||||
|
||||
The request map takes the following keys:
|
||||
|
||||
- `:body` - the request body. Can be a string, a primitive Java byte array or a java.io.InputStream.
|
||||
- `:expect-continue?` - See the [javadoc](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpRequest.Builder.html#expectContinue%28boolean%29)
|
||||
- `:headers` - the HTTP headers, a map where keys are strings and values are strings or a list of strings
|
||||
- `:method` - the HTTP method as a keyword (e.g `:get`, `:put`, `:post`)
|
||||
- `:timeout` - the request timeout in milliseconds or a `java.time.Duration`
|
||||
- `:uri` - the request uri
|
||||
- `:version` - the HTTP protocol version, one of `:http1.1` or `:http2`
|
||||
|
||||
`opts` is a map containing one of the following keywords:
|
||||
|
||||
- `:as` - converts the response body to one of the following formats:
|
||||
- `:string` - a java.lang.String (default)
|
||||
- `:byte-array` - a Java primitive byte array.
|
||||
- `:input-stream` - a java.io.InputStream.
|
||||
|
||||
- `:client` - the [HttpClient](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpClient.html) to use for the request. If not provided the [[default-client]] will be used.
|
||||
|
||||
- `:raw?` - if true, skip the Ring format conversion and return the [HttpResponse](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpResponse.html")
|
||||
|
||||
(add-docstring #'send-async
|
||||
"Sends a request asynchronously and immediately returns a [CompletableFuture](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html). Converts the
|
||||
eventual response to a map as per [[response->map]].
|
||||
|
||||
See [[send]] for a description of `req` and `opts`.
|
||||
|
||||
`callback` is a one argument function that will be applied to the response on completion.
|
||||
|
||||
`ex-handler` is a one argument function that will be called if an exception is thrown anywhere during the request.")
|
||||
|
||||
(add-docstring #'response->map
|
||||
"Converts a [HttpResponse](https://docs.oracle.com/en/java/javase/11/docs/api/java.net.http/java/net/http/HttpResponse.html) into a map.
|
||||
|
||||
The response map contains the following keys:
|
||||
|
||||
- `:body` - the response body
|
||||
- `:headers` - the HTTP headers, a map where keys are strings and values are strings or a list of strings
|
||||
- `:status` - the HTTP status code
|
||||
- `:version` - the HTTP protocol version, one of `:http1.1` or `:http2`")
|
||||
18
src/babashka/impl/http_client/util.clj
Normal file
18
src/babashka/impl/http_client/util.clj
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
(ns babashka.impl.http-client.util
|
||||
{:no-doc true}
|
||||
(:import [java.time Duration]
|
||||
[java.util.function Function]))
|
||||
|
||||
(set! *warn-on-reflection* true)
|
||||
|
||||
(defmacro add-docstring [var docstring]
|
||||
`(alter-meta! ~var #(assoc % :doc ~docstring)))
|
||||
|
||||
(defn convert-timeout [t]
|
||||
(if (integer? t)
|
||||
(Duration/ofMillis t)
|
||||
t))
|
||||
|
||||
(defmacro clj-fn->function ^Function [f]
|
||||
`(reify Function
|
||||
(apply [_# x#] (~f x#))))
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
[babashka.impl.test :as t]
|
||||
[babashka.impl.tools.cli :refer [tools-cli-namespace]]
|
||||
[babashka.nrepl.server :as nrepl-server]
|
||||
[babashka.impl.http-client :as http-client]
|
||||
[babashka.wait :as wait]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.java.io :as io]
|
||||
|
|
@ -390,7 +391,8 @@ If neither -e, -f, or --socket-repl are specified, then the first argument that
|
|||
'clojure.java.browse browse-namespace
|
||||
'clojure.datafy datafy-namespace
|
||||
'clojure.core.protocols protocols-namespace
|
||||
'babashka.process process-namespace}
|
||||
'babashka.process process-namespace
|
||||
'babashka.http-client http-client/http-client-namespace}
|
||||
features/xml? (assoc 'clojure.data.xml @(resolve 'babashka.impl.xml/xml-namespace))
|
||||
features/yaml? (assoc 'clj-yaml.core @(resolve 'babashka.impl.yaml/yaml-namespace)
|
||||
'flatland.ordered.map @(resolve 'babashka.impl.ordered/ordered-map-ns))
|
||||
|
|
|
|||
Loading…
Reference in a new issue