safsaf/index.html
Automatic website updater bcaa35dd2c Automatic website update
2026-04-14 10:09:06 +01:00

2428 lines
113 KiB
HTML

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<!-- Created by GNU Texinfo 6.8, https://www.gnu.org/software/texinfo/ -->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Safsaf</title>
<meta name="description" content="Safsaf">
<meta name="keywords" content="Safsaf">
<meta name="resource-type" content="document">
<meta name="distribution" content="global">
<meta name="Generator" content="makeinfo">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link href="#Top" rel="start" title="Top">
<link href="#Concept-Index" rel="index" title="Concept Index">
<link href="#SEC_Contents" rel="contents" title="Table of Contents">
<link href="#Guidance" rel="next" title="Guidance">
<style type="text/css">
<!--
a.copiable-anchor {visibility: hidden; text-decoration: none; line-height: 0em}
a.summary-letter {text-decoration: none}
blockquote.indentedblock {margin-right: 0em}
div.display {margin-left: 3.2em}
div.example {margin-left: 3.2em}
kbd {font-style: oblique}
pre.display {font-family: inherit}
pre.format {font-family: inherit}
pre.menu-comment {font-family: serif}
pre.menu-preformatted {font-family: serif}
span.nolinebreak {white-space: nowrap}
span.roman {font-family: initial; font-weight: normal}
span.sansserif {font-family: sans-serif; font-weight: normal}
span:hover a.copiable-anchor {visibility: visible}
ul.no-bullet {list-style: none}
-->
</style>
<link rel="stylesheet" type="text/css" href="https://luis-felipe.gitlab.io/texinfo-css/static/css/texinfo-7.css">
</head>
<body lang="en">
<h1 class="settitle" align="center">Safsaf</h1>
<div style="text-align: center;">
<img src="logo.svg" alt="" width="200" height="200">
</div>
<div class="top" id="Top">
<div class="header">
<p>
Next: <a href="#Guidance" accesskey="n" rel="next">Guidance</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Overview"></span><h1 class="top">Overview</h1>
<p>Safsaf is a web framework for Guile Scheme, built on
<a href="https://codeberg.org/guile/fibers">Guile Fibers</a> using the
<a href="https://cbaines.codeberg.page/guile-knots/">Guile Knots</a> web
server.
</p>
<div class="Contents_element" id="SEC_Contents">
<h2 class="contents-heading">Table of Contents</h2>
<div class="contents">
<ul class="no-bullet">
<li><a id="toc-Guidance-1" href="#Guidance">1 Guidance</a>
<ul class="no-bullet">
<li><a id="toc-Getting-Started-1" href="#Getting-Started">1.1 Getting Started</a></li>
<li><a id="toc-Routing-1" href="#Routing">1.2 Routing</a></li>
<li><a id="toc-Handler-Wrappers-1" href="#Handler-Wrappers">1.3 Handler Wrappers</a></li>
<li><a id="toc-Responses-1" href="#Responses">1.4 Responses</a></li>
<li><a id="toc-Request-Parsing-1" href="#Request-Parsing">1.5 Request Parsing</a></li>
<li><a id="toc-Parameter-Parsing-1" href="#Parameter-Parsing">1.6 Parameter Parsing</a></li>
<li><a id="toc-Sessions-1" href="#Sessions">1.7 Sessions</a></li>
<li><a id="toc-Templating-1" href="#Templating">1.8 Templating</a></li>
<li><a id="toc-Static-Files-1" href="#Static-Files">1.9 Static Files</a></li>
</ul></li>
<li><a id="toc-API-1" href="#API">2 API</a>
<ul class="no-bullet">
<li><a id="toc-_0028safsaf_0029" href="#safsaf">2.1 (safsaf)</a>
<ul class="no-bullet">
<li><a id="toc-Procedures" href="#Procedures">2.1.1 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-handler_002dwrappers-cors_0029" href="#safsaf_005fhandler_002dwrappers_005fcors">2.2 (safsaf handler-wrappers cors)</a>
<ul class="no-bullet">
<li><a id="toc-Procedures-1" href="#Procedures-1">2.2.1 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-handler_002dwrappers-csrf_0029" href="#safsaf_005fhandler_002dwrappers_005fcsrf">2.3 (safsaf handler-wrappers csrf)</a>
<ul class="no-bullet">
<li><a id="toc-Parameters" href="#Parameters">2.3.1 Parameters</a></li>
<li><a id="toc-Procedures-2" href="#Procedures-2">2.3.2 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-handler_002dwrappers-exceptions_0029" href="#safsaf_005fhandler_002dwrappers_005fexceptions">2.4 (safsaf handler-wrappers exceptions)</a>
<ul class="no-bullet">
<li><a id="toc-Procedures-3" href="#Procedures-3">2.4.1 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-handler_002dwrappers-logging_0029" href="#safsaf_005fhandler_002dwrappers_005flogging">2.5 (safsaf handler-wrappers logging)</a>
<ul class="no-bullet">
<li><a id="toc-Procedures-4" href="#Procedures-4">2.5.1 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-handler_002dwrappers-max_002dbody_002dsize_0029" href="#safsaf_005fhandler_002dwrappers_005fmax_002dbody_002dsize">2.6 (safsaf handler-wrappers max-body-size)</a>
<ul class="no-bullet">
<li><a id="toc-Procedures-5" href="#Procedures-5">2.6.1 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-handler_002dwrappers-security_002dheaders_0029" href="#safsaf_005fhandler_002dwrappers_005fsecurity_002dheaders">2.7 (safsaf handler-wrappers security-headers)</a>
<ul class="no-bullet">
<li><a id="toc-Procedures-6" href="#Procedures-6">2.7.1 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-handler_002dwrappers-sessions_0029" href="#safsaf_005fhandler_002dwrappers_005fsessions">2.8 (safsaf handler-wrappers sessions)</a>
<ul class="no-bullet">
<li><a id="toc-Parameters-1" href="#Parameters-1">2.8.1 Parameters</a></li>
<li><a id="toc-Procedures-7" href="#Procedures-7">2.8.2 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-handler_002dwrappers-trailing_002dslash_0029" href="#safsaf_005fhandler_002dwrappers_005ftrailing_002dslash">2.9 (safsaf handler-wrappers trailing-slash)</a>
<ul class="no-bullet">
<li><a id="toc-Procedures-8" href="#Procedures-8">2.9.1 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-params_0029" href="#safsaf_005fparams">2.10 (safsaf params)</a>
<ul class="no-bullet">
<li><a id="toc-Macros" href="#Macros">2.10.1 Macros</a></li>
<li><a id="toc-Procedures-9" href="#Procedures-9">2.10.2 Procedures</a></li>
<li><a id="toc-Record-Types" href="#Record-Types">2.10.3 Record Types</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-response_002dhelpers_0029" href="#safsaf_005fresponse_002dhelpers">2.11 (safsaf response-helpers)</a>
<ul class="no-bullet">
<li><a id="toc-Procedures-10" href="#Procedures-10">2.11.1 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-router_0029" href="#safsaf_005frouter">2.12 (safsaf router)</a>
<ul class="no-bullet">
<li><a id="toc-Macros-1" href="#Macros-1">2.12.1 Macros</a></li>
<li><a id="toc-Parameters-2" href="#Parameters-2">2.12.2 Parameters</a></li>
<li><a id="toc-Procedures-11" href="#Procedures-11">2.12.3 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-templating_0029" href="#safsaf_005ftemplating">2.13 (safsaf templating)</a>
<ul class="no-bullet">
<li><a id="toc-Procedures-12" href="#Procedures-12">2.13.1 Procedures</a></li>
</ul></li>
<li><a id="toc-_0028safsaf-utils_0029" href="#safsaf_005futils">2.14 (safsaf utils)</a>
<ul class="no-bullet">
<li><a id="toc-Procedures-13" href="#Procedures-13">2.14.1 Procedures</a></li>
</ul></li>
</ul></li>
<li><a id="toc-Version-History-1" href="#Version-History">Appendix A Version History</a></li>
<li><a id="toc-Copying-Information-1" href="#Copying-Information">Appendix B Copying Information</a></li>
<li><a id="toc-Concept-Index-1" href="#Concept-Index" rel="index">Concept Index</a></li>
<li><a id="toc-Data-Type-Index-1" href="#Data-Type-Index" rel="index">Data Type Index</a></li>
<li><a id="toc-Procedure-Index-1" href="#Procedure-Index" rel="index">Procedure Index</a></li>
<li><a id="toc-Variable-Index-1" href="#Variable-Index" rel="index">Variable Index</a></li>
</ul>
</div>
</div>
<hr>
<div class="chapter" id="Guidance">
<div class="header">
<p>
Next: <a href="#API" accesskey="n" rel="next">API</a>, Previous: <a href="#Top" accesskey="p" rel="prev">Overview</a>, Up: <a href="#Top" accesskey="u" rel="up">Overview</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Guidance-1"></span><h2 class="chapter">1 Guidance</h2>
<p>This chapter explains how the pieces of Safsaf fit together. Each
section covers one concept with a short code example. For the full
list of parameters and options, see <a href="#API">API</a>.
</p>
<ul class="section-toc">
<li><a href="#Getting-Started" accesskey="1">Getting Started</a></li>
<li><a href="#Routing" accesskey="2">Routing</a></li>
<li><a href="#Handler-Wrappers" accesskey="3">Handler Wrappers</a></li>
<li><a href="#Responses" accesskey="4">Responses</a></li>
<li><a href="#Request-Parsing" accesskey="5">Request Parsing</a></li>
<li><a href="#Parameter-Parsing" accesskey="6">Parameter Parsing</a></li>
<li><a href="#Sessions" accesskey="7">Sessions</a></li>
<li><a href="#Templating" accesskey="8">Templating</a></li>
<li><a href="#Static-Files" accesskey="9">Static Files</a></li>
</ul>
<hr>
<div class="section" id="Getting-Started">
<div class="header">
<p>
Next: <a href="#Routing" accesskey="n" rel="next">Routing</a>, Up: <a href="#Guidance" accesskey="u" rel="up">Guidance</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Getting-Started-1"></span><h3 class="section">1.1 Getting Started</h3>
<p>A Safsaf application is a list of routes passed to <code>run-safsaf</code>.
Each route binds an HTTP method and URL pattern to a handler procedure.
The handler receives a Guile <code>&lt;request&gt;</code> and a body port, and
returns two values: a response and a body.
</p>
<div class="example lisp">
<pre class="lisp">(use-modules (safsaf)
(safsaf router)
(safsaf response-helpers))
(define routes
(list
(route 'GET '() index-page
#:name 'index)
(route 'GET '(&quot;hello&quot; name) hello-page)
(route '* '* (lambda (request body-port)
(not-found-response)))))
(define (index-page request body-port)
(html-response '(h1 &quot;Welcome&quot;)))
(define (hello-page request body-port)
(let ((name (assoc-ref (current-route-params) 'name)))
(text-response (string-append &quot;Hello, &quot; name &quot;!&quot;))))
(run-safsaf routes #:port 8080)
</pre></div>
<p>The last route should be a catch-all (<code>'*</code> method, <code>'*</code>
pattern) so that every request is handled. <code>run-safsaf</code> sets up
a Fibers scheduler, starts the HTTP server, and blocks until Ctrl-C.
</p>
<hr>
</div>
<div class="section" id="Routing">
<div class="header">
<p>
Next: <a href="#Handler-Wrappers" accesskey="n" rel="next">Handler Wrappers</a>, Previous: <a href="#Getting-Started" accesskey="p" rel="prev">Getting Started</a>, Up: <a href="#Guidance" accesskey="u" rel="up">Guidance</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Routing-1"></span><h3 class="section">1.2 Routing</h3>
<span id="Patterns"></span><h4 class="subheading">Patterns</h4>
<p>Route patterns are lists of segments. A string matches literally, a
symbol captures that segment into <code>current-route-params</code>, and a
two-element list <code>(predicate name)</code> captures only when
<var>predicate</var> returns true. A dotted tail captures the remaining
path.
</p>
<div class="example lisp">
<pre class="lisp">;; Literal: /about
(route 'GET '(&quot;about&quot;) about-handler)
;; Capture: /users/:id
(route 'GET '(&quot;users&quot; id) show-user)
;; Predicate: /posts/:id where id is numeric
(route 'GET '(&quot;posts&quot; (,string-&gt;number id)) show-post)
;; Wildcard (rest): /files/* — captures remaining segments
(route 'GET '(&quot;files&quot; . path) serve-file)
</pre></div>
<span id="Route-groups"></span><h4 class="subheading">Route groups</h4>
<p><code>route-group</code> nests routes under a shared prefix:
</p>
<div class="example lisp">
<pre class="lisp">(route-group '(&quot;api&quot; &quot;v1&quot;)
(route 'GET '(&quot;users&quot;) api-list-users)
(route 'GET '(&quot;users&quot; id) api-show-user))
</pre></div>
<p>This matches <code>/api/v1/users</code> and <code>/api/v1/users/:id</code>.
</p>
<span id="Named-routes-and-path_002dfor"></span><h4 class="subheading">Named routes and path-for</h4>
<p>Give a route a <code>#:name</code> and use <code>path-for</code> to generate its
URL, so paths are never hard-coded. The first argument is always a
route group:
</p>
<div class="example lisp">
<pre class="lisp">(define my-routes
(route-group '()
(route 'GET '(&quot;posts&quot; id) show-post #:name 'show-post)))
(define all-routes
(list my-routes
(route '* '* (lambda (r b) (not-found-response)))))
;; In a handler or template:
(path-for my-routes 'show-post '((id . &quot;42&quot;)))
;; =&gt; &quot;/posts/42&quot;
</pre></div>
<p><code>path-for</code> also accepts <code>#:query</code> and <code>#:fragment</code>
keyword arguments.
</p>
<hr>
</div>
<div class="section" id="Handler-Wrappers">
<div class="header">
<p>
Next: <a href="#Responses" accesskey="n" rel="next">Responses</a>, Previous: <a href="#Routing" accesskey="p" rel="prev">Routing</a>, Up: <a href="#Guidance" accesskey="u" rel="up">Guidance</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Handler-Wrappers-1"></span><h3 class="section">1.3 Handler Wrappers</h3>
<p>A handler wrapper is a procedure that takes a handler and returns a
new handler. It can transform the request on the way in and the
response on the way out. Apply wrappers to a route tree with
<code>wrap-routes</code>.
</p>
<div class="example lisp">
<pre class="lisp">(wrap-routes routes
(make-exceptions-handler-wrapper #:dev? #t)
logging-handler-wrapper)
</pre></div>
<p>When multiple wrappers are given, the first wraps outermost — it runs
first on the request and last on the response. In the example above,
exceptions catches errors from the logging wrapper and the inner
handler.
</p>
<span id="Per_002dgroup-wrappers"></span><h4 class="subheading">Per-group wrappers</h4>
<p>Apply wrappers to part of the route tree by wrapping a group
separately:
</p>
<div class="example lisp">
<pre class="lisp">(define api-routes
(wrap-routes
(route-group '(&quot;api&quot;)
(route 'GET '(&quot;items&quot;) api-list-items))
cors-handler-wrapper))
(define all-routes
(wrap-routes
(list api-routes
(route 'GET '() index-page)
(route '* '* (lambda (r b) (not-found-response))))
logging-handler-wrapper))
</pre></div>
<p>Here CORS headers are added only to <code>/api/*</code> routes, while
logging applies to everything.
</p>
<span id="Security-headers"></span><h4 class="subheading">Security headers</h4>
<p><code>security-headers-handler-wrapper</code> <em>appends</em> its headers to
the response rather than replacing existing ones. If a handler sets
<code>X-Frame-Options</code> itself, both values will appear in the response.
To avoid duplication, either omit the header from the wrapper (pass
<code>#:frame-options #f</code>) or do not set it in the handler.
</p>
<span id="Max-body-size"></span><h4 class="subheading">Max body size</h4>
<p><code>make-max-body-size-handler-wrapper</code> checks the
<code>Content-Length</code> header and rejects requests that exceed the
limit with a 413 response. However, it does <em>not</em> limit chunked
transfer-encoded requests that lack <code>Content-Length</code>. For
untrusted networks, use a reverse proxy (e.g. Nginx&rsquo;s
<code>client_max_body_size</code>) to enforce size limits at the transport
level.
</p>
<hr>
</div>
<div class="section" id="Responses">
<div class="header">
<p>
Next: <a href="#Request-Parsing" accesskey="n" rel="next">Request Parsing</a>, Previous: <a href="#Handler-Wrappers" accesskey="p" rel="prev">Handler Wrappers</a>, Up: <a href="#Guidance" accesskey="u" rel="up">Guidance</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Responses-1"></span><h3 class="section">1.4 Responses</h3>
<p>Safsaf provides helpers that return <code>(values response body)</code>
directly:
</p>
<div class="example lisp">
<pre class="lisp">;; HTML — streams an SXML tree
(html-response '(div (h1 &quot;Hello&quot;) (p &quot;world&quot;)))
;; JSON — takes a JSON string
(json-response (scm-&gt;json-string '((&quot;ok&quot; . #t))))
;; Plain text
(text-response &quot;pong&quot;)
;; Redirect (default 303 See Other)
(redirect-response &quot;/login&quot;)
(redirect-response &quot;/new-item&quot; #:code 302)
;; Error responses
(not-found-response)
(bad-request-response &quot;Missing field&quot;)
(forbidden-response)
</pre></div>
<p><code>html-response</code>, <code>json-response</code>, <code>text-response</code>, and
<code>redirect-response</code> accept <code>#:code</code> and <code>#:headers</code> for
overrides. The error helpers (<code>not-found-response</code>, etc.) accept
<code>#:headers</code> but have a fixed status code.
</p>
<p>For content negotiation, use <code>negotiate-content-type</code>:
</p>
<div class="example lisp">
<pre class="lisp">(define (show-item request body-port)
(let ((item (fetch-item (assoc-ref (current-route-params) 'id))))
(case (negotiate-content-type request
'(text/html application/json))
((application/json)
(json-response (scm-&gt;json-string (item-&gt;alist item))))
(else
(html-response `(div (h1 ,(item-title item))))))))
</pre></div>
<hr>
</div>
<div class="section" id="Request-Parsing">
<div class="header">
<p>
Next: <a href="#Parameter-Parsing" accesskey="n" rel="next">Parameter Parsing</a>, Previous: <a href="#Responses" accesskey="p" rel="prev">Responses</a>, Up: <a href="#Guidance" accesskey="u" rel="up">Guidance</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Request-Parsing-1"></span><h3 class="section">1.5 Request Parsing</h3>
<span id="Form-bodies"></span><h4 class="subheading">Form bodies</h4>
<p><code>parse-form-body</code> reads a URL-encoded POST body and returns an
alist of string pairs:
</p>
<div class="example lisp">
<pre class="lisp">(define (handle-login request body-port)
(let* ((form (parse-form-body request body-port))
(username (assoc-ref form &quot;username&quot;))
(password (assoc-ref form &quot;password&quot;)))
(if (valid-credentials? username password)
(redirect-response &quot;/dashboard&quot;)
(text-response &quot;Invalid login&quot; #:code 401))))
</pre></div>
<span id="Query-strings"></span><h4 class="subheading">Query strings</h4>
<p><code>parse-query-string</code> extracts query parameters from the request
URL:
</p>
<div class="example lisp">
<pre class="lisp">(let ((qs (parse-query-string request)))
(assoc-ref qs &quot;page&quot;)) ;; =&gt; &quot;2&quot; or #f
</pre></div>
<span id="Multipart"></span><h4 class="subheading">Multipart</h4>
<p>For file uploads, use <code>parse-multipart-body</code>:
</p>
<div class="example lisp">
<pre class="lisp">(let* ((parts (parse-multipart-body request body-port))
(form (multipart-text-fields parts))
(file (parts-ref parts &quot;avatar&quot;)))
;; form is an alist of text fields
;; file is a &lt;part&gt; record — read its body with (part-body file)
...)
</pre></div>
<span id="Cookies"></span><h4 class="subheading">Cookies</h4>
<p>Read cookies with <code>request-cookie-ref</code> or
<code>request-cookies</code>. Set them via response headers with
<code>set-cookie-header</code> and <code>delete-cookie-header</code>:
</p>
<div class="example lisp">
<pre class="lisp">(request-cookie-ref request &quot;theme&quot;) ;; =&gt; &quot;dark&quot; or #f
(text-response &quot;ok&quot;
#:headers (list (set-cookie-header &quot;theme&quot; &quot;dark&quot;
#:path &quot;/&quot;
#:http-only #t)))
</pre></div>
<hr>
</div>
<div class="section" id="Parameter-Parsing">
<div class="header">
<p>
Next: <a href="#Sessions" accesskey="n" rel="next">Sessions</a>, Previous: <a href="#Request-Parsing" accesskey="p" rel="prev">Request Parsing</a>, Up: <a href="#Guidance" accesskey="u" rel="up">Guidance</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Parameter-Parsing-1"></span><h3 class="section">1.6 Parameter Parsing</h3>
<p><code>parse-params</code> validates and transforms raw form or query data
according to a declarative spec. Each spec entry names a parameter,
a processor (a procedure that converts a string or returns an
<code>&lt;invalid-param&gt;</code>), and options like <code>#:required</code> or
<code>#:default</code>.
</p>
<div class="example lisp">
<pre class="lisp">(let ((params (parse-params
`((page ,as-integer #:default 1)
(per-page ,as-integer #:default 20)
(q ,as-string))
(parse-query-string request))))
(assq-ref params 'page)) ;; =&gt; 1 (integer, not string)
</pre></div>
<p>Built-in processors: <code>as-string</code>, <code>as-integer</code>,
<code>as-number</code>, <code>as-checkbox</code>, <code>as-one-of</code>,
<code>as-matching</code>, <code>as-predicate</code>.
</p>
<span id="Form-params-with-CSRF"></span><h4 class="subheading">Form params with CSRF</h4>
<p>For POST forms, use <code>parse-form-params</code> instead — it
automatically checks the CSRF token (from
<code>csrf-handler-wrapper</code>) before parsing:
</p>
<div class="example lisp">
<pre class="lisp">(let* ((form (parse-form-body request body-port))
(params (parse-form-params
`((title ,as-string #:required)
(body ,as-string #:required))
form)))
(if (any-invalid-params? params)
;; Re-render the form with errors
(render-form (field-errors params 'title)
(field-errors params 'body))
;; Proceed
(create-item! (assq-ref params 'title)
(assq-ref params 'body))))
</pre></div>
<p><code>any-invalid-params?</code> returns <code>#t</code> if any value failed
validation. <code>field-errors</code> returns a list of error message
strings for a given field, suitable for rendering next to form inputs.
</p>
<hr>
</div>
<div class="section" id="Sessions">
<div class="header">
<p>
Next: <a href="#Templating" accesskey="n" rel="next">Templating</a>, Previous: <a href="#Parameter-Parsing" accesskey="p" rel="prev">Parameter Parsing</a>, Up: <a href="#Guidance" accesskey="u" rel="up">Guidance</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Sessions-1"></span><h3 class="section">1.7 Sessions</h3>
<p>Sessions use HMAC-signed cookies via <code>(webutils sessions)</code>.
Set up a session config and apply the wrapper:
</p>
<div class="example lisp">
<pre class="lisp">(define session-mgr
(make-session-config &quot;my-secret-key&quot;
#:cookie-name &quot;my-session&quot;))
(define routes
(wrap-routes my-routes
(make-session-handler-wrapper session-mgr)))
</pre></div>
<p>Inside a handler, <code>(current-session)</code> returns the session data
(an alist) or <code>#f</code> if no valid session exists.
</p>
<p>To set session data, include a <code>session-set</code> header in the
response. To delete, use <code>session-delete</code>:
</p>
<div class="example lisp">
<pre class="lisp">;; Set session
(redirect-response &quot;/&quot;
#:headers (list (session-set session-mgr
'((user-id . 42)))))
;; Read session
(let ((user-id (and (current-session)
(assoc-ref (current-session) 'user-id))))
...)
;; Delete session
(redirect-response &quot;/&quot;
#:headers (list (session-delete session-mgr)))
</pre></div>
<hr>
</div>
<div class="section" id="Templating">
<div class="header">
<p>
Next: <a href="#Static-Files" accesskey="n" rel="next">Static Files</a>, Previous: <a href="#Sessions" accesskey="p" rel="prev">Sessions</a>, Up: <a href="#Guidance" accesskey="u" rel="up">Guidance</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Templating-1"></span><h3 class="section">1.8 Templating</h3>
<p><code>write-shtml-as-html/streaming</code> works like htmlprag&rsquo;s
<code>write-shtml-as-html</code>, but any procedure in the SHTML tree is
called as <code>(proc port)</code> and can write dynamic content directly.
</p>
<p><code>streaming-html-response</code> wraps this into a response: give it an
SHTML tree (with optional procedure slots) and it returns
<code>(values response body)</code> ready for a handler.
</p>
<div class="example lisp">
<pre class="lisp">(define (base-layout title content-proc)
`(*TOP*
(*DECL* DOCTYPE html)
(html
(head (title ,title))
(body
(nav (a (@ (href &quot;/&quot;)) &quot;Home&quot;))
(main ,content-proc)
(footer (p &quot;Footer&quot;))))))
</pre></div>
<p>The layout is plain SHTML with a procedure in the <var>content-proc</var>
position. Use <code>streaming-html-response</code> to send it:
</p>
<div class="example lisp">
<pre class="lisp">(define (index-page request body-port)
(streaming-html-response
(base-layout &quot;Home&quot;
(lambda (port)
(write-shtml-as-html
`(div (h1 &quot;Welcome&quot;)
(p &quot;Content goes here.&quot;))
port)))))
</pre></div>
<p>You can also call <code>write-shtml-as-html/streaming</code> directly when
you need to write SHTML with procedure slots to an arbitrary port.
</p>
<hr>
</div>
<div class="section" id="Static-Files">
<div class="header">
<p>
Previous: <a href="#Templating" accesskey="p" rel="prev">Templating</a>, Up: <a href="#Guidance" accesskey="u" rel="up">Guidance</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Static-Files-1"></span><h3 class="section">1.9 Static Files</h3>
<p><code>make-static-handler</code> returns a handler that serves files from a
directory. Pair it with a wildcard route:
</p>
<div class="example lisp">
<pre class="lisp">(route-group '(&quot;static&quot;)
(route 'GET '(. path)
(make-static-handler &quot;./public&quot;
#:cache-control '((max-age . 3600)))))
</pre></div>
<p>This serves <code>/static/css/style.css</code> from
<code>./public/css/style.css</code>. The handler supports
<code>If-Modified-Since</code> for 304 responses.
</p>
<hr>
</div>
</div>
<div class="chapter" id="API">
<div class="header">
<p>
Next: <a href="#Version-History" accesskey="n" rel="next">Version History</a>, Previous: <a href="#Guidance" accesskey="p" rel="prev">Guidance</a>, Up: <a href="#Top" accesskey="u" rel="up">Overview</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="API-1"></span><h2 class="chapter">2 API</h2>
<p>The following is the list of modules provided by this library.
</p>
<ul class="section-toc">
<li><a href="#safsaf" accesskey="1">(safsaf)</a></li>
<li><a href="#safsaf_005fhandler_002dwrappers_005fcors" accesskey="2">(safsaf handler-wrappers cors)</a></li>
<li><a href="#safsaf_005fhandler_002dwrappers_005fcsrf" accesskey="3">(safsaf handler-wrappers csrf)</a></li>
<li><a href="#safsaf_005fhandler_002dwrappers_005fexceptions" accesskey="4">(safsaf handler-wrappers exceptions)</a></li>
<li><a href="#safsaf_005fhandler_002dwrappers_005flogging" accesskey="5">(safsaf handler-wrappers logging)</a></li>
<li><a href="#safsaf_005fhandler_002dwrappers_005fmax_002dbody_002dsize" accesskey="6">(safsaf handler-wrappers max-body-size)</a></li>
<li><a href="#safsaf_005fhandler_002dwrappers_005fsecurity_002dheaders" accesskey="7">(safsaf handler-wrappers security-headers)</a></li>
<li><a href="#safsaf_005fhandler_002dwrappers_005fsessions" accesskey="8">(safsaf handler-wrappers sessions)</a></li>
<li><a href="#safsaf_005fhandler_002dwrappers_005ftrailing_002dslash" accesskey="9">(safsaf handler-wrappers trailing-slash)</a></li>
<li><a href="#safsaf_005fparams">(safsaf params)</a></li>
<li><a href="#safsaf_005fresponse_002dhelpers">(safsaf response-helpers)</a></li>
<li><a href="#safsaf_005frouter">(safsaf router)</a></li>
<li><a href="#safsaf_005ftemplating">(safsaf templating)</a></li>
<li><a href="#safsaf_005futils">(safsaf utils)</a></li>
</ul>
<hr>
<div class="section" id="safsaf">
<div class="header">
<p>
Next: <a href="#safsaf_005fhandler_002dwrappers_005fcors" accesskey="n" rel="next">(safsaf handler-wrappers cors)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf_0029"></span><h3 class="section">2.1 (safsaf)</h3>
<ul class="section-toc">
<li><a href="#Procedures" accesskey="1">Procedures</a></li>
</ul>
<div class="subsection" id="Procedures">
<h4 class="subsection">2.1.1 Procedures</h4>
<dl class="def">
<dt id="index-default_002dmethod_002dnot_002dallowed_002dhandler"><span class="category">Procedure: </span><span><strong>default-method-not-allowed-handler</strong> <em>request allowed-methods</em><a href='#index-default_002dmethod_002dnot_002dallowed_002dhandler' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a 405 Method Not Allowed response with an Allow header listing
ALLOWED-METHODS.
</p>
</dd></dl>
<dl class="def">
<dt id="index-run_002dsafsaf"><span class="category">Procedure: </span><span><strong>run-safsaf</strong> <em>routes KEY: #:host #:port #:method-not-allowed? #:method-not-allowed-handler #:connection-buffer-size</em><a href='#index-run_002dsafsaf' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Start a Safsaf web server.
</p>
<p>ROUTES is a list of routes and route-groups (as returned by component
constructors). The last route must be a catch-all so that every request
is handled.
</p>
<p>HEAD requests are handled automatically: when no explicit HEAD route
matches, the matching GET handler runs and its response body is
discarded. Explicit HEAD routes always take precedence.
</p>
<p>When METHOD-NOT-ALLOWED? is #t (the default), requests that match a
route&rsquo;s path but not its method receive a 405 response with an Allow
header. METHOD-NOT-ALLOWED-HANDLER is a procedure (request
allowed-methods) -&gt; (values response body) that produces the 405
response; the default returns plain text.
</p>
<p>When called outside a Fibers scheduler, sets up a scheduler, starts the
HTTP server, and blocks until Ctrl-C. When called inside an existing
scheduler (e.g. within run-fibers), just starts the HTTP server and
returns immediately — the caller manages the lifecycle.
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005fhandler_002dwrappers_005fcors">
<div class="header">
<p>
Next: <a href="#safsaf_005fhandler_002dwrappers_005fcsrf" accesskey="n" rel="next">(safsaf handler-wrappers csrf)</a>, Previous: <a href="#safsaf" accesskey="p" rel="prev">(safsaf)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-handler_002dwrappers-cors_0029"></span><h3 class="section">2.2 (safsaf handler-wrappers cors)</h3>
<ul class="section-toc">
<li><a href="#Procedures-1" accesskey="1">Procedures</a></li>
</ul>
<div class="subsection" id="Procedures-1">
<h4 class="subsection">2.2.1 Procedures</h4>
<dl class="def">
<dt id="index-cors_002dhandler_002dwrapper"><span class="category">Procedure: </span><span><strong>cors-handler-wrapper</strong> <em>handler KEY: #:origins #:methods #:headers #:max-age #:allow-credentials? #:expose-headers</em><a href='#index-cors_002dhandler_002dwrapper' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Handler wrapper that adds CORS (Cross-Origin Resource Sharing) headers
to responses.
</p>
<p>Browsers enforce the Same-Origin Policy: scripts on one origin (scheme +
host + port) cannot read responses from a different origin. CORS
relaxes this by letting the server declare which origins, methods, and
headers are permitted.
</p>
<p>For &ldquo;simple&rdquo; requests the browser sends the request and checks the
response headers. For non-simple requests (e.g. PUT/DELETE, custom
headers, or JSON Content-Type) the browser sends a preflight OPTIONS
request first. This wrapper handles both cases.
</p>
<p>ORIGINS is a list of allowed origin strings, or &rsquo;(&quot;*&quot;) for any. METHODS
is a list of allowed method symbols. HEADERS is a list of allowed
request header name strings. MAX-AGE is the preflight cache duration in
seconds. ALLOW-CREDENTIALS? controls whether credentials (cookies,
auth) are allowed cross-origin. Note: cannot be #t when origins is
&rsquo;(&quot;*&quot;). EXPOSE-HEADERS is a list of response header name strings the
browser may read from JavaScript.
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005fhandler_002dwrappers_005fcsrf">
<div class="header">
<p>
Next: <a href="#safsaf_005fhandler_002dwrappers_005fexceptions" accesskey="n" rel="next">(safsaf handler-wrappers exceptions)</a>, Previous: <a href="#safsaf_005fhandler_002dwrappers_005fcors" accesskey="p" rel="prev">(safsaf handler-wrappers cors)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-handler_002dwrappers-csrf_0029"></span><h3 class="section">2.3 (safsaf handler-wrappers csrf)</h3>
<ul class="section-toc">
<li><a href="#Parameters" accesskey="1">Parameters</a></li>
<li><a href="#Procedures-2" accesskey="2">Procedures</a></li>
</ul>
<div class="subsection" id="Parameters">
<h4 class="subsection">2.3.1 Parameters</h4>
<dl class="def">
<dt id="index-current_002dcsrf_002dtoken"><span class="category">Parameter: </span><span><strong>current-csrf-token</strong><a href='#index-current_002dcsrf_002dtoken' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Default value:
</p>
<pre class="verbatim">#f
</pre>
</dd></dl>
</div>
<div class="subsection" id="Procedures-2">
<h4 class="subsection">2.3.2 Procedures</h4>
<dl class="def">
<dt id="index-csrf_002dhandler_002dwrapper"><span class="category">Procedure: </span><span><strong>csrf-handler-wrapper</strong> <em>handler KEY: #:cookie-name</em><a href='#index-csrf_002dhandler_002dwrapper' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>CSRF token handler wrapper.
</p>
<p>Ensures a CSRF token cookie is present on every response (generates one
if the request has none). The token is bound to current-csrf-token so
handlers and templates can read it via (current-csrf-token).
</p>
<p>Token validation is NOT done here — it belongs in the form processing
layer. Use parse-form-params from (safsaf params), which automatically
checks the submitted token against the cookie token.
</p>
</dd></dl>
<dl class="def">
<dt id="index-csrf_002dtoken_002dfield"><span class="category">Procedure: </span><span><strong>csrf-token-field</strong><a href='#index-csrf_002dtoken_002dfield' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return an SXML hidden input element for the CSRF token. Use in forms:
<code>(csrf-token-field)</code> &rArr; <code>(input (@ (type &quot;hidden&quot;)
...))</code>.
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005fhandler_002dwrappers_005fexceptions">
<div class="header">
<p>
Next: <a href="#safsaf_005fhandler_002dwrappers_005flogging" accesskey="n" rel="next">(safsaf handler-wrappers logging)</a>, Previous: <a href="#safsaf_005fhandler_002dwrappers_005fcsrf" accesskey="p" rel="prev">(safsaf handler-wrappers csrf)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-handler_002dwrappers-exceptions_0029"></span><h3 class="section">2.4 (safsaf handler-wrappers exceptions)</h3>
<ul class="section-toc">
<li><a href="#Procedures-3" accesskey="1">Procedures</a></li>
</ul>
<div class="subsection" id="Procedures-3">
<h4 class="subsection">2.4.1 Procedures</h4>
<dl class="def">
<dt id="index-default_002drender_002derror"><span class="category">Procedure: </span><span><strong>default-render-error</strong> <em>render-html render-json</em><a href='#index-default_002drender_002derror' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a render-error procedure that content-negotiates between
RENDER-HTML and RENDER-JSON based on the request&rsquo;s Accept header.
</p>
</dd></dl>
<dl class="def">
<dt id="index-default_002drender_002dhtml"><span class="category">Procedure: </span><span><strong>default-render-html</strong> <em>request code message backtrace-string dev?</em><a href='#index-default_002drender_002dhtml' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Default HTML error renderer. In dev mode, shows a rich backtrace page.
In production, returns a minimal HTML page.
</p>
</dd></dl>
<dl class="def">
<dt id="index-default_002drender_002djson"><span class="category">Procedure: </span><span><strong>default-render-json</strong> <em>_request code message backtrace-string dev?</em><a href='#index-default_002drender_002djson' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Default JSON error renderer. In dev mode, includes the backtrace. In
production, returns only the error message.
</p>
</dd></dl>
<dl class="def">
<dt id="index-exceptions_002dhandler_002dwrapper"><span class="category">Procedure: </span><span><strong>exceptions-handler-wrapper</strong> <em>handler KEY: #:dev? #:logger #:render-html #:render-json #:render-error</em><a href='#index-exceptions_002dhandler_002dwrapper' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Handler wrapper that catches exceptions from HANDLER and returns an
error response.
</p>
<p>The response format is content-negotiated from the request&rsquo;s Accept
header, choosing between HTML and JSON.
</p>
<p>When LOGGER is provided, exceptions are logged through it. Otherwise,
the backtrace is written to the current error port. In dev mode (DEV?
is #t), the response includes the backtrace and exception details. In
production mode, a generic error is returned.
</p>
<p>Rendering can be customised at three levels:
</p>
<p>#:render-error — full override. A procedure (request code message
backtrace-string dev?) -&gt; (values response body) that bypasses content
negotiation entirely.
</p>
<p>#:render-html — custom HTML rendering. A procedure with the same
signature, called when content negotiation selects HTML.
</p>
<p>#:render-json — custom JSON rendering. A procedure with the same
signature, called when content negotiation selects JSON.
</p>
<p>The default RENDER-ERROR content-negotiates between RENDER-HTML and
RENDER-JSON. Providing #:render-html or #:render-json replaces just
that format; providing #:render-error replaces the entire rendering.
</p>
</dd></dl>
<dl class="def">
<dt id="index-make_002dexceptions_002dhandler_002dwrapper"><span class="category">Procedure: </span><span><strong>make-exceptions-handler-wrapper</strong> <em>KEY: #:dev? #:logger #:render-html #:render-json #:render-error</em><a href='#index-make_002dexceptions_002dhandler_002dwrapper' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a handler wrapper that catches exceptions and returns an error
response. See exceptions-handler-wrapper for details.
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005fhandler_002dwrappers_005flogging">
<div class="header">
<p>
Next: <a href="#safsaf_005fhandler_002dwrappers_005fmax_002dbody_002dsize" accesskey="n" rel="next">(safsaf handler-wrappers max-body-size)</a>, Previous: <a href="#safsaf_005fhandler_002dwrappers_005fexceptions" accesskey="p" rel="prev">(safsaf handler-wrappers exceptions)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-handler_002dwrappers-logging_0029"></span><h3 class="section">2.5 (safsaf handler-wrappers logging)</h3>
<ul class="section-toc">
<li><a href="#Procedures-4" accesskey="1">Procedures</a></li>
</ul>
<div class="subsection" id="Procedures-4">
<h4 class="subsection">2.5.1 Procedures</h4>
<dl class="def">
<dt id="index-logging_002dhandler_002dwrapper"><span class="category">Procedure: </span><span><strong>logging-handler-wrapper</strong> <em>handler KEY: #:logger #:level</em><a href='#index-logging_002dhandler_002dwrapper' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Handler wrapper that logs each request and response.
</p>
<p>Logs at LEVEL (default &rsquo;INFO) with method, path, status code, and
duration in milliseconds. If LOGGER is given, logs to that logger;
otherwise uses the default logger set via set-default-logger!.
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005fhandler_002dwrappers_005fmax_002dbody_002dsize">
<div class="header">
<p>
Next: <a href="#safsaf_005fhandler_002dwrappers_005fsecurity_002dheaders" accesskey="n" rel="next">(safsaf handler-wrappers security-headers)</a>, Previous: <a href="#safsaf_005fhandler_002dwrappers_005flogging" accesskey="p" rel="prev">(safsaf handler-wrappers logging)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-handler_002dwrappers-max_002dbody_002dsize_0029"></span><h3 class="section">2.6 (safsaf handler-wrappers max-body-size)</h3>
<ul class="section-toc">
<li><a href="#Procedures-5" accesskey="1">Procedures</a></li>
</ul>
<div class="subsection" id="Procedures-5">
<h4 class="subsection">2.6.1 Procedures</h4>
<dl class="def">
<dt id="index-make_002dmax_002dbody_002dsize_002dhandler_002dwrapper"><span class="category">Procedure: </span><span><strong>make-max-body-size-handler-wrapper</strong> <em>max-bytes KEY: #:handler-413</em><a href='#index-make_002dmax_002dbody_002dsize_002dhandler_002dwrapper' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a handler wrapper that rejects requests whose Content-Length
exceeds MAX-BYTES with a 413 Payload Too Large response.
</p>
<p>HANDLER-413 is a handler (request body-port) -&gt; (values response body)
called when the limit is exceeded; the default returns plain text.
</p>
<p>Note: this checks the Content-Length header only. Chunked transfers
without Content-Length are not limited by this wrapper.
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005fhandler_002dwrappers_005fsecurity_002dheaders">
<div class="header">
<p>
Next: <a href="#safsaf_005fhandler_002dwrappers_005fsessions" accesskey="n" rel="next">(safsaf handler-wrappers sessions)</a>, Previous: <a href="#safsaf_005fhandler_002dwrappers_005fmax_002dbody_002dsize" accesskey="p" rel="prev">(safsaf handler-wrappers max-body-size)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-handler_002dwrappers-security_002dheaders_0029"></span><h3 class="section">2.7 (safsaf handler-wrappers security-headers)</h3>
<ul class="section-toc">
<li><a href="#Procedures-6" accesskey="1">Procedures</a></li>
</ul>
<div class="subsection" id="Procedures-6">
<h4 class="subsection">2.7.1 Procedures</h4>
<dl class="def">
<dt id="index-security_002dheaders_002dhandler_002dwrapper"><span class="category">Procedure: </span><span><strong>security-headers-handler-wrapper</strong> <em>handler KEY: #:content-type-options #:frame-options #:strict-transport-security #:referrer-policy #:cross-origin-opener-policy #:permissions-policy #:content-security-policy #:content-security-policy-report-only</em><a href='#index-security_002dheaders_002dhandler_002dwrapper' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Handler wrapper that adds security headers to every response.
</p>
<p>All headers are optional and configurable. Pass #f to disable a header.
Defaults: X-Content-Type-Options: nosniff X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
</p>
<p>Not set by default (enable explicitly): Strict-Transport-Security (e.g.
&quot;max-age=63072000; includeSubDomains&quot;) Cross-Origin-Opener-Policy (e.g.
&quot;same-origin&quot;) Permissions-Policy (e.g. &quot;camera=(), microphone=()&quot;)
Content-Security-Policy (e.g. &quot;default-src &rsquo;self&rsquo;; script-src &rsquo;self&rsquo;&quot;)
Content-Security-Policy-Report-Only — same syntax, for testing policies
without enforcing them
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005fhandler_002dwrappers_005fsessions">
<div class="header">
<p>
Next: <a href="#safsaf_005fhandler_002dwrappers_005ftrailing_002dslash" accesskey="n" rel="next">(safsaf handler-wrappers trailing-slash)</a>, Previous: <a href="#safsaf_005fhandler_002dwrappers_005fsecurity_002dheaders" accesskey="p" rel="prev">(safsaf handler-wrappers security-headers)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-handler_002dwrappers-sessions_0029"></span><h3 class="section">2.8 (safsaf handler-wrappers sessions)</h3>
<ul class="section-toc">
<li><a href="#Parameters-1" accesskey="1">Parameters</a></li>
<li><a href="#Procedures-7" accesskey="2">Procedures</a></li>
</ul>
<div class="subsection" id="Parameters-1">
<h4 class="subsection">2.8.1 Parameters</h4>
<dl class="def">
<dt id="index-current_002dsession"><span class="category">Parameter: </span><span><strong>current-session</strong><a href='#index-current_002dsession' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Default value:
</p>
<pre class="verbatim">#f
</pre>
</dd></dl>
</div>
<div class="subsection" id="Procedures-7">
<h4 class="subsection">2.8.2 Procedures</h4>
<dl class="def">
<dt id="index-make_002dsession_002dconfig"><span class="category">Procedure: </span><span><strong>make-session-config</strong> <em>secret-key KEY: #:cookie-name #:expire-delta #:algorithm</em><a href='#index-make_002dsession_002dconfig' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Create a session manager for use with session-handler-wrapper.
</p>
<p>SECRET-KEY is the HMAC signing key (a string). EXPIRE-DELTA is (days
hours minutes), default 30 days. ALGORITHM is the HMAC algorithm,
default sha512.
</p>
</dd></dl>
<dl class="def">
<dt id="index-make_002dsession_002dhandler_002dwrapper"><span class="category">Procedure: </span><span><strong>make-session-handler-wrapper</strong> <em>session-manager</em><a href='#index-make_002dsession_002dhandler_002dwrapper' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a handler wrapper that binds session data from SESSION-MANAGER.
See session-handler-wrapper for details.
</p>
</dd></dl>
<dl class="def">
<dt id="index-session_002ddelete"><span class="category">Procedure: </span><span><strong>session-delete</strong> <em>session-manager</em><a href='#index-session_002ddelete' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a Set-Cookie header that expires the session cookie. Include in
a response headers list: (redirect-response &quot;/&quot; #:headers (list
(session-delete manager)))
</p>
</dd></dl>
<dl class="def">
<dt id="index-session_002dhandler_002dwrapper"><span class="category">Procedure: </span><span><strong>session-handler-wrapper</strong> <em>handler session-manager</em><a href='#index-session_002dhandler_002dwrapper' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Session handler wrapper using signed cookies via (webutils sessions).
</p>
<p>Reads the session cookie from the request, verifies the HMAC signature,
and binds current-session for the duration of the handler. If no valid
session cookie is present, current-session is #f.
</p>
<p>Handlers read session data via: (current-session) → session data or #f
</p>
<p>To set or delete the session, handlers include the appropriate header in
their response using session-set and session-delete:
</p>
<p>(redirect-response &quot;/&quot; #:headers (list (session-set manager data)))
(redirect-response &quot;/&quot; #:headers (list (session-delete manager)))
</p>
</dd></dl>
<dl class="def">
<dt id="index-session_002dset"><span class="category">Procedure: </span><span><strong>session-set</strong> <em>session-manager data</em><a href='#index-session_002dset' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a Set-Cookie header that stores signed DATA in the session
cookie. DATA can be any Scheme value that can be written and read back.
Include in a response headers list: (redirect-response &quot;/&quot; #:headers
(list (session-set manager &rsquo;((user-id . 42)))))
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005fhandler_002dwrappers_005ftrailing_002dslash">
<div class="header">
<p>
Next: <a href="#safsaf_005fparams" accesskey="n" rel="next">(safsaf params)</a>, Previous: <a href="#safsaf_005fhandler_002dwrappers_005fsessions" accesskey="p" rel="prev">(safsaf handler-wrappers sessions)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-handler_002dwrappers-trailing_002dslash_0029"></span><h3 class="section">2.9 (safsaf handler-wrappers trailing-slash)</h3>
<ul class="section-toc">
<li><a href="#Procedures-8" accesskey="1">Procedures</a></li>
</ul>
<div class="subsection" id="Procedures-8">
<h4 class="subsection">2.9.1 Procedures</h4>
<dl class="def">
<dt id="index-make_002dtrailing_002dslash_002dhandler_002dwrapper"><span class="category">Procedure: </span><span><strong>make-trailing-slash-handler-wrapper</strong> <em>KEY: #:mode #:code</em><a href='#index-make_002dtrailing_002dslash_002dhandler_002dwrapper' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a handler wrapper that normalizes trailing slashes.
</p>
<p>MODE is either &rsquo;strip (default) or &rsquo;append: &rsquo;strip — redirect /foo/ to
/foo &rsquo;append — redirect /foo to /foo/
</p>
<p>The root path / is always left alone.
</p>
<p>CODE is the HTTP status code for the redirect (default 301).
</p>
<p>Use with wrap-routes: (wrap-routes routes
(make-trailing-slash-handler-wrapper #:mode &rsquo;append))
</p>
</dd></dl>
<dl class="def">
<dt id="index-trailing_002dslash_002dhandler_002dwrapper"><span class="category">Procedure: </span><span><strong>trailing-slash-handler-wrapper</strong> <em>handler KEY: #:mode #:code</em><a href='#index-trailing_002dslash_002dhandler_002dwrapper' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Handler wrapper that normalizes trailing slashes in request paths.
</p>
<p>MODE is either &rsquo;strip (default) or &rsquo;append: &rsquo;strip — redirect /foo/ to
/foo &rsquo;append — redirect /foo to /foo/
</p>
<p>The root path / is always left alone.
</p>
<p>CODE is the HTTP status code for the redirect (default 301).
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005fparams">
<div class="header">
<p>
Next: <a href="#safsaf_005fresponse_002dhelpers" accesskey="n" rel="next">(safsaf response-helpers)</a>, Previous: <a href="#safsaf_005fhandler_002dwrappers_005ftrailing_002dslash" accesskey="p" rel="prev">(safsaf handler-wrappers trailing-slash)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-params_0029"></span><h3 class="section">2.10 (safsaf params)</h3>
<ul class="section-toc">
<li><a href="#Macros" accesskey="1">Macros</a></li>
<li><a href="#Procedures-9" accesskey="2">Procedures</a></li>
<li><a href="#Record-Types" accesskey="3">Record Types</a></li>
</ul>
<div class="subsection" id="Macros">
<h4 class="subsection">2.10.1 Macros</h4>
<dl class="def">
<dt id="index-invalid_002dparam_002dmessage"><span class="category">Macro: </span><span><strong>invalid-param-message</strong> <em>x</em><a href='#index-invalid_002dparam_002dmessage' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Undocumented macro.
</p>
</dd></dl>
<dl class="def">
<dt id="index-invalid_002dparam_002dvalue"><span class="category">Macro: </span><span><strong>invalid-param-value</strong> <em>x</em><a href='#index-invalid_002dparam_002dvalue' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Undocumented macro.
</p>
</dd></dl>
<dl class="def">
<dt id="index-invalid_002dparam_003f"><span class="category">Macro: </span><span><strong>invalid-param?</strong> <em>x</em><a href='#index-invalid_002dparam_003f' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Undocumented macro.
</p>
</dd></dl>
<dl class="def">
<dt id="index-make_002dinvalid_002dparam"><span class="category">Macro: </span><span><strong>make-invalid-param</strong> <em>x</em><a href='#index-make_002dinvalid_002dparam' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Undocumented macro.
</p>
</dd></dl>
</div>
<div class="subsection" id="Procedures-9">
<h4 class="subsection">2.10.2 Procedures</h4>
<dl class="def">
<dt id="index-any_002dinvalid_002dparams_003f"><span class="category">Procedure: </span><span><strong>any-invalid-params?</strong> <em>parsed-params</em><a href='#index-any_002dinvalid_002dparams_003f' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return #t if any values in PARSED-PARAMS are invalid.
</p>
</dd></dl>
<dl class="def">
<dt id="index-as_002dcheckbox"><span class="category">Procedure: </span><span><strong>as-checkbox</strong> <em>s</em><a href='#index-as_002dcheckbox' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Undocumented procedure.
</p>
</dd></dl>
<dl class="def">
<dt id="index-as_002dinteger"><span class="category">Procedure: </span><span><strong>as-integer</strong> <em>s</em><a href='#index-as_002dinteger' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Undocumented procedure.
</p>
</dd></dl>
<dl class="def">
<dt id="index-as_002dmatching"><span class="category">Procedure: </span><span><strong>as-matching</strong> <em>regex KEY: #:message</em><a href='#index-as_002dmatching' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a processor that accepts values matching REGEX.
</p>
</dd></dl>
<dl class="def">
<dt id="index-as_002dnumber"><span class="category">Procedure: </span><span><strong>as-number</strong> <em>s</em><a href='#index-as_002dnumber' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Undocumented procedure.
</p>
</dd></dl>
<dl class="def">
<dt id="index-as_002done_002dof"><span class="category">Procedure: </span><span><strong>as-one-of</strong> <em>choices KEY: #:message</em><a href='#index-as_002done_002dof' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a processor that accepts only values in CHOICES (a list of
strings).
</p>
</dd></dl>
<dl class="def">
<dt id="index-as_002dpredicate"><span class="category">Procedure: </span><span><strong>as-predicate</strong> <em>pred KEY: #:message</em><a href='#index-as_002dpredicate' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a processor that accepts values for which PRED returns true.
</p>
</dd></dl>
<dl class="def">
<dt id="index-as_002dstring"><span class="category">Procedure: </span><span><strong>as-string</strong> <em>x</em><a href='#index-as_002dstring' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Undocumented procedure.
</p>
</dd></dl>
<dl class="def">
<dt id="index-field_002derrors"><span class="category">Procedure: </span><span><strong>field-errors</strong> <em>parsed-params name</em><a href='#index-field_002derrors' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a list of error message strings for NAME, or &rsquo;(). Convenient for
rendering form fields with per-field errors.
</p>
</dd></dl>
<dl class="def">
<dt id="index-guard_002dagainst_002dmutually_002dexclusive_002dparams"><span class="category">Procedure: </span><span><strong>guard-against-mutually-exclusive-params</strong> <em>parsed-params groups</em><a href='#index-guard_002dagainst_002dmutually_002dexclusive_002dparams' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Check PARSED-PARAMS for mutually exclusive parameter groups. GROUPS is
a list of lists of symbols, e.g. &rsquo;((limit_results all_results)). If
parameters from the same group co-occur, the later ones are replaced
with &lt;invalid-param&gt; records.
</p>
</dd></dl>
<dl class="def">
<dt id="index-invalid_002dparam_002dref"><span class="category">Procedure: </span><span><strong>invalid-param-ref</strong> <em>parsed-params name</em><a href='#index-invalid_002dparam_002dref' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return the &lt;invalid-param&gt; record for NAME, or #f if valid or absent.
</p>
</dd></dl>
<dl class="def">
<dt id="index-params_002d_003equery_002dstring"><span class="category">Procedure: </span><span><strong>params-&gt;query-string</strong> <em>parsed-params</em><a href='#index-params_002d_003equery_002dstring' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Serialize PARSED-PARAMS back to a URI query string. Skips invalid
params. Handles multi-value (list) entries. Useful for building
pagination links that preserve current filters.
</p>
</dd></dl>
<dl class="def">
<dt id="index-parse_002dform_002dparams"><span class="category">Procedure: </span><span><strong>parse-form-params</strong> <em>param-specs raw-params KEY: #:csrf-field</em><a href='#index-parse_002dform_002dparams' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Like parse-params but prepends a CSRF token check. Uses
current-csrf-token from (safsaf handler-wrappers csrf).
</p>
</dd></dl>
<dl class="def">
<dt id="index-parse_002dparams"><span class="category">Procedure: </span><span><strong>parse-params</strong> <em>param-specs raw-params</em><a href='#index-parse_002dparams' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Parse and transform parameters from RAW-PARAMS according to PARAM-SPECS.
</p>
<p>RAW-PARAMS is an alist of (string . string) pairs, as returned by
parse-query-string or parse-form-body.
</p>
<p>PARAM-SPECS is a list of specifications. Each spec is a list whose
first element is the parameter name (a symbol), second is a processor
procedure (string -&gt; value | &lt;invalid-param&gt;), and the rest are keyword
options:
</p>
<p>(name processor) ; optional (name processor #:required) ; must be
present (name processor #:default value) ; fallback (name processor
#:multi-value) ; collect all occurrences (name processor #:multi-value
#:default value) ; multi-value with fallback (name processor
#:no-default-when (fields) #:default value) ; conditional default
</p>
<p>Returns an alist of (symbol . value) pairs. Values that fail
validation appear as &lt;invalid-param&gt; records inline. Missing optional
params without defaults are omitted.
</p>
</dd></dl>
</div>
<div class="subsection" id="Record-Types">
<h4 class="subsection">2.10.3 Record Types</h4>
<dl class="def">
<dt id="index-_003cinvalid_002dparam_003e"><span class="category">Record type: </span><span><strong>&lt;invalid-param&gt;</strong><a href='#index-_003cinvalid_002dparam_003e' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>This record type has the following fields:
</p>
<ul>
<li> <code>value</code></li><li> <code>message</code></li></ul>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005fresponse_002dhelpers">
<div class="header">
<p>
Next: <a href="#safsaf_005frouter" accesskey="n" rel="next">(safsaf router)</a>, Previous: <a href="#safsaf_005fparams" accesskey="p" rel="prev">(safsaf params)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-response_002dhelpers_0029"></span><h3 class="section">2.11 (safsaf response-helpers)</h3>
<ul class="section-toc">
<li><a href="#Procedures-10" accesskey="1">Procedures</a></li>
</ul>
<div class="subsection" id="Procedures-10">
<h4 class="subsection">2.11.1 Procedures</h4>
<dl class="def">
<dt id="index-bad_002drequest_002dresponse"><span class="category">Procedure: </span><span><strong>bad-request-response</strong> <em>OPT: body KEY: #:headers</em><a href='#index-bad_002drequest_002dresponse' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a 400 Bad Request response.
</p>
</dd></dl>
<dl class="def">
<dt id="index-build_002dresponse_002finherit"><span class="category">Procedure: </span><span><strong>build-response/inherit</strong> <em>response KEY: #:headers</em><a href='#index-build_002dresponse_002finherit' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Build a new response based on RESPONSE, preserving its version, status
code, and reason phrase. HEADERS defaults to the existing headers;
override it to modify them.
</p>
<p>Use this in handler wrappers that need to adjust headers on an inner
handler&rsquo;s response without losing any response fields.
</p>
</dd></dl>
<dl class="def">
<dt id="index-delete_002dcookie_002dheader"><span class="category">Procedure: </span><span><strong>delete-cookie-header</strong> <em>name</em><a href='#index-delete_002dcookie_002dheader' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a Set-Cookie header pair that expires cookie NAME. Wraps
(webutils cookie) delete-cookie.
</p>
</dd></dl>
<dl class="def">
<dt id="index-forbidden_002dresponse"><span class="category">Procedure: </span><span><strong>forbidden-response</strong> <em>OPT: body KEY: #:headers</em><a href='#index-forbidden_002dresponse' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a 403 Forbidden response.
</p>
</dd></dl>
<dl class="def">
<dt id="index-html_002dresponse"><span class="category">Procedure: </span><span><strong>html-response</strong> <em>shtml KEY: #:code #:headers #:charset</em><a href='#index-html_002dresponse' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return an HTML response by streaming SHTML to the client. SHTML is an
SXML/SHTML tree as accepted by write-shtml-as-html. CHARSET defaults to
&quot;utf-8&quot;.
</p>
</dd></dl>
<dl class="def">
<dt id="index-internal_002dserver_002derror_002dresponse"><span class="category">Procedure: </span><span><strong>internal-server-error-response</strong> <em>OPT: body KEY: #:headers</em><a href='#index-internal_002dserver_002derror_002dresponse' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a 500 Internal Server Error response.
</p>
</dd></dl>
<dl class="def">
<dt id="index-json_002dresponse"><span class="category">Procedure: </span><span><strong>json-response</strong> <em>str KEY: #:code #:headers</em><a href='#index-json_002dresponse' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a JSON response. STR is the JSON string to send.
</p>
</dd></dl>
<dl class="def">
<dt id="index-list_002d_003estreaming_002djson_002darray"><span class="category">Procedure: </span><span><strong>list-&gt;streaming-json-array</strong> <em>proc lst port KEY: #:unicode</em><a href='#index-list_002d_003estreaming_002djson_002darray' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Write LST as a JSON array to PORT, applying PROC to each element to
produce a JSON-serializable value. Each element is written individually
via scm-&gt;json so the entire array need not be held in memory.
</p>
</dd></dl>
<dl class="def">
<dt id="index-make_002dstatic_002dhandler"><span class="category">Procedure: </span><span><strong>make-static-handler</strong> <em>root-dir KEY: #:cache-control</em><a href='#index-make_002dstatic_002dhandler' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a handler that serves static files from ROOT-DIR.
</p>
<p>The handler expects route params to contain a wildcard capture (the file
path segments). Use with a wildcard route:
</p>
<p>(route &rsquo;GET &rsquo;(. path) (make-static-handler &quot;/path/to/public&quot;))
</p>
<p>Supports If-Modified-Since for 304 responses. CACHE-CONTROL, if given,
is a Cache-Control value in Guile&rsquo;s header format — an alist, e.g.
&rsquo;((max-age . 3600)) or &rsquo;((no-cache)).
</p>
<p>Works with /gnu/store paths: files with a very low mtime (as produced by
the store&rsquo;s timestamp normalization) use the process start time as
Last-Modified instead, so that conditional requests behave sensibly.
</p>
</dd></dl>
<dl class="def">
<dt id="index-negotiate_002dcontent_002dtype"><span class="category">Procedure: </span><span><strong>negotiate-content-type</strong> <em>request OPT: supported KEY: #:extensions</em><a href='#index-negotiate_002dcontent_002dtype' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return the most appropriate MIME type symbol for REQUEST from SUPPORTED.
</p>
<p>Checks the URL path extension first (.json, .html, .txt) — if present
and the implied type is in SUPPORTED, it wins. Otherwise, walks the
Accept header and returns the first type that appears in SUPPORTED.
Falls back to the first element of SUPPORTED if nothing matches.
</p>
<p>EXTENSIONS is an alist mapping file extension strings to MIME type
symbols, used for path-based negotiation. Defaults to
%negotiation-extensions.
</p>
</dd></dl>
<dl class="def">
<dt id="index-not_002dfound_002dresponse"><span class="category">Procedure: </span><span><strong>not-found-response</strong> <em>OPT: body KEY: #:headers</em><a href='#index-not_002dfound_002dresponse' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a 404 Not Found response.
</p>
</dd></dl>
<dl class="def">
<dt id="index-payload_002dtoo_002dlarge_002dresponse"><span class="category">Procedure: </span><span><strong>payload-too-large-response</strong> <em>OPT: body KEY: #:headers</em><a href='#index-payload_002dtoo_002dlarge_002dresponse' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a 413 Payload Too Large response.
</p>
</dd></dl>
<dl class="def">
<dt id="index-redirect_002dresponse"><span class="category">Procedure: </span><span><strong>redirect-response</strong> <em>path KEY: #:code #:headers</em><a href='#index-redirect_002dresponse' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a redirect response to PATH (a string).
</p>
</dd></dl>
<dl class="def">
<dt id="index-scm_002dalist_002d_003estreaming_002djson"><span class="category">Procedure: </span><span><strong>scm-alist-&gt;streaming-json</strong> <em>alist port KEY: #:unicode</em><a href='#index-scm_002dalist_002d_003estreaming_002djson' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Write ALIST as a JSON object to PORT, streaming each value as it is
produced. If a value in the alist is a procedure, it is called with
PORT so it can write its own JSON representation directly. Otherwise
the value is serialized via scm-&gt;json.
</p>
</dd></dl>
<dl class="def">
<dt id="index-set_002dcookie_002dheader"><span class="category">Procedure: </span><span><strong>set-cookie-header</strong> <em>name value KEY: #:path #:domain #:max-age #:secure #:http-only #:expires</em><a href='#index-set_002dcookie_002dheader' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a Set-Cookie header pair suitable for inclusion in a response
headers alist. Wraps (webutils cookie) set-cookie.
</p>
<p>Example: (values (build-response #:headers (list (set-cookie-header
&quot;session&quot; token #:path &quot;/&quot; #:http-only #t #:secure #t))) &quot;ok&quot;)
</p>
</dd></dl>
<dl class="def">
<dt id="index-streaming_002djson_002dresponse"><span class="category">Procedure: </span><span><strong>streaming-json-response</strong> <em>thunk KEY: #:code #:headers</em><a href='#index-streaming_002djson_002dresponse' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a JSON response whose body is written incrementally by THUNK.
THUNK is a procedure of one argument (the output port). Use
scm-alist-&gt;streaming-json and list-&gt;streaming-json-array inside THUNK to
write JSON without materializing the entire response in memory.
</p>
</dd></dl>
<dl class="def">
<dt id="index-text_002dresponse"><span class="category">Procedure: </span><span><strong>text-response</strong> <em>str KEY: #:code #:headers</em><a href='#index-text_002dresponse' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return a plain text response. STR is the text string to send.
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005frouter">
<div class="header">
<p>
Next: <a href="#safsaf_005ftemplating" accesskey="n" rel="next">(safsaf templating)</a>, Previous: <a href="#safsaf_005fresponse_002dhelpers" accesskey="p" rel="prev">(safsaf response-helpers)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-router_0029"></span><h3 class="section">2.12 (safsaf router)</h3>
<ul class="section-toc">
<li><a href="#Macros-1" accesskey="1">Macros</a></li>
<li><a href="#Parameters-2" accesskey="2">Parameters</a></li>
<li><a href="#Procedures-11" accesskey="3">Procedures</a></li>
</ul>
<div class="subsection" id="Macros-1">
<h4 class="subsection">2.12.1 Macros</h4>
<dl class="def">
<dt id="index-compiled_002droute_002dhandler"><span class="category">Macro: </span><span><strong>compiled-route-handler</strong> <em>x</em><a href='#index-compiled_002droute_002dhandler' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Undocumented macro.
</p>
</dd></dl>
<dl class="def">
<dt id="index-route_002dgroup_002dchildren"><span class="category">Macro: </span><span><strong>route-group-children</strong> <em>x</em><a href='#index-route_002dgroup_002dchildren' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return the list of child routes and groups of ROUTE-GROUP.
</p>
</dd></dl>
<dl class="def">
<dt id="index-route_002dgroup_002dname"><span class="category">Macro: </span><span><strong>route-group-name</strong> <em>x</em><a href='#index-route_002dgroup_002dname' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return the name of ROUTE-GROUP, or <code>#f</code> if unnamed.
</p>
</dd></dl>
<dl class="def">
<dt id="index-route_002dgroup_002dprefix"><span class="category">Macro: </span><span><strong>route-group-prefix</strong> <em>x</em><a href='#index-route_002dgroup_002dprefix' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return the prefix pattern of ROUTE-GROUP.
</p>
</dd></dl>
<dl class="def">
<dt id="index-route_002dgroup_003f"><span class="category">Macro: </span><span><strong>route-group?</strong> <em>x</em><a href='#index-route_002dgroup_003f' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return <code>#t</code> if OBJ is a <code>&lt;route-group&gt;</code>.
</p>
</dd></dl>
<dl class="def">
<dt id="index-route_002dhandler"><span class="category">Macro: </span><span><strong>route-handler</strong> <em>x</em><a href='#index-route_002dhandler' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return the handler procedure of ROUTE.
</p>
</dd></dl>
<dl class="def">
<dt id="index-route_002dmethod"><span class="category">Macro: </span><span><strong>route-method</strong> <em>x</em><a href='#index-route_002dmethod' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return the HTTP method of ROUTE.
</p>
</dd></dl>
<dl class="def">
<dt id="index-route_002dname"><span class="category">Macro: </span><span><strong>route-name</strong> <em>x</em><a href='#index-route_002dname' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return the name of ROUTE, or <code>#f</code> if unnamed.
</p>
</dd></dl>
<dl class="def">
<dt id="index-route_002dpattern"><span class="category">Macro: </span><span><strong>route-pattern</strong> <em>x</em><a href='#index-route_002dpattern' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return the URL pattern of ROUTE.
</p>
</dd></dl>
<dl class="def">
<dt id="index-route_003f"><span class="category">Macro: </span><span><strong>route?</strong> <em>x</em><a href='#index-route_003f' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return <code>#t</code> if OBJ is a <code>&lt;route&gt;</code>.
</p>
</dd></dl>
</div>
<div class="subsection" id="Parameters-2">
<h4 class="subsection">2.12.2 Parameters</h4>
<dl class="def">
<dt id="index-current_002dreverse_002droutes"><span class="category">Parameter: </span><span><strong>current-reverse-routes</strong><a href='#index-current_002dreverse_002droutes' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Default value:
</p>
<pre class="verbatim">#f
</pre>
</dd></dl>
<dl class="def">
<dt id="index-current_002droute_002dparams"><span class="category">Parameter: </span><span><strong>current-route-params</strong><a href='#index-current_002droute_002dparams' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Default value:
</p>
<pre class="verbatim">()
</pre>
</dd></dl>
</div>
<div class="subsection" id="Procedures-11">
<h4 class="subsection">2.12.3 Procedures</h4>
<dl class="def">
<dt id="index-compile_002droutes"><span class="category">Procedure: </span><span><strong>compile-routes</strong> <em>routes</em><a href='#index-compile_002droutes' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Compile a route tree (route, route-group, or list) into two values: 1.
An ordered list of &lt;compiled-route&gt; records ready for matching. 2. A
&lt;reverse-routes&gt; record for use with path-for.
</p>
<p>The last route must be a catch-all (&rsquo;* pattern with a rest parameter) so
that every request is handled.
</p>
</dd></dl>
<dl class="def">
<dt id="index-find_002dallowed_002dmethods"><span class="category">Procedure: </span><span><strong>find-allowed-methods</strong> <em>compiled-routes path-segments</em><a href='#index-find_002dallowed_002dmethods' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Scan COMPILED-ROUTES for routes whose path matches PATH-SEGMENTS,
collecting their HTTP methods. The last route (the catch-all) is
excluded. Returns a deduplicated list of method symbols, or &rsquo;() if no
route&rsquo;s path matches.
</p>
</dd></dl>
<dl class="def">
<dt id="index-make_002droute_002dgroup"><span class="category">Procedure: </span><span><strong>make-route-group</strong> <em>prefix KEY: #:name</em><a href='#index-make_002droute_002dgroup' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Create an empty route group with PREFIX. Children can be added later
with route-group-add-children!.
</p>
</dd></dl>
<dl class="def">
<dt id="index-match_002droute"><span class="category">Procedure: </span><span><strong>match-route</strong> <em>compiled-routes method path-segments</em><a href='#index-match_002droute' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Find the first matching route for METHOD and PATH-SEGMENTS. Returns
(values handler bindings) on match, or (values #f #f) on no match.
</p>
</dd></dl>
<dl class="def">
<dt id="index-path_002dfor"><span class="category">Procedure: </span><span><strong>path-for</strong> <em>group name OPT: params KEY: #:query #:fragment #:relative?</em><a href='#index-path_002dfor' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Generate a URL path for a named route within GROUP.
</p>
<p>GROUP is a route-group value. NAME is either a symbol naming a route
within GROUP, or a list of symbols for nested lookup where the last
element is the route name and preceding elements are child group names.
</p>
<p>(path-for routes &rsquo;users) (path-for routes &rsquo;user &rsquo;((id . &quot;42&quot;)))
(path-for routes &rsquo;(api items) &rsquo;((id . &quot;7&quot;)))
</p>
<p>PARAMS is an alist mapping capture symbols to string values, or to a
list of strings for rest parameters.
</p>
<p>Optional keyword arguments: #:query — alist of query parameters ((key .
value) ...) #:fragment — fragment string (without the leading #)
#:relative? — if #t, omit the leading /
</p>
</dd></dl>
<dl class="def">
<dt id="index-route"><span class="category">Procedure: </span><span><strong>route</strong> <em>method pattern handler KEY: #:name</em><a href='#index-route' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Create a route. METHOD is a symbol, list of symbols, or &rsquo;* for any.
PATTERN is a list of segments: strings (literal), symbols (capture),
two-element lists (predicate capture: (proc name)), with optional dotted
tail (wildcard capture). HANDLER is a procedure (request body-port) -&gt;
(values response body). NAME is an optional symbol used for reverse
routing with path-for.
</p>
</dd></dl>
<dl class="def">
<dt id="index-route_002dgroup"><span class="category">Procedure: </span><span><strong>route-group</strong> <em>prefix KEY: #:name . children</em><a href='#index-route_002dgroup' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Create a route group. PREFIX is a pattern list (same syntax as route
patterns). CHILDREN is an ordered list of routes and route-groups. NAME
is an optional symbol for nested path-for lookups.
</p>
</dd></dl>
<dl class="def">
<dt id="index-route_002dgroup_002dadd_002dchildren_0021"><span class="category">Procedure: </span><span><strong>route-group-add-children!</strong> <em>group new-children</em><a href='#index-route_002dgroup_002dadd_002dchildren_0021' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Append NEW-CHILDREN to GROUP&rsquo;s child list.
</p>
</dd></dl>
<dl class="def">
<dt id="index-wrap_002droutes"><span class="category">Procedure: </span><span><strong>wrap-routes</strong> <em>routes . wrappers</em><a href='#index-wrap_002droutes' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Apply WRAPPERS to every handler in ROUTES, which may be a route,
route-group, or list of either. Returns a new structure with wrapped
handlers. When multiple wrappers are given, the first wrapper in the
list wraps outermost (runs first on the request, last on the response).
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005ftemplating">
<div class="header">
<p>
Next: <a href="#safsaf_005futils" accesskey="n" rel="next">(safsaf utils)</a>, Previous: <a href="#safsaf_005frouter" accesskey="p" rel="prev">(safsaf router)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-templating_0029"></span><h3 class="section">2.13 (safsaf templating)</h3>
<ul class="section-toc">
<li><a href="#Procedures-12" accesskey="1">Procedures</a></li>
</ul>
<div class="subsection" id="Procedures-12">
<h4 class="subsection">2.13.1 Procedures</h4>
<dl class="def">
<dt id="index-streaming_002dhtml_002dresponse"><span class="category">Procedure: </span><span><strong>streaming-html-response</strong> <em>shtml KEY: #:code #:headers #:charset</em><a href='#index-streaming_002dhtml_002dresponse' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return an HTML response that streams SHTML to the client.
</p>
<p>SHTML is an SHTML tree that may contain procedures. Each procedure is
called as <code>(proc port)</code> during output and should write HTML to the
port. Static parts are rendered via htmlprag.
</p>
<div class="example">
<pre class="example"> (streaming-response
`(*TOP*
(*DECL* DOCTYPE html)
(html (head (title &quot;My Page&quot;))
(body (h1 &quot;Hello&quot;)
,(lambda (port)
(write-shtml-as-html '(p &quot;dynamic&quot;) port))))))
</pre></div>
</dd></dl>
<dl class="def">
<dt id="index-write_002dshtml_002das_002dhtml_002fstreaming"><span class="category">Procedure: </span><span><strong>write-shtml-as-html/streaming</strong> <em>shtml port</em><a href='#index-write_002dshtml_002das_002dhtml_002fstreaming' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Write SHTML to PORT, like <code>write-shtml-as-html</code> from htmlprag, but
any procedure encountered in the tree is called as <code>(proc port)</code>
and may write directly to PORT.
</p>
<p>This allows mixing static SHTML with dynamic streaming sections:
</p>
<div class="example">
<pre class="example"> (write-shtml-as-html/streaming
`(html (body (h1 &quot;Title&quot;)
,(lambda (port) (display &quot;dynamic&quot; port))
(footer &quot;bye&quot;)))
port)
</pre></div>
<p>Static parts are rendered via htmlprag&rsquo;s <code>shtml-&gt;html</code>, then
interleaved with procedure calls at output time.
</p>
</dd></dl>
<hr>
</div>
</div>
<div class="section" id="safsaf_005futils">
<div class="header">
<p>
Previous: <a href="#safsaf_005ftemplating" accesskey="p" rel="prev">(safsaf templating)</a>, Up: <a href="#API" accesskey="u" rel="up">API</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="g_t_0028safsaf-utils_0029"></span><h3 class="section">2.14 (safsaf utils)</h3>
<ul class="section-toc">
<li><a href="#Procedures-13" accesskey="1">Procedures</a></li>
</ul>
<div class="subsection" id="Procedures-13">
<h4 class="subsection">2.14.1 Procedures</h4>
<dl class="def">
<dt id="index-multipart_002dtext_002dfields"><span class="category">Procedure: </span><span><strong>multipart-text-fields</strong> <em>parts</em><a href='#index-multipart_002dtext_002dfields' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Extract text fields from multipart PARTS as an alist of (name . value).
File upload parts (those with a filename parameter) are excluded.
</p>
</dd></dl>
<dl class="def">
<dt id="index-parse_002dform_002dbody"><span class="category">Procedure: </span><span><strong>parse-form-body</strong> <em>request body-port</em><a href='#index-parse_002dform_002dbody' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Read and parse a URL-encoded form body from REQUEST. Returns an alist
of string key-value pairs.
</p>
</dd></dl>
<dl class="def">
<dt id="index-parse_002dmultipart_002dbody"><span class="category">Procedure: </span><span><strong>parse-multipart-body</strong> <em>request body-port</em><a href='#index-parse_002dmultipart_002dbody' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Read and parse a multipart/form-data body from REQUEST. Returns a list
of &lt;part&gt; records from (webutils multipart). Use parts-ref,
parts-ref-string, part-body, etc. to access parts.
</p>
</dd></dl>
<dl class="def">
<dt id="index-parse_002dquery_002dstring"><span class="category">Procedure: </span><span><strong>parse-query-string</strong> <em>request</em><a href='#index-parse_002dquery_002dstring' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Parse the query string from REQUEST. Returns an alist of string
key-value pairs, or &rsquo;() if no query string.
</p>
</dd></dl>
<dl class="def">
<dt id="index-request_002dcookie_002dref"><span class="category">Procedure: </span><span><strong>request-cookie-ref</strong> <em>request name OPT: default</em><a href='#index-request_002dcookie_002dref' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return the value of cookie NAME from REQUEST, or DEFAULT if not found.
</p>
</dd></dl>
<dl class="def">
<dt id="index-request_002dcookies"><span class="category">Procedure: </span><span><strong>request-cookies</strong> <em>request</em><a href='#index-request_002dcookies' class='copiable-anchor'> &para;</a></span></dt>
<dd><p>Return the cookies from REQUEST as an alist of (name . value) pairs.
Returns &rsquo;() if no Cookie header is present. Importing (webutils cookie)
registers the Cookie header parser with (web http).
</p>
</dd></dl>
<hr>
</div>
</div>
</div>
<div class="appendix" id="Version-History">
<div class="header">
<p>
Next: <a href="#Copying-Information" accesskey="n" rel="next">Copying Information</a>, Previous: <a href="#API" accesskey="p" rel="prev">API</a>, Up: <a href="#Top" accesskey="u" rel="up">Overview</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Version-History-1"></span><h2 class="appendix">Appendix A Version History</h2>
<dl compact="compact">
<dt><span><em>Version 0.1</em></span></dt>
<dd><ul>
<li> Initial release.
</li><li> Built on the code of the Guix Data Serivce, plus other web
services like the Guix Build Coordinator and Nar Herder.
</li><li> Written using Claude Opus 4.6 using Claude Code.
</li></ul>
</dd>
</dl>
<hr>
</div>
<div class="appendix" id="Copying-Information">
<div class="header">
<p>
Next: <a href="#Concept-Index" accesskey="n" rel="next">Concept Index</a>, Previous: <a href="#Version-History" accesskey="p" rel="prev">Version History</a>, Up: <a href="#Top" accesskey="u" rel="up">Overview</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Copying-Information-1"></span><h2 class="appendix">Appendix B Copying Information</h2>
<p>Copyright &copy; 2026 Christopher Baines &lt;mail@cbaines.net&gt;
</p>
<p>This library is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 3 of the
License, or (at your option) any later version.
</p>
<hr>
</div>
<div class="unnumbered" id="Concept-Index">
<div class="header">
<p>
Next: <a href="#Data-Type-Index" accesskey="n" rel="next">Data Type Index</a>, Previous: <a href="#Copying-Information" accesskey="p" rel="prev">Copying Information</a>, Up: <a href="#Top" accesskey="u" rel="up">Overview</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Concept-Index-1"></span><h2 class="unnumbered">Concept Index</h2>
<hr>
</div>
<div class="unnumbered" id="Data-Type-Index">
<div class="header">
<p>
Next: <a href="#Procedure-Index" accesskey="n" rel="next">Procedure Index</a>, Previous: <a href="#Concept-Index" accesskey="p" rel="prev">Concept Index</a>, Up: <a href="#Top" accesskey="u" rel="up">Overview</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Data-Type-Index-1"></span><h2 class="unnumbered">Data Type Index</h2>
<table><tr><th valign="top">Jump to: &nbsp; </th><td><a class="summary-letter" href="#Data-Type-Index_tp_symbol-1"><b>&lt;</b></a>
</td></tr></table>
<table class="index-tp" border="0">
<tr><td></td><th align="left">Index Entry</th><td>&nbsp;</td><th align="left"> Section</th></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Data-Type-Index_tp_symbol-1">&lt;</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-_003cinvalid_002dparam_003e"><code>&lt;invalid-param&gt;</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
</table>
<table><tr><th valign="top">Jump to: &nbsp; </th><td><a class="summary-letter" href="#Data-Type-Index_tp_symbol-1"><b>&lt;</b></a>
</td></tr></table>
<hr>
</div>
<div class="unnumbered" id="Procedure-Index">
<div class="header">
<p>
Next: <a href="#Variable-Index" accesskey="n" rel="next">Variable Index</a>, Previous: <a href="#Data-Type-Index" accesskey="p" rel="prev">Data Type Index</a>, Up: <a href="#Top" accesskey="u" rel="up">Overview</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Procedure-Index-1"></span><h2 class="unnumbered">Procedure Index</h2>
<table><tr><th valign="top">Jump to: &nbsp; </th><td><a class="summary-letter" href="#Procedure-Index_fn_letter-A"><b>A</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-B"><b>B</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-C"><b>C</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-D"><b>D</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-E"><b>E</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-F"><b>F</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-G"><b>G</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-H"><b>H</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-I"><b>I</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-J"><b>J</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-L"><b>L</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-M"><b>M</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-N"><b>N</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-P"><b>P</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-R"><b>R</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-S"><b>S</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-T"><b>T</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-W"><b>W</b></a>
&nbsp;
</td></tr></table>
<table class="index-fn" border="0">
<tr><td></td><th align="left">Index Entry</th><td>&nbsp;</td><th align="left"> Section</th></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-A">A</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-any_002dinvalid_002dparams_003f"><code>any-invalid-params?</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-as_002dcheckbox"><code>as-checkbox</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-as_002dinteger"><code>as-integer</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-as_002dmatching"><code>as-matching</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-as_002dnumber"><code>as-number</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-as_002done_002dof"><code>as-one-of</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-as_002dpredicate"><code>as-predicate</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-as_002dstring"><code>as-string</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-B">B</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-bad_002drequest_002dresponse"><code>bad-request-response</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-build_002dresponse_002finherit"><code>build-response/inherit</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-C">C</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-compile_002droutes"><code>compile-routes</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-compiled_002droute_002dhandler"><code>compiled-route-handler</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-cors_002dhandler_002dwrapper"><code>cors-handler-wrapper</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fcors">safsaf_handler-wrappers_cors</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-csrf_002dhandler_002dwrapper"><code>csrf-handler-wrapper</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fcsrf">safsaf_handler-wrappers_csrf</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-csrf_002dtoken_002dfield"><code>csrf-token-field</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fcsrf">safsaf_handler-wrappers_csrf</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-D">D</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-default_002dmethod_002dnot_002dallowed_002dhandler"><code>default-method-not-allowed-handler</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf">safsaf</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-default_002drender_002derror"><code>default-render-error</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fexceptions">safsaf_handler-wrappers_exceptions</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-default_002drender_002dhtml"><code>default-render-html</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fexceptions">safsaf_handler-wrappers_exceptions</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-default_002drender_002djson"><code>default-render-json</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fexceptions">safsaf_handler-wrappers_exceptions</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-delete_002dcookie_002dheader"><code>delete-cookie-header</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-E">E</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-exceptions_002dhandler_002dwrapper"><code>exceptions-handler-wrapper</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fexceptions">safsaf_handler-wrappers_exceptions</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-F">F</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-field_002derrors"><code>field-errors</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-find_002dallowed_002dmethods"><code>find-allowed-methods</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-forbidden_002dresponse"><code>forbidden-response</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-G">G</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-guard_002dagainst_002dmutually_002dexclusive_002dparams"><code>guard-against-mutually-exclusive-params</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-H">H</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-html_002dresponse"><code>html-response</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-I">I</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-internal_002dserver_002derror_002dresponse"><code>internal-server-error-response</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-invalid_002dparam_002dmessage"><code>invalid-param-message</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-invalid_002dparam_002dref"><code>invalid-param-ref</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-invalid_002dparam_002dvalue"><code>invalid-param-value</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-invalid_002dparam_003f"><code>invalid-param?</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-J">J</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-json_002dresponse"><code>json-response</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-L">L</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-list_002d_003estreaming_002djson_002darray"><code>list-&gt;streaming-json-array</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-logging_002dhandler_002dwrapper"><code>logging-handler-wrapper</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005flogging">safsaf_handler-wrappers_logging</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-M">M</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-make_002dexceptions_002dhandler_002dwrapper"><code>make-exceptions-handler-wrapper</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fexceptions">safsaf_handler-wrappers_exceptions</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-make_002dinvalid_002dparam"><code>make-invalid-param</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-make_002dmax_002dbody_002dsize_002dhandler_002dwrapper"><code>make-max-body-size-handler-wrapper</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fmax_002dbody_002dsize">safsaf_handler-wrappers_max-body-size</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-make_002droute_002dgroup"><code>make-route-group</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-make_002dsession_002dconfig"><code>make-session-config</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fsessions">safsaf_handler-wrappers_sessions</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-make_002dsession_002dhandler_002dwrapper"><code>make-session-handler-wrapper</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fsessions">safsaf_handler-wrappers_sessions</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-make_002dstatic_002dhandler"><code>make-static-handler</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-make_002dtrailing_002dslash_002dhandler_002dwrapper"><code>make-trailing-slash-handler-wrapper</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005ftrailing_002dslash">safsaf_handler-wrappers_trailing-slash</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-match_002droute"><code>match-route</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-multipart_002dtext_002dfields"><code>multipart-text-fields</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005futils">safsaf_utils</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-N">N</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-negotiate_002dcontent_002dtype"><code>negotiate-content-type</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-not_002dfound_002dresponse"><code>not-found-response</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-P">P</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-params_002d_003equery_002dstring"><code>params-&gt;query-string</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-parse_002dform_002dbody"><code>parse-form-body</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005futils">safsaf_utils</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-parse_002dform_002dparams"><code>parse-form-params</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-parse_002dmultipart_002dbody"><code>parse-multipart-body</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005futils">safsaf_utils</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-parse_002dparams"><code>parse-params</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fparams">safsaf_params</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-parse_002dquery_002dstring"><code>parse-query-string</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005futils">safsaf_utils</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-path_002dfor"><code>path-for</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-payload_002dtoo_002dlarge_002dresponse"><code>payload-too-large-response</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-R">R</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-redirect_002dresponse"><code>redirect-response</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-request_002dcookie_002dref"><code>request-cookie-ref</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005futils">safsaf_utils</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-request_002dcookies"><code>request-cookies</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005futils">safsaf_utils</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route"><code>route</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route_002dgroup"><code>route-group</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route_002dgroup_002dadd_002dchildren_0021"><code>route-group-add-children!</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route_002dgroup_002dchildren"><code>route-group-children</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route_002dgroup_002dname"><code>route-group-name</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route_002dgroup_002dprefix"><code>route-group-prefix</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route_002dgroup_003f"><code>route-group?</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route_002dhandler"><code>route-handler</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route_002dmethod"><code>route-method</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route_002dname"><code>route-name</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route_002dpattern"><code>route-pattern</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-route_003f"><code>route?</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-run_002dsafsaf"><code>run-safsaf</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf">safsaf</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-S">S</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-scm_002dalist_002d_003estreaming_002djson"><code>scm-alist-&gt;streaming-json</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-security_002dheaders_002dhandler_002dwrapper"><code>security-headers-handler-wrapper</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fsecurity_002dheaders">safsaf_handler-wrappers_security-headers</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-session_002ddelete"><code>session-delete</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fsessions">safsaf_handler-wrappers_sessions</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-session_002dhandler_002dwrapper"><code>session-handler-wrapper</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fsessions">safsaf_handler-wrappers_sessions</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-session_002dset"><code>session-set</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fsessions">safsaf_handler-wrappers_sessions</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-set_002dcookie_002dheader"><code>set-cookie-header</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-streaming_002dhtml_002dresponse"><code>streaming-html-response</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005ftemplating">safsaf_templating</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-streaming_002djson_002dresponse"><code>streaming-json-response</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-T">T</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-text_002dresponse"><code>text-response</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fresponse_002dhelpers">safsaf_response-helpers</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-trailing_002dslash_002dhandler_002dwrapper"><code>trailing-slash-handler-wrapper</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005ftrailing_002dslash">safsaf_handler-wrappers_trailing-slash</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Procedure-Index_fn_letter-W">W</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-wrap_002droutes"><code>wrap-routes</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-write_002dshtml_002das_002dhtml_002fstreaming"><code>write-shtml-as-html/streaming</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005ftemplating">safsaf_templating</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
</table>
<table><tr><th valign="top">Jump to: &nbsp; </th><td><a class="summary-letter" href="#Procedure-Index_fn_letter-A"><b>A</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-B"><b>B</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-C"><b>C</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-D"><b>D</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-E"><b>E</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-F"><b>F</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-G"><b>G</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-H"><b>H</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-I"><b>I</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-J"><b>J</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-L"><b>L</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-M"><b>M</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-N"><b>N</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-P"><b>P</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-R"><b>R</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-S"><b>S</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-T"><b>T</b></a>
&nbsp;
<a class="summary-letter" href="#Procedure-Index_fn_letter-W"><b>W</b></a>
&nbsp;
</td></tr></table>
<hr>
</div>
<div class="unnumbered" id="Variable-Index">
<div class="header">
<p>
Previous: <a href="#Procedure-Index" accesskey="p" rel="prev">Procedure Index</a>, Up: <a href="#Top" accesskey="u" rel="up">Overview</a> &nbsp; [<a href="#SEC_Contents" title="Table of contents" rel="contents">Contents</a>][<a href="#Concept-Index" title="Index" rel="index">Index</a>]</p>
</div>
<span id="Variable-Index-1"></span><h2 class="unnumbered">Variable Index</h2>
<table><tr><th valign="top">Jump to: &nbsp; </th><td><a class="summary-letter" href="#Variable-Index_vr_letter-C"><b>C</b></a>
&nbsp;
</td></tr></table>
<table class="index-vr" border="0">
<tr><td></td><th align="left">Index Entry</th><td>&nbsp;</td><th align="left"> Section</th></tr>
<tr><td colspan="4"> <hr></td></tr>
<tr><th id="Variable-Index_vr_letter-C">C</th><td></td><td></td></tr>
<tr><td></td><td valign="top"><a href="#index-current_002dcsrf_002dtoken"><code>current-csrf-token</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fcsrf">safsaf_handler-wrappers_csrf</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-current_002dreverse_002droutes"><code>current-reverse-routes</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-current_002droute_002dparams"><code>current-route-params</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005frouter">safsaf_router</a></td></tr>
<tr><td></td><td valign="top"><a href="#index-current_002dsession"><code>current-session</code></a>:</td><td>&nbsp;</td><td valign="top"><a href="#safsaf_005fhandler_002dwrappers_005fsessions">safsaf_handler-wrappers_sessions</a></td></tr>
<tr><td colspan="4"> <hr></td></tr>
</table>
<table><tr><th valign="top">Jump to: &nbsp; </th><td><a class="summary-letter" href="#Variable-Index_vr_letter-C"><b>C</b></a>
&nbsp;
</td></tr></table>
</div>
</div>
</body>
</html>