Controlling the response — :rf.server/*¶
You know the request lifecycle. This page is one job: shape status, headers, cookies, and redirects as data from handlers.
Server-only effects write a per-request accumulator; the Ring adapter materialises it. Response logic stays pure and testable like any effect map.
Prerequisites. The model or the tutorial through Step 7.
The effects¶
| fx-id | args | does |
|---|---|---|
:rf.server/set-status |
<int> |
set the HTTP status (last write wins; a conflict emits a :rf.warning/multiple-status-set trace) |
:rf.server/set-header |
{:name :value} |
set a header, replacing any prior value (case-insensitive name) |
:rf.server/append-header |
{:name :value} |
append another instance — for multi-value headers (Set-Cookie, Vary) |
:rf.server/set-cookie |
a structured cookie map | the adapter does the wire encoding |
:rf.server/delete-cookie |
{:name :path} |
expire a cookie (sugar over set-cookie with :max-age 0) |
:rf.server/redirect |
{:status :location} |
short-circuit the render with a redirect (:status defaults to 302; use 303 for POST success) |
:rf.server/safe-redirect |
{:location :relative-only? :allow} |
a validated redirect for user-supplied locations — the open-redirect guard |
Cookies are structured maps¶
Never hand-build header strings. Hand the framework the attributes; the adapter does RFC 6265 wire encoding (the place raw-string cookie APIs grow quoting bugs):
{:fx [[:rf.server/set-cookie
{:name "session"
:value session-token
:max-age 3600
:secure true
:http-only true
:same-site :lax ;; one of :strict :lax :none
:path "/"}]]}
Redirects truncate the render¶
If a redirect fires anywhere in the drain — setup, route handler, pipeline — the
runtime sets :redirect, skips HTML render (no body), and skips the hydration
payload (no client to hydrate). The host emits status + Location with no body.
Last-write-wins on multiple redirects, with a :rf.warning/multiple-redirects trace.
:rf.server/redirect trusts its caller — fine for a location you control.
Use :rf.server/safe-redirect for user-supplied locations
For a :location built from user input (?next=...), use :rf.server/safe-redirect.
It runs the gauntlet in order: the URL must parse
(:rf.error/safe-redirect-invalid-url), javascript: / data: / vbscript:
schemes are rejected (:rf.error/safe-redirect-scheme-rejected), and
:relative-only? true or an :allow ["app.example.com"] allowlist gates the host
(:rf.error/safe-redirect-host-disallowed). An attacker-controlled ?next=… cannot
bounce a freshly-authed user off-origin.
Header injection fails loud¶
A \r or \n smuggled into a header value is a response-splitting attack. The
framework does not quietly strip it:
:rf.server/set-header/:append-headerthrow:rf.error/header-invalid-value:rf.server/redirectthrows:rf.error/redirect-invalid-locationon CRLF/NUL in:location:rf.server/set-cookieCRLF-checks every attribute (:name,:value,:domain,:path, …) before serialisation
Fail-fast over strip-and-warn — silent normalisation masks the bug.
Where this fits¶
- Form POST success →
[:rf.server/redirect {:status 303 :location …}]— see The model → two patterns - Server throws / 4xx / 5xx → When the server throws
- Full arg schemas → re-frame.ssr / re-frame.ssr.ring