Usage | Interceptor | Representor
A Clojure(Script) HTTP async client library with swappable implementation.
happy
ships with a Clojure client based on OkHTTP and a ClojureScript client based on XMLHttpRequest.
happy
main function is happy.core/send!
. It allows to send an HTTP call represented as a map and receive via a handler function a response map.
(ns my.app
(:require [happy.core :as h]
[happy.client.xmlhttprequest :as hc]))
(h/set-default-client! (hc/create))
(let [c (h/send! {:method "GET" :url "http://google.com"} {:handler #(println "received " %)})]
; an HTTP call can be aborted
(h/-abort c))
A request map has the following shape:
{:method "GET" ; an uppercase String identifying the HTTP method used
:headers {} ; a String/String map of key/value headers
:body "" ; a body payload whose type must match client implementation capacities}
When called, the :handler
function will receive as single argument a response map with the following shape:
{:type :response ; a keyword identifying the response
; can be `:response`, `:progress` or `:failure`
; if :type = :response
:status 200 ; an integer of the HTTP status code
:headers {} ; a String/(String or seq) map of key/value headers
:body "" ; a payload whose type depends on client implementation
; if :type = :progress
:direction :sending ; a keyword identifying if this is a `:receiving` or `:sending` progress
:loaded 10 ; an integer of the count of currently loaded bytes, optional
:total 150 ; an integer of total bytes, optional
; if :type = :failure
:termination :abort ; a keyword whose value can be `:abort`, `:timeout` or `:network`
:reason "" ; a String detailing the failure, optional
}
A handler is called only once per request with a :type
of :response
or :failure
.
If :report-progress?
option is provided and the client implementation supports it :handler
can be called a number of times with type :progress
.
For simplicity both request and response are modeled after the ring SPEC.
Helper functions for common verbs are provided to simplify common calls.
(ns my.app
(:require [happy.core :as h :refer [GET PUT]]
[happy.client.xmlhttprequest :as hc]))
(h/set-default-client! (hc/create))
(GET "http://google.com" {} {:handler #(println "received " %)})
(PUT "http://my-app.com" {:data "some payload"})
The second parameter to happy.core/send!
is a map of options that will affect an HTTP call.
Each client can accept any option. Those must be advertised in the happy.core/-supports
map as extra-options
.
Common options are available (optional unless specified):
:client
to define the client implementation, mandatory:handler
the callback function called when the HTTP call is executed:timeout
the maximum time allowed for the HTTP call to finish, in milliseconds:request-body-as
the type of:body
send by the client. Client specific, default to :string:response-body-as
the type of:body
received by the client. Client specific, default to :string:report-progress?
if:progress
event are provided to the callback:handler
:request-interceptors
the sequence of interceptors applied to a request:response-interceptors
the sequence of interceptors applied to a response
Options can also be set globally (stored in happy.core/default-options
) using happy.core/swap-options!
, happy.core/merge-options!
and happy.core/set-default-client!
.
(ns my.app
(:require [happy.core :as h :refer [GET]]
[happy.client.xmlhttprequest :as hc]))
(h/set-default-client! (hc/create))
(h/merge-options! {:report-progress? true
:handler #(println %)})
(GET "http://google.com")
Interceptors allow users to modify request and response part of an HTTP call. Interceptors are simple function returning their argument eventually modified and are applied in order.
happy
bundles a couple interceptors.
A request interceptor is specified via :request-interceptors
and receive as argument a sequence of the request map and the options map.
A response interceptor is specified via :response-interceptors
and receive as argument the response map.
(ns my.app
(:require [happy.core :as h :refer [GET]]
[happy.client.xmlhttprequest :as hc]))
(defn dump-request
[[req om :as m]]
(println "Request: " req)
m)
(h/set-default-client! (hc/create))
(h/merge-options! {:request-interceptors [dump-request]})
(GET "http://google.com")
options
can be modified in a request interceptor. This allows for instance to generate per call response interceptor, like in this timing interceptor:
(ns my.app
(:require [happy.core :as h :refer [GET]]
[happy.client.xmlhttprequest :as hc]))
(defn now [] (System/currentTimeMillis))
(defn timing-interceptor
[[_ om :as v]]
(let [i (now)]
(assoc v 1 (update om :response-interceptors #(cons %2 %1) (fn [m _] (assoc m :timing (- (now) i)))))))
(h/set-default-client! (hc/create))
(h/merge-options! {:request-interceptors [timing-interceptor]})
(GET "http://google.com" {} {:handler #(println "Executed in " (:timing %) "ms")})
Representors encapsulate the logic of converting HTTP body between the user and the client implementation. Custom representors can be provided by implementing the happy.core/Representor
protocol.
To have a representor used automatically as part of the HTTP call it must be defined using respectively the request-interceptors
and response-interceptors
options.
Representors as interceptors are automatically applied based on request / response content-type
and will replace :body
with the result of their invocation. By specifying a mime-type via override-request-mime-type
or override-response-mime-type
a user can control with representor will be used.
Default representor for edn
, json
, transit
and other common mime types are available and can be setup using the merge-representors!
function defined in their respective namespace.
Copyright (C) 2015-2016 Julien Eluard
Distributed under the Eclipse Public License, the same as Clojure.