07 — HTTP¶
Managed HTTP is the answer to "I want my app to talk to a server, but I don't want to write a custom fx-handler every time, and I want retries / cancellation / timeouts / decode / failure-classification to be the framework's problem, not mine." :rf.http/managed is one fx-id that takes one args map and gives you back one reply event with one closed taxonomy of failure kinds.
You don't get this for free — Spec 014 is an optional capability. Implementations ship it (the CLJS reference does, on Fetch in the browser and java.net.http.HttpClient on the JVM); ports that omit it must not reuse the :rf.http/* namespace for anything else. If you're using the CLJS reference, you have it; if you're vendoring a port that doesn't, you're back to writing your own.
This chapter covers the canonical fx, the verb helpers, the test stubs, the request-interceptor surface, and the closed failure taxonomy. The normative source — args map, decode pipeline, retry semantics, reply addressing — lives at 014-HTTPRequests.md.
The canonical fx¶
[:rf.http/managed args-map]¶
- Kind: fx
- Args: per 014 §The args map and
:rf.fx/managed-args - Description: The one fx-id. Args carry the request envelope, decode policy, accept fn, retry policy, timeout, success / failure target events, request-id (for abort), and optional abort-signal.
- In the wild: managed_http_counter · realworld
[:rf.http/managed-abort request-id]¶
- Kind: fx
- Args: request-id
- Description: Abort the in-flight request with the given
:request-id. The aborted request's reply fires with{:rf/reply {:kind :failure :failure {:kind :rf.http/aborted ...}}}.
A minimal request¶
(rf/reg-event-fx :cart/load
(fn [_ _]
{:fx [[:rf.http/managed
{:request {:method :get :url "/api/cart"}
:on-success [:cart/loaded]
:on-failure [:cart/load-failed]}]]}))
(rf/reg-event-db :cart/loaded
(fn [db [_ {:keys [rf/reply]}]]
(assoc-in db [:cart :items] (:value reply))))
That's enough to issue a request, decode the JSON reply, and dispatch the result back. Retries, timeouts, schema validation, abort, decode customisation, accept-fn refinement — all of those are optional keys in the args map; you reach for them when the problem asks for them.
Verb helpers¶
Call-site helpers for the common shapes. They're pure synthesis fns that produce the canonical [:rf.http/managed args-map] fx vector — no magic, no hidden state. The point is ergonomics: writing (rf.http/get "/api/cart") reads better at the call site than spelling out the full args map for a no-frills GET.
re-frame.http/get¶
- Signature:
- Description: "Synthesise a GET fx vector." Pure; no side effect — drop the result into
:fx.
re-frame.http/post¶
- Signature:
- Description: POST. Pass
:bodyinargs.
re-frame.http/put¶
- Signature:
- Description: PUT.
re-frame.http/delete¶
- Signature:
- Description: DELETE.
re-frame.http/patch¶
- Signature:
- Description: PATCH.
re-frame.http/head¶
- Signature:
- Description: HEAD.
re-frame.http/options¶
- Signature:
- Description: OPTIONS.
The verb helpers live in re-frame.http — users (:require [re-frame.http :as rf.http]) alongside re-frame.core. The namespace ships in day8/re-frame2-http, the same artefact as the fx itself, so loading the helpers and the fx is a single dep decision.
{:fx [(rf.http/get "/api/cart"
{:on-success [:cart/loaded]
:on-failure [:cart/load-failed]
:retry {:on #{:rf.http/transport :rf.http/timeout}
:max-attempts 3
:backoff-ms 100}})]}
Reply addressing¶
Every reply lands under :rf/reply in the dispatched event's payload map. Two shapes:
;; Success
{:rf/reply {:kind :success :value decoded-body}}
;; Failure
{:rf/reply {:kind :failure
:failure {:kind :rf.http/<category>
:tags {...}}}}
Default reply addressing dispatches [<originating-event-id> (assoc original-msg :rf/reply ...)] back to the same handler — your :cart/load handler sees the reply at :rf/reply. Explicit :on-success / :on-failure targets append the reply payload as the last event-vector arg — your :cart/loaded handler sees [:cart/loaded {:rf/reply ...}]. Both shapes detailed in 014 §Reply addressing.
Failure categories (closed set)¶
Eight failure :kind values, all reserved under :rf.http/*. The set is closed — ports that ship Spec 014 deliver exactly these eight categories, and your handler's failure switch can be exhaustive.
:kind |
Meaning |
|---|---|
:rf.http/transport |
Network / DNS / connection error pre-HTTP. |
:rf.http/cors |
CORS preflight rejected (CLJS-only). |
:rf.http/timeout |
Per-attempt timeout fired. |
:rf.http/http-4xx |
Non-2xx 4xx response. |
:rf.http/http-5xx |
Non-2xx 5xx response. |
:rf.http/decode-failure |
2xx response but decode rejected the body. |
:rf.http/accept-failure |
:accept returned {:failure user-map}. |
:rf.http/aborted |
Request aborted via :request-id or :abort-signal. |
See 014 §Failure categories for tags-by-kind.
Request-interceptor middleware¶
Sometimes you want to inject behaviour into every request — adding an auth header, stamping a request ID, logging. Re-frame2's answer is a small middleware surface that mirrors the rest of the reg-* family.
reg-http-interceptor¶
- Kind: function
- Signature:
- Description: Register a request-side interceptor on a frame's
:rf.http/managedmiddleware chain.beforeis(fn [ctx] ctx')where ctx is{:request :args :frame :event}.optscarries:frame(default:rf/default) plus the standard:rf/registration-metadata. - In the wild: realworld
clear-http-interceptor¶
- Kind: function
- Signature:
- Description: Unregister an interceptor by id. Single-arity targets
:rf/default.
(rf/reg-http-interceptor :auth/inject
(fn [{:keys [request] :as ctx}]
(assoc-in ctx [:request :headers "Authorization"]
(str "Bearer " (token-from-app-db)))))
The interceptor runs before the request is dispatched to the platform's HTTP client. If a :before throws, the request is not dispatched; :rf.error/http-interceptor-failed fires with :frame, :interceptor-id, :url, and :cause. See 014 §Middleware.
Testing: stubbed responses¶
Tests want to drive the cascade without hitting the network. The test-support surface provides canned-reply fx and a stubbing macro that reroutes requests at the routes you name.
[:rf.http/managed-canned-success {:value v}]¶
- Kind: fx
- Description: Synthesise the canonical success reply directly into
:fx. Useful for "stub THIS request inline" patterns. Registered at load ofre-frame.http-test-support.
[:rf.http/managed-canned-failure {:kind <:rf.http/*> :tags {...}}]¶
- Kind: fx
- Description: Synthesise the canonical failure reply directly into
:fx.
with-managed-request-stubs¶
- Kind: macro
- Signature:
- Description: Lexical-scope stubbing.
route-mapis{[<method> <url>] {:reply <value-or-failure>}}. Inside the body, requests matching a stubbed route bypass the real client.
with-managed-request-stubs*¶
- Kind: function
- Signature:
- Description: Plain-fn surface beneath the macro. Use for computed route-maps or non-literal bodies.
install-managed-request-stubs!¶
- Kind: function
- Signature:
- Description: Lower-level than
with-managed-request-stubs: install stubs that persist untiluninstall-managed-request-stubs!. Use when stubs span multipledeftests.
uninstall-managed-request-stubs!¶
- Kind: function
- Signature:
- Description: Drop installed stubs; restore real-request routing. Idempotent.
All the test-support surfaces live in re-frame.http-test-support (the single home per audit of audits #15). One namespace; same artefact (day8/re-frame2-http) as the production code.
(deftest cart-loads
(with-managed-request-stubs
{[:get "/api/cart"] {:reply [{:id 1 :name "widget"}]}}
(rf/dispatch-sync [:cart/load])
(is (= 1 (count (subscribe-once [:cart/items]))))))
Schema-reflection metadata¶
Handlers may declare :rf.http/decode-schemas [<schema> ...] in their reg-event-fx metadata-map; pair tools and generators read it via (rf/handler-meta :event id). Optional, never enforced — pure metadata for tooling. See 014 §Schema reflection.
Trace events emitted by :rf.http/managed¶
:operation |
:op-type |
When |
|---|---|---|
:rf.http/retry-attempt |
:info |
Per intermediate attempt that matched :retry :on. Carries :attempt, :max-attempts, :failure, :next-backoff-ms. |
:rf.warning/decode-defaulted |
:warning |
The request relied on :decode :auto (the default). Informational; not an error. |
:rf.http.interceptor/registered |
:info |
A reg-http-interceptor succeeded. Carries :frame, :id. |
:rf.http.interceptor/cleared |
:info |
A clear-http-interceptor removed an existing slot. |
:rf.error/http-interceptor-failed |
:error |
A request-interceptor :before threw. Carries :frame, :interceptor-id, :url, :cause. The request is NOT dispatched. |
See also¶
- 03 — Effects and interceptors —
:rf.http/managedrowed in the standard fx table. - 08 — Schemas —
:rf.http/decode-schemasand the:schemametadata key. - 10 — Testing — patterns for combining HTTP stubs with
dispatch-sequence. - Spec 014 — HTTP Requests — the normative source.