Clj-http-lite is a Clojure, Babashka and GraalVM compatible liteweight subset of clj-http.
-
Instead of Apache HttpClient, clj-http-lite uses HttpURLConnection
-
No automatic JSON decoding for response bodies
-
No automatic request body encoding beyond charset and url encoding of form params
-
No cookie support
-
No multipart form uploads
-
No persistent connection support
-
Fewer options
-
namespace rename
clj-http.*
→clj-http.lite.*
Like its namesake, clj-http-lite is light and simple, but ping us if there is some clj-http feature you’d like to see in clj-http-lite. We can discuss.
-
JDK 8, 11, 17, 21
-
Clojure 1.8 runtime and above
-
Babashka current release
-
Windows, Linux, macOS
-
Sep 2011 - dakrone/clj-http created (and is still actively maintained)
-
Feb 2012 - hiredman/clj-http-lite (now archived) forked from
dakrone/clj-http
to use Java’s HttpURLConnection instead of Apache HttpClient. -
Jul 2018 -
martinklepsch/clj-http-lite
forked fromhiredman/clj-http-lite
for new development and maintenance -
Nov 2021 - Martin transfered his fork to
clj-commons/clj-http-lite
so it could get the ongoing love it needs from the Clojure community
Maybe clj-http-lite is not your cup of tea? Some alternatives to explore:
Clojure based:
-
clj-http (jdk8+) - heavier than clj-http-lite, but has many more features
Babashka compatible:
-
babashka/http-client (jdk11+) - HTTP client for Clojure and Babashka built on java.net.http
-
java-http-clj (jdk11+) - Clojure wrapper for java.net.http with async, HTTP/2 and WebSockets
-
http-kit (jdk8+?) - minimalist, event-driven, high-performance Clojure HTTP server/client library with WebSocket and asynchronous support
-
hato (jdk11+) - An HTTP client for Clojure, wrapping JDK 11’s HttpClient
-
babashka.curl (jdk8+) - A tiny curl wrapper via idiomatic Clojure, inspired by clj-http, Ring and friends (now mostly replaced by babashka/http-client)
Clojure cli users, add the following under :deps
in your deps.edn
file.
Babashka users, add the following under :deps
in your bb.edn
file:
org.clj-commons/clj-http-lite {:mvn/version "1.0.13"}
Lein users, add the following into the :dependencies
vector in your project.clj
file:
[org.clj-commons/clj-http-lite "1.0.13"]
HTTP client functionality is provided by the clj-http.lite.client
namespace:
(require '[clj-http.lite.client :as client])
The client supports simple get
, head
, put
, post
, and delete
requests.
They all return Ring-style response maps:
(client/get "https://google.com")
=> {:status 200
:headers {"date" "Wed, 17 Aug 2022 21:37:58 GMT"
"cache-control" "private, max-age=0"
"content-type" "text/html; charset=ISO-8859-1"
...}
:body "<!doctype html>..."}
Tip
|
We encourage you to try out these examples in your REPL, httpbin.org is a free HTTP test playground and used in many of our examples.
|
(client/get "https://httpbin.org/user-agent")
;; Tell the server you'd like a json response
(client/get "https://httpbin.org/user-agent" {:accept :json})
;; Or maybe you'd like html back
(client/get "https://httpbin.org/html" {:accept "text/html"})
;; Various options
(client/post "https://httpbin.org/anything"
{:basic-auth ["joe" "cool"]
:body "{\"json\": \"input\"}"
:headers {"X-Api-Version" "2"}
:content-type :json
:socket-timeout 1000
:conn-timeout 1000
:accept :json})
;; Need to contact a server with an untrusted SSL cert?
(client/get "https://expired.badssl.com" {:insecure? true})
;; By default we automatically follow 30* redirects...
(client/get "https://httpbin.org/redirect-to?url=https%3A%2F%2Fclojure.org")
;; ... but you don't have to
(client/get "https://httpbin.org/redirect-to?url=https%3A%2F%2Fclojure.org"
{:follow-redirects false})
;; Send form params as a urlencoded body
(client/post "https://httpbin.org/post" {:form-params {:foo "bar"}})
;; Basic authentication
(client/get "https://joe:[email protected]/basic-auth/joe/cool")
(client/get "https://httpbin.org/basic-auth/joe/cool" {:basic-auth ["joe" "cool"]})
(client/get "https://httpbin.org/basic-auth/joe/cool" {:basic-auth "joe:cool"})
;; Query parameters can be specified as a map
(client/get "https://httpbin.org/get" {:query-params {"q" "foo, bar"}})
The client transparently accepts and decompresses the gzip
and deflate
content encodings.
(client/get "https://httpbin.org/gzip")
(client/get "https://httpbin.org/deflate")
Nested parameter {:a {:b 1}}
in :form-params
or :query-params
is automatically flattened to a[b]=1
.
(-> (client/get "https://httpbin.org/get"
{:query-params {:one {:two 2 :three 3}}})
:body
println)
{
"args": {
"one[three]": "3",
"one[two]": "2"
},
...
}
(-> (client/post "https://httpbin.org/post"
{:form-params {:one {:two 2
:three {:four {:five 5}}}
:six 6}})
:body
println)
{
...
"form": {
"one[three][four][five]": "5",
"one[two]": "2",
"six": "6"
},
...
}
;; body as byte-array
(client/post "https://httbin.org/post" {:body (.getBytes "testing123")})
;; body from a string
(client/post "https://httpbin.org/post" {:body "testing456"})
;; string :body-encoding is optional and defaults to "UTF-8"
(client/post "https://httpbin.org/post"
{:body "mystring" :body-encoding "UTF-8"})
;; body from a file
(require '[clojure.java.io :as io])
(spit "clj-http-lite-test.txt" "from a file")
(client/post "https://httpbin.org/post"
{:body (io/file "clj-http-lite-test.txt")
:body-encoding "UTF-8"})
;; from a stream
(with-open [is (io/input-stream "clj-http-lite-test.txt")]
(client/post "https://httpbin.org/post"
{:body (io/input-stream "clj-http-lite-test.txt")}) )
;; The default response body is a string body
(client/get "https://clojure.org")
;; Coerce to a byte-array
(client/get "http://clojure.org" {:as :byte-array})
;; Coerce to a string with using a specific charset, default is UTF-8
(client/get "http://clojure.org" {:as "US-ASCII"})
;; Try to automatically coerce the body based on the content-type
;; response header charset
(client/get "https://google.com" {:as :auto})
;; Return the body as a stream
;; Note that the connection to the server will NOT be closed until the
;; stream has been read
(let [res (client/get "https://clojure.org" {:as :stream})]
(with-open [body-stream (:body res)]
(slurp body-stream)))
A more general request
function is also available, which is useful as a primitive for building higher-level interfaces:
(defn api-action [method path & [opts]]
(client/request
(merge {:method method :url (str "https://some.api/" path)} opts)))
When a server returns an exceptional HTTP status code, by default, clj-http-lite throws an ex-info
exception.
The response is included as ex-data
.
(client/get "https://httpbin.org/404")
;; => ExceptionInfo clj-http: status 404 clojure.core/ex-info (core.clj:4617)
(-> *e ex-data :status)
;; => 404
(-> *e ex-data keys)
;; => (:headers :status :body)
You can suppress HTTP status exceptions and handle them yourself via the :throw-exceptions
option:
(client/get "https://httpbin.org/404" {:throw-exceptions false})
You can choose to ignore an unknown host via :ingore-unknown-host?
option.
When enabled, requests return nil
if the host is not found.
(client/get "http://aoeuntahuf89o.com" {:ignore-unknown-host? true})
;; => nil
Mocking responses from the clj-http-lite client in tests is easily accomplished with e.g. with-redefs
:
(defn my-http-function []
(let [response (client/get "https://example.org")]
(when (= 200 (:status response))
(:body response))))
(deftest my-http-function-test
(with-redefs [client/get (fn [_] {:status 200 :headers {"content-type" "text/plain"} :body "OK"})]
(is (= (my-http-function) "OK"))))
More advanced mocking may be performed by matching attributes in the request
, like the mock-response
function below.
(ns http-test
(:require [clojure.data.json :as json]
[clojure.test :refer [deftest is testing]]
[clj-http.lite.client :as client]))
(defn send-report [data]
(:body (client/post "https://example.com/reports" {:body data})))
(defn get-users []
(json/read-str (:body (client/get "https://example.com/users"))))
(defn get-admin []
(let [response (client/get "https://example.com/admin")]
(if (= 200 (:status response))
(:body response)
"403 Forbidden")))
(defn mock-response [{:keys [url method body] :as request}]
(condp = [url method]
["https://example.com/reports" :post]
{:status 201 :headers {"content-type" "text/plain"} :body (str "created: " body)}
["https://example.com/users" :get]
{:status 200 :headers {"content-type" "application/json"} :body (json/write-str ["joe" "jane" "bob"])}
["https://example.com/admin" :get]
{:status 403 :headers {"content-type" "text/plain"} :body "forbidden"}
(throw (ex-info "unexpected request" request))))
(deftest send-report-test
(with-redefs [client/request mock-response]
(testing "sending report"
(is (= (send-report {:balance 100}) "created: {:balance 100}")))
(testing "list users"
(is (= (get-users) ["joe" "jane" "bob"])))
(testing "access admin page"
(is (= (get-admin) "403 Forbidden")))))
You’ll need to enable url protocols when building your native image.
See GraalVM docs.
The design of clj-http
(and therefore clj-http-lite
) is inspired by the Ring protocol for Clojure HTTP server applications.
The client in clj-http.lite.core
makes HTTP requests according to a given Ring request map and returns Ring response maps corresponding to the resulting HTTP response.
The function clj-http.lite.client/request
uses Ring-style middleware to layer functionality over the core HTTP request/response implementation.
Methods like clj-http.lite.client/get
are sugar over this clj-http.lite.client/request
function.