Interceptors allow you to customize the behaviour of cljs-ajax
. Examples of why you might want to do this include:
DELETE
on Google App Enginenull
with JSON responsesThere will be example implementations of each of these nearer the bottom of the file.
The request and response formats are implementing using interceptors, so you've got access to everything possible. An interceptor is any object that implements the ajax.core/Interceptor
protocol. The easiest way to achieve this is to call to-interceptor
passing a map with the following keys
:request
- The request interceptor, optional:response
- The response interceptor, also optional:name
- The name of the interceptor, optional but highly recommended for debugging purposes.A request works like this:
GET
or POST
&c, the request map is converted to a form accepted by ajax-request
.ajax-request
map has its method normalized, so :get
becomes "GET"
&c@default-interceptors
. default-interceptors
is an atom vector of interceptors.:response-format
at the start and :request-format
at the end.:body
entry (unless it's a GET
).AjaxResponse
.:response-format
interceptor runs last.:handler
is called with the result of that.Note that request and response handlers are run using reduce
. This means that you can use reduced
to skip the rest of the interceptors
. Bear in mind that in the case of a response interceptor, this means you need to convert to [ok result]
format before calling reduced
.
You can use an interceptor to change the request format, but not the response format. This is partly caused by technical internal considerations, but it's worth considering that most of the reasons you'd want it are either to slightly customize response behaviour, which can be handled with an interceptor, or to switch formats, which can be handled with format detection.
Equally, you can't use an interceptor to change the list of interceptors. This would be technically feasible if complex, but I can't think of any cases where it wouldn't be a code smell. In general terms, interceptors should be independent of one another. If they're not, it's probably best to just create a control interceptor that calls the others rather than trying to 'trick' the architecture to achieve the same ends.
Quite a few people want to put CSRF or session tokens into every request.
(def token (atom 2334))
(def token-interceptor
(to-interceptor {:name "Token Interceptor"
:request #(assoc-in % [:params :token] @token)}))
(swap! default-interceptors (partial cons token-interceptor))
Note that in general, we will want to prepend elements to the default-interceptor
atom as we do above.
We may wish to see a rule appended, however. The Google App Engine doesn't permit a body in DELETE
requests; its implementation of the HTTP spec is wrong, but there's no point arguing. The following appends this rule:
(defn delete-is-empty [{:keys [method] :as request}]
(if (= method "DELETE")
(reduced (assoc request :body nil))
request))
(def app-engine-delete-interceptor
(to-interceptor {:name "Google App Engine Delete Rule"
:request delete-is-empty}))
;;; Since this rule uses `reduced`, it is important that it is
;;; positioned at the end of the list, hence we use `concat` here
(swap! standard-interceptors concat [app-engine-delete-interceptor])
Finally, many systems return an empty JSON response when returning nil
. Strictly speaking, they should return "null"
.
(defn empty-means-nil [response]
(if (ajax.protocols/-body response)
response
(reduced [(-> response ajax.protocols/-status ajax.core/success?) nil])))
(def treat-nil-as-empty
(to-interceptor {:name "JSON special case nil"
:response empty-means-nil}))
(swap! standard-interceptors concat [treat-nil-as-empty]))
Can you improve this documentation? These fine people already did:
Ian Bishop, Dean Hayes, Matthew Wampler-Doty & Matthew MolloyEdit on GitHub
cljdoc is a website building & hosting documentation for Clojure/Script libraries
× close