(require '[httpurr.client :as http])
(require '[httpurr.client.xhr :refer [client]])
(http/send! client
{:method :get
:url "https://api.github.com/orgs/funcool"})
A ring-inspired, promise-returning, simple ClojureScript HTTP client.
There are plenty of HTTP client libraries available, each with its own design decisions. Here
are the ones made for httpurr
.
Promises are a natural fit for the request-response nature of HTTP. They contain either
an eventual value (the response) or an error value. CSP channels lack first class errors
and callbacks/errbacks are cumbersome to compose.
httpurr
uses promesa, a wrapper around the
performant Bluebird JS library which implements the Applicative and Monad protocols of
cats. This means that you have core.async
like syntax with cats.core/mlet
and, if you want to maximize concurrency, applicative
composition of promises with cats.core/alet
.
A data based API, requests and responses are just maps. This makes easy to create and transform requests piping various transformations together and the same is true for responses.
No automatic encoding/decoding based on content type, it sits at a lower level. Is your
responsibility to encode and decode data, httpurr
just speaks HTTP.
No automatic generation of query strings. There is no consensus on how to use query params
for things like collection and don’t want to make the choice for you. httpurr
uses goog.net.Uri
internally so your URLs will be correctly encoded.
Constants with every HTTP status code, sets of status codes and predicates for discerning response types.
Pluggable client implementation.
Intended as a infrastructure lib that sits at the bottom of your HTTP client API, we’ll add things judiciously.
There are several alternatives, httpurr
tries to steal the best of each of them while having
a promise-based API which no one offers.
cljs-http: Pretty popular and complete, uses CSP channels for responses. Implicitly
encodes and decodes data. It has some features like helpers for JSONP and auth that I may
eventually add to httpurr
.
cljs-ajax: Works in both Clojure and ClojureScript. Implicitly encodes and decodes data. Callback-based API.
happy: Encoding/decoding are explicit. Callback-based API. Works in both Clojure and ClojureScript. Pluggable clients through global state mutation.
All listed alternatives are licensed with EPL.
httpurr.client
httpurr.client
is the namespace containing the functions to perform HTTP requests.
Requests are maps with the following keys:
:method
is a keyword with the HTTP method to use. All valid HTTP methods are supported.
:url
is the URL of the request
:headers
is a map from strings to strings with the headers of the request.
:body
is the body of the request. Everything that goog.net.Xhr
accepts is fine.
:query-string
is a string with the query part of the URL.
send!
send!
is a function that, given a request map and optionally a map of options, performs the
request and returns a promise that will be resolved if there is a response and rejected on
timeout, exceptions, HTTP errors or aborts.
Let’s try to make a GET request using the XMLHttpRequest-based client that ships with httpurr
:
(require '[httpurr.client :as http])
(require '[httpurr.client.xhr :refer [client]])
(http/send! client
{:method :get
:url "https://api.github.com/orgs/funcool"})
The options map accepts a :timeout
, in miliseconds, after which the promise will be rejected
with the :timeout
keyword as a value.
The httpurr.client
namespaces exposes get
, put
, post
, delete
, head
, options
and trace
methods with identical signatures. They all accept the client as the first argument.
These functions have three arities:
Two arguments the first is the client and the second is assumed to be the URL of the request.
Three arguments: like above and the third is the request map without the :url
key.
Four arguments: like above and the fourth argument is an option map passed to send!
.
For convenience, client implementations provide aliases for the HTTP methods and the send!
function:
(require '[httpurr.client.xhr :as http])
(http/get "https://api.github.com/orgs/funcool")
Responses are maps with the following keys:
:status
is the response status code.
:headers
is a map from strings to strings with the headers of the response.
:body
is the body of the response.
httpurr.status
The httpurr.status
namespace contains constants for HTTP codes and predicates for discerning the types of
responses. They can help you make decissions about how to translate responses to either resolved or rejected
promises.
HTTP has 5 types of responses and httpurr.status
provides predicates for checking wheter a response is of
a certain type.
For 1xx status codes the predicate is informational?
(require '[httpurr.status :as s])
(s/informational? {:status s/continue})
;; => true
For 2xx status codes the predicate is success?
(require '[httpurr.status :as s])
(s/success? {:status s/ok})
;; => true
For 3xx status codes the predicate is redirection?
(require '[httpurr.status :as s])
(s/redirection? {:status s/moved-permanently})
;; => true
For 4xx status codes the predicate is client-error?
(require '[httpurr.status :as s])
(s/client-error? {:status s/not-found})
;; => true
For 5xx status codes the predicate is server-error?
(require '[httpurr.status :as s])
(s/server-error? {:status s/internal-server-error})
;; => true
If you need more granularity you can always check for status codes in your responses and transform the promise accordingly.
Let’s say you’re building an API client and you want to perform GET requests for the URL of an entity that can return:
200 OK status code if everything went well
404 not found if the requested entity wasn’t found
402 unauthorized when we don’t have permission to read the resource
We want to transform the promises by extracting the body of the 200 responses and, if we encounter a 404 or 402, return a keyword denoting the type of error. Let’s give it a go:
(require '[httpurr.status :as s])
(require '[httpurr.client.xhr :as xhr])
(require '[promesa.core :as p])
(defn process-response
[response]
(condp = (:status response)
s/ok (p/resolved (:body response))
s/not-found (p/rejected :not-found)
s/unauthorized (p/rejected :unauthorized)))
(defn id->url
[id]
(str "my.api/entity/" id))
(defn entity [id]
(p/then (xhr/get (id->url id))
process-response))
The Promesa docs explain all the possible combinators
for working with promises. We’ve already used then
for processing responses, let’s look at two
other useful functions: catch
and branch
.
If we want to attach an error handler to the promise we can use the catch
function. Let’s rewrite
our previous entity
function for handling the error case. We’ll just log the error to the console,
you may want to use a better error handling in your code.
(defn entity [id]
(-> (p/then (xhr/get (id->url id))
process-response)
(p/catch (fn [err]
(.error js/console err)))))
For cases when we want to attach both a success and error handler to a promise we can use the branch
function:
(defn entity [id]
(p/branch (xhr/get (id->url id))
process-response
(fn [err]
(.error js/console err))))
The functions in httpurr.client
are based on abstractions defined as protocols in httpurr.protocols
so
you can implement our own clients.
The following protocols are defined in httpurr.protocols
:
Client
is the protocol for a HTTP client
Request
is the protocol for HTTP requests
Abort
is an optional protocol for abortable HTTP requests
Response
is the protocol for HTTP responses
Take a look at the XHR client at httpurr.client.xhr
for reference.
Note that the requests passed to the clients have a escaped URL generated as their :url
value, inferred
from the :url
and :query-string
from the original requests before being passed to the protocol’s
send!
function.
Unlike Clojure and other Clojure contrib libs, does not have many restrictions for contributions. Just open a issue or pull request.
httpurr is open source and can be found on github.
You can clone the public repository with this command:
git clone https://github.com/funcool/httpurr
To run the tests execute the following:
./scripts/build
node out/tests.js
You will need to have nodejs or iojs installed on your system.
httpurr is public domain.
This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to <http://unlicense.org/>
Can you improve this documentation?Edit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close