2428 lines
113 KiB
HTML
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> [<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> [<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> [<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><request></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 '("hello" name) hello-page)
|
|
(route '* '* (lambda (request body-port)
|
|
(not-found-response)))))
|
|
|
|
(define (index-page request body-port)
|
|
(html-response '(h1 "Welcome")))
|
|
|
|
(define (hello-page request body-port)
|
|
(let ((name (assoc-ref (current-route-params) 'name)))
|
|
(text-response (string-append "Hello, " name "!"))))
|
|
|
|
(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> [<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 '("about") about-handler)
|
|
|
|
;; Capture: /users/:id
|
|
(route 'GET '("users" id) show-user)
|
|
|
|
;; Predicate: /posts/:id where id is numeric
|
|
(route 'GET '("posts" (,string->number id)) show-post)
|
|
|
|
;; Wildcard (rest): /files/* — captures remaining segments
|
|
(route 'GET '("files" . 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 '("api" "v1")
|
|
(route 'GET '("users") api-list-users)
|
|
(route 'GET '("users" 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 '("posts" 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 . "42")))
|
|
;; => "/posts/42"
|
|
</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> [<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 '("api")
|
|
(route 'GET '("items") 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’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> [<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 "Hello") (p "world")))
|
|
|
|
;; JSON — takes a JSON string
|
|
(json-response (scm->json-string '(("ok" . #t))))
|
|
|
|
;; Plain text
|
|
(text-response "pong")
|
|
|
|
;; Redirect (default 303 See Other)
|
|
(redirect-response "/login")
|
|
(redirect-response "/new-item" #:code 302)
|
|
|
|
;; Error responses
|
|
(not-found-response)
|
|
(bad-request-response "Missing field")
|
|
(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->json-string (item->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> [<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 "username"))
|
|
(password (assoc-ref form "password")))
|
|
(if (valid-credentials? username password)
|
|
(redirect-response "/dashboard")
|
|
(text-response "Invalid login" #: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 "page")) ;; => "2" 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 "avatar")))
|
|
;; form is an alist of text fields
|
|
;; file is a <part> 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 "theme") ;; => "dark" or #f
|
|
|
|
(text-response "ok"
|
|
#:headers (list (set-cookie-header "theme" "dark"
|
|
#:path "/"
|
|
#: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> [<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><invalid-param></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)) ;; => 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> [<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 "my-secret-key"
|
|
#:cookie-name "my-session"))
|
|
|
|
(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 "/"
|
|
#: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 "/"
|
|
#: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> [<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’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 "/")) "Home"))
|
|
(main ,content-proc)
|
|
(footer (p "Footer"))))))
|
|
</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 "Home"
|
|
(lambda (port)
|
|
(write-shtml-as-html
|
|
`(div (h1 "Welcome")
|
|
(p "Content goes here."))
|
|
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> [<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 '("static")
|
|
(route 'GET '(. path)
|
|
(make-static-handler "./public"
|
|
#: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> [<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> [<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'> ¶</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'> ¶</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’s path but not its method receive a 405 response with an Allow
|
|
header. METHOD-NOT-ALLOWED-HANDLER is a procedure (request
|
|
allowed-methods) -> (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> [<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'> ¶</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 “simple” 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 ’("*") 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
|
|
’("*"). 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> [<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'> ¶</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'> ¶</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'> ¶</a></span></dt>
|
|
<dd><p>Return an SXML hidden input element for the CSRF token. Use in forms:
|
|
<code>(csrf-token-field)</code> ⇒ <code>(input (@ (type "hidden")
|
|
...))</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> [<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'> ¶</a></span></dt>
|
|
<dd><p>Return a render-error procedure that content-negotiates between
|
|
RENDER-HTML and RENDER-JSON based on the request’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'> ¶</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'> ¶</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'> ¶</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’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?) -> (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'> ¶</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> [<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'> ¶</a></span></dt>
|
|
<dd><p>Handler wrapper that logs each request and response.
|
|
</p>
|
|
<p>Logs at LEVEL (default ’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> [<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'> ¶</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) -> (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> [<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'> ¶</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.
|
|
"max-age=63072000; includeSubDomains") Cross-Origin-Opener-Policy (e.g.
|
|
"same-origin") Permissions-Policy (e.g. "camera=(), microphone=()")
|
|
Content-Security-Policy (e.g. "default-src ’self’; script-src ’self’")
|
|
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> [<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'> ¶</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'> ¶</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'> ¶</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'> ¶</a></span></dt>
|
|
<dd><p>Return a Set-Cookie header that expires the session cookie. Include in
|
|
a response headers list: (redirect-response "/" #: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'> ¶</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 "/" #:headers (list (session-set manager data)))
|
|
(redirect-response "/" #: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'> ¶</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 "/" #:headers
|
|
(list (session-set manager ’((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> [<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'> ¶</a></span></dt>
|
|
<dd><p>Return a handler wrapper that normalizes trailing slashes.
|
|
</p>
|
|
<p>MODE is either ’strip (default) or ’append: ’strip — redirect /foo/ to
|
|
/foo ’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 ’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'> ¶</a></span></dt>
|
|
<dd><p>Handler wrapper that normalizes trailing slashes in request paths.
|
|
</p>
|
|
<p>MODE is either ’strip (default) or ’append: ’strip — redirect /foo/ to
|
|
/foo ’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> [<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'> ¶</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'> ¶</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'> ¶</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'> ¶</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'> ¶</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'> ¶</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'> ¶</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'> ¶</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'> ¶</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'> ¶</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'> ¶</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'> ¶</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'> ¶</a></span></dt>
|
|
<dd><p>Return a list of error message strings for NAME, or ’(). 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'> ¶</a></span></dt>
|
|
<dd><p>Check PARSED-PARAMS for mutually exclusive parameter groups. GROUPS is
|
|
a list of lists of symbols, e.g. ’((limit_results all_results)). If
|
|
parameters from the same group co-occur, the later ones are replaced
|
|
with <invalid-param> 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'> ¶</a></span></dt>
|
|
<dd><p>Return the <invalid-param> 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->query-string</strong> <em>parsed-params</em><a href='#index-params_002d_003equery_002dstring' class='copiable-anchor'> ¶</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'> ¶</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'> ¶</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 -> value | <invalid-param>), 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 <invalid-param> 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><invalid-param></strong><a href='#index-_003cinvalid_002dparam_003e' class='copiable-anchor'> ¶</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> [<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'> ¶</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'> ¶</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’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'> ¶</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'> ¶</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'> ¶</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
|
|
"utf-8".
|
|
</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'> ¶</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'> ¶</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->streaming-json-array</strong> <em>proc lst port KEY: #:unicode</em><a href='#index-list_002d_003estreaming_002djson_002darray' class='copiable-anchor'> ¶</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->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'> ¶</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 ’GET ’(. path) (make-static-handler "/path/to/public"))
|
|
</p>
|
|
<p>Supports If-Modified-Since for 304 responses. CACHE-CONTROL, if given,
|
|
is a Cache-Control value in Guile’s header format — an alist, e.g.
|
|
’((max-age . 3600)) or ’((no-cache)).
|
|
</p>
|
|
<p>Works with /gnu/store paths: files with a very low mtime (as produced by
|
|
the store’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'> ¶</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'> ¶</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'> ¶</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'> ¶</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->streaming-json</strong> <em>alist port KEY: #:unicode</em><a href='#index-scm_002dalist_002d_003estreaming_002djson' class='copiable-anchor'> ¶</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->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'> ¶</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
|
|
"session" token #:path "/" #:http-only #t #:secure #t))) "ok")
|
|
</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'> ¶</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->streaming-json and list->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'> ¶</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> [<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'> ¶</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'> ¶</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'> ¶</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'> ¶</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'> ¶</a></span></dt>
|
|
<dd><p>Return <code>#t</code> if OBJ is a <code><route-group></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'> ¶</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'> ¶</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'> ¶</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'> ¶</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'> ¶</a></span></dt>
|
|
<dd><p>Return <code>#t</code> if OBJ is a <code><route></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'> ¶</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'> ¶</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'> ¶</a></span></dt>
|
|
<dd><p>Compile a route tree (route, route-group, or list) into two values: 1.
|
|
An ordered list of <compiled-route> records ready for matching. 2. A
|
|
<reverse-routes> record for use with path-for.
|
|
</p>
|
|
<p>The last route must be a catch-all (’* 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'> ¶</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 ’() if no
|
|
route’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'> ¶</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'> ¶</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'> ¶</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 ’users) (path-for routes ’user ’((id . "42")))
|
|
(path-for routes ’(api items) ’((id . "7")))
|
|
</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'> ¶</a></span></dt>
|
|
<dd><p>Create a route. METHOD is a symbol, list of symbols, or ’* 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) ->
|
|
(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'> ¶</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'> ¶</a></span></dt>
|
|
<dd><p>Append NEW-CHILDREN to GROUP’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'> ¶</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> [<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'> ¶</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 "My Page"))
|
|
(body (h1 "Hello")
|
|
,(lambda (port)
|
|
(write-shtml-as-html '(p "dynamic") 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'> ¶</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 "Title")
|
|
,(lambda (port) (display "dynamic" port))
|
|
(footer "bye")))
|
|
port)
|
|
</pre></div>
|
|
|
|
<p>Static parts are rendered via htmlprag’s <code>shtml->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> [<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'> ¶</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'> ¶</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'> ¶</a></span></dt>
|
|
<dd><p>Read and parse a multipart/form-data body from REQUEST. Returns a list
|
|
of <part> 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'> ¶</a></span></dt>
|
|
<dd><p>Parse the query string from REQUEST. Returns an alist of string
|
|
key-value pairs, or ’() 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'> ¶</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'> ¶</a></span></dt>
|
|
<dd><p>Return the cookies from REQUEST as an alist of (name . value) pairs.
|
|
Returns ’() 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> [<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> [<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 © 2026 Christopher Baines <mail@cbaines.net>
|
|
</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> [<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> [<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: </th><td><a class="summary-letter" href="#Data-Type-Index_tp_symbol-1"><b><</b></a>
|
|
</td></tr></table>
|
|
<table class="index-tp" border="0">
|
|
<tr><td></td><th align="left">Index Entry</th><td> </td><th align="left"> Section</th></tr>
|
|
<tr><td colspan="4"> <hr></td></tr>
|
|
<tr><th id="Data-Type-Index_tp_symbol-1"><</th><td></td><td></td></tr>
|
|
<tr><td></td><td valign="top"><a href="#index-_003cinvalid_002dparam_003e"><code><invalid-param></code></a>:</td><td> </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: </th><td><a class="summary-letter" href="#Data-Type-Index_tp_symbol-1"><b><</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> [<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: </th><td><a class="summary-letter" href="#Procedure-Index_fn_letter-A"><b>A</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-B"><b>B</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-C"><b>C</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-D"><b>D</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-E"><b>E</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-F"><b>F</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-G"><b>G</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-H"><b>H</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-I"><b>I</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-J"><b>J</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-L"><b>L</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-M"><b>M</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-N"><b>N</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-P"><b>P</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-R"><b>R</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-S"><b>S</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-T"><b>T</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-W"><b>W</b></a>
|
|
|
|
</td></tr></table>
|
|
<table class="index-fn" border="0">
|
|
<tr><td></td><th align="left">Index Entry</th><td> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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->streaming-json-array</code></a>:</td><td> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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->query-string</code></a>:</td><td> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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->streaming-json</code></a>:</td><td> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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> </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: </th><td><a class="summary-letter" href="#Procedure-Index_fn_letter-A"><b>A</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-B"><b>B</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-C"><b>C</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-D"><b>D</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-E"><b>E</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-F"><b>F</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-G"><b>G</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-H"><b>H</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-I"><b>I</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-J"><b>J</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-L"><b>L</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-M"><b>M</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-N"><b>N</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-P"><b>P</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-R"><b>R</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-S"><b>S</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-T"><b>T</b></a>
|
|
|
|
<a class="summary-letter" href="#Procedure-Index_fn_letter-W"><b>W</b></a>
|
|
|
|
</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> [<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: </th><td><a class="summary-letter" href="#Variable-Index_vr_letter-C"><b>C</b></a>
|
|
|
|
</td></tr></table>
|
|
<table class="index-vr" border="0">
|
|
<tr><td></td><th align="left">Index Entry</th><td> </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> </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> </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> </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> </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: </th><td><a class="summary-letter" href="#Variable-Index_vr_letter-C"><b>C</b></a>
|
|
|
|
</td></tr></table>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
</body>
|
|
</html>
|