janus/http-client
An interceptor-chain-based, async HTTP client library built on top of core.async and Pedestal interceptors. Requests flow through a composable interceptor pipeline before being dispatched by a pluggable backing implementation. The library itself is Ring-compatible and transport-agnostic — it requires a separate implementation package to actually send HTTP requests (e.g. janus/http-client-jetty).
Setup
Add the dependency:
;; deps.edn
{io.forward-publishing.janus/http-client {:mvn/version "..."}}
You also need a backing implementation on the classpath. See janus/http-client-jetty for a Jetty 9–12 implementation.
Using create — low-level client
http/create builds an InterceptorChainClient directly.
It accepts keyword arguments and returns a client that satisfies both Stoppable and java.io.Closeable, so it works with with-open.
| Option | Description |
|---|---|
|
Required. A backing implementation satisfying the |
|
Interceptor pipeline. Defaults to |
(require '[clojure.core.async :refer [<!!]]
'[io.forward-publishing.janus.http-client :as http]
'[io.forward-publishing.janus.http-client.interceptors :as interceptors]
'[janus.http.client.jetty.api :as jetty])
(with-open [client (http/create ::http/interceptors
(conj interceptors/default-interceptors
(interceptors/rate-limiter 1))
::http/impl (jetty/create {}))]
(<!! (http/submit client "https://example.com")))
Submitting requests
http/submit has two arities:
;; Returns a new channel that will receive the response
(http/submit client request)
;; Puts the response onto the provided channel
(http/submit client request chan)
request can be a URL string (processed by construct-request and explode-uri) or a Ring request map.
The response is a Ring response map, or a cognitect anomaly map on failure.
Using create-api — named-route API client
http/create-api builds a higher-level client around a set of Reitit routes.
Routes must resolve to absolute URLs.
The returned value is both a Stoppable and a Clojure function.
| Arity | Description |
|---|---|
|
Calls the route named |
|
Calls the route named |
|
Same as above, but puts the response onto |
If ::http/impl is omitted, the client creates and manages its own backing implementation and will stop it when http/stop is called.
If you provide ::http/impl externally, you are responsible for stopping it independently.
(require '[clojure.core.async :refer [<!!]]
'[io.forward-publishing.janus.http-client :as http]
'[io.forward-publishing.janus.http-client.interceptors :refer [extract-body]]
'[janus.http.client.jetty.api :as jetty])
(def routes
["http://api.example.com"
{:interceptors [extract-body]}
["/users" :users]
["/users/{id}" :user]])
(def api (http/create-api routes ::http/impl (jetty/create {})))
;; Call by route name
(<!! (api :users))
(<!! (api :user {:id "42"}))
;; Stop when done (also stops the externally-provided impl if you passed one)
(http/stop api)
Default interceptors
interceptors/default-interceptors is a vector of interceptors applied to every request:
| Interceptor | Description |
|---|---|
|
Converts a bare URI string or |
|
Parses an absolute URI into Ring keys: |
|
Encodes the |
|
Encodes |
|
Sets the |
Additional interceptors available in io.forward-publishing.janus.http-client.interceptors:
| Interceptor | Description |
|---|---|
|
Limits throughput to at most |
|
Injects |
|
On success (2xx), replaces the response with just the decoded body. On failure, throws a cognitect anomaly. |
|
Limits concurrent in-flight requests to at most |