HTTP interceptors¶
Add authentication headers to every article request with one registration.
An HTTP interceptor can change outgoing requests in :before and incoming
replies in :after, for every frame that names it.
Interceptors: stamp every request once¶
rf/reg-http-interceptor takes an id and an interceptor map. Read the token
in :before so each new request uses the current token:
(ns app.article-interceptors
(:require [re-frame.core :as rf]
[re-frame.http.managed]))
(rf/reg-http-interceptor
:app/bearer-auth
{:doc "Stamp Bearer <token> on every outgoing request."
:before (fn [ctx]
(let [token (-> (rf/app-db-value (:frame ctx)) :auth :token)]
(cond-> ctx
token (assoc-in [:request :headers "Authorization"]
(str "Bearer " token)))))})
;; Now no handler threads auth — the header is added on the way out:
(rf/reg-event :article/load
(fn [_ [_ slug]]
{:fx [[:rf.http/managed
{:request {:url (str "/api/articles/" slug)}
:decode :json
:on-success [:article/loaded]
:on-failure [:article/load-error]}]]}))
The registration belongs to no frame. A frame turns it on by naming its id in
:http-interceptors; here that is :app/articles, the same frame used by the
tutorial:
It also applies to HTTP requests issued by resources and mutations in that frame.
Authorization is redacted from HTTP traces automatically. Request and response
bodies need their own declarations; HTTP trace redaction shows
where to put them.
Transforming requests and replies¶
:before receives a context containing :request, :args, :frame,
:event and :sensitive?. Return that context with the modified request.
Read current app state through rf/app-db-value, as the auth example does.
To mark a request sensitive from an interceptor, set [:request :sensitive?]
to true in the context.
:after receives the final request context and a reply map. Return the reply,
with any transformations. On success, :meta includes the HTTP status and
lower-cased header names. For example, parse the rate-limit header once so
article handlers can read a structured value:
(rf/reg-http-interceptor
:app/rate-limit
{:doc "Parse X-RateLimit-* once; downstream handlers read :rate-limit."
:after (fn [_ctx response]
(let [remaining (some-> (get-in response [:meta :headers "x-ratelimit-remaining"])
parse-long)]
(cond-> response
remaining (assoc :rate-limit {:remaining remaining}))))})
;; and the frame names both:
;; {:id :app/articles :http-interceptors [:app/bearer-auth :app/rate-limit]}
Registration and lifetime¶
- An interceptor applies only to requests from a frame
that names it in
:http-interceptors. Register it at the top level, with no:frame; frames that need different behaviour name different ids. :beforeruns in the order of the frame's:http-interceptorsvector;:afterruns in reverse, as with event interceptors. Load order sets nothing.- The chain is captured at request start.
:beforeruns once, and retries reuse that request, including its token.:afterruns once on the final reply, including cancellation. Refreshing a token and issuing another request belongs in the application's authentication flow. - Re-registering an id replaces it for every frame that names it.
(rf/clear :http-interceptor id)removes it for every frame; to stop one frame running it, leave the id out of that frame's:http-interceptors.
Add authentication uses this pattern for a complete login flow. The interceptor reference records the context fields and registration forms.
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
:rf.error/unregistered-interceptor |
The frame's :http-interceptors names an id nothing registered |
Require the namespace that registers it, or fix the id |
:rf.error/http-bad-interceptor |
Neither phase is a function, the id is not a keyword, or the map carries :frame |
Supply at least one function phase and a keyword id; name the id in the frame's :http-interceptors instead of passing :frame |
:rf.error/http-interceptor-bad-return |
A phase returned a non-map | Return the request context from :before and the reply map from :after |
:rf.error/http-interceptor-failed |
A phase threw | Handle expected failures in the phase; a failed :before sends no request, and a failed :after delivers no reply |
When to use a request builder¶
An interceptor transforms all requests from a frame. If only a group of call sites shares a base URL or body encoding, use an ordinary request-builder function for that group.