Safsaf is a web framework for Guile Scheme, built on Guile Fibers using the Guile Knots web server.
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 API.
A Safsaf application is a list of routes passed to run-safsaf.
Each route binds an HTTP method and URL pattern to a handler procedure.
The handler receives a Guile <request> and a body port, and
returns two values: a response and a body.
(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)
The last route should be a catch-all ('* method, '*
pattern) so that every request is handled. run-safsaf sets up
a Fibers scheduler, starts the HTTP server, and blocks until Ctrl-C.
Next: Handler Wrappers, Previous: Getting Started, Up: Guidance [Contents][Index]
Route patterns are lists of segments. A string matches literally, a
symbol captures that segment into current-route-params, and a
two-element list (predicate name) captures only when
predicate returns true. A dotted tail captures the remaining
path.
;; 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)
route-group nests routes under a shared prefix:
(route-group '("api" "v1")
(route 'GET '("users") api-list-users)
(route 'GET '("users" id) api-show-user))
This matches /api/v1/users and /api/v1/users/:id.
Give a route a #:name and use path-for to generate its
URL, so paths are never hard-coded. The first argument is always a
route group:
(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"
path-for also accepts #:query and #:fragment
keyword arguments.
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
wrap-routes.
(wrap-routes routes (make-exceptions-handler-wrapper #:dev? #t) logging-handler-wrapper)
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.
Apply wrappers to part of the route tree by wrapping a group separately:
(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))
Here CORS headers are added only to /api/* routes, while
logging applies to everything.
security-headers-handler-wrapper appends its headers to
the response rather than replacing existing ones. If a handler sets
X-Frame-Options itself, both values will appear in the response.
To avoid duplication, either omit the header from the wrapper (pass
#:frame-options #f) or do not set it in the handler.
make-max-body-size-handler-wrapper checks the
Content-Length header and rejects requests that exceed the
limit with a 413 response. However, it does not limit chunked
transfer-encoded requests that lack Content-Length. For
untrusted networks, use a reverse proxy (e.g. Nginx’s
client_max_body_size) to enforce size limits at the transport
level.
Next: Request Parsing, Previous: Handler Wrappers, Up: Guidance [Contents][Index]
Safsaf provides helpers that return (values response body)
directly:
;; 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)
html-response, json-response, text-response, and
redirect-response accept #:code and #:headers for
overrides. The error helpers (not-found-response, etc.) accept
#:headers but have a fixed status code.
For content negotiation, use negotiate-content-type:
(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))))))))
Next: Parameter Parsing, Previous: Responses, Up: Guidance [Contents][Index]
parse-form-body reads a URL-encoded POST body and returns an
alist of string pairs:
(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))))
parse-query-string extracts query parameters from the request
URL:
(let ((qs (parse-query-string request))) (assoc-ref qs "page")) ;; => "2" or #f
For file uploads, use parse-multipart-body:
(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)
...)
Read cookies with request-cookie-ref or
request-cookies. Set them via response headers with
set-cookie-header and delete-cookie-header:
(request-cookie-ref request "theme") ;; => "dark" or #f
(text-response "ok"
#:headers (list (set-cookie-header "theme" "dark"
#:path "/"
#:http-only #t)))
Next: Sessions, Previous: Request Parsing, Up: Guidance [Contents][Index]
parse-params 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
<invalid-param>), and options like #:required or
#:default.
(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)
Built-in processors: as-string, as-integer,
as-number, as-checkbox, as-one-of,
as-matching, as-predicate.
For POST forms, use parse-form-params instead — it
automatically checks the CSRF token (from
csrf-handler-wrapper) before parsing:
(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))))
any-invalid-params? returns #t if any value failed
validation. field-errors returns a list of error message
strings for a given field, suitable for rendering next to form inputs.
Next: Templating, Previous: Parameter Parsing, Up: Guidance [Contents][Index]
Sessions use HMAC-signed cookies via (webutils sessions).
Set up a session config and apply the wrapper:
(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)))
Inside a handler, (current-session) returns the session data
(an alist) or #f if no valid session exists.
To set session data, include a session-set header in the
response. To delete, use session-delete:
;; 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)))
Next: Static Files, Previous: Sessions, Up: Guidance [Contents][Index]
write-shtml-as-html/streaming works like htmlprag’s
write-shtml-as-html, but any procedure in the SHTML tree is
called as (proc port) and can write dynamic content directly.
streaming-html-response wraps this into a response: give it an
SHTML tree (with optional procedure slots) and it returns
(values response body) ready for a handler.
(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"))))))
The layout is plain SHTML with a procedure in the content-proc
position. Use streaming-html-response to send it:
(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)))))
You can also call write-shtml-as-html/streaming directly when
you need to write SHTML with procedure slots to an arbitrary port.
Previous: Templating, Up: Guidance [Contents][Index]
make-static-handler returns a handler that serves files from a
directory. Pair it with a wildcard route:
(route-group '("static")
(route 'GET '(. path)
(make-static-handler "./public"
#:cache-control '((max-age . 3600)))))
This serves /static/css/style.css from
./public/css/style.css. The handler supports
If-Modified-Since for 304 responses.
Next: Version History, Previous: Guidance, Up: Overview [Contents][Index]
The following is the list of modules provided by this library.
Next: (safsaf handler-wrappers cors), Up: API [Contents][Index]
Return a 405 Method Not Allowed response with an Allow header listing ALLOWED-METHODS.
Start a Safsaf web server.
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.
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.
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.
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.
Next: (safsaf handler-wrappers csrf), Previous: (safsaf), Up: API [Contents][Index]
Handler wrapper that adds CORS (Cross-Origin Resource Sharing) headers to responses.
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.
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.
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.
Next: (safsaf handler-wrappers exceptions), Previous: (safsaf handler-wrappers cors), Up: API [Contents][Index]
Default value:
#f
CSRF token handler wrapper.
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).
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.
Return an SXML hidden input element for the CSRF token. Use in forms:
(csrf-token-field) ⇒ (input (@ (type "hidden")
...)).
Next: (safsaf handler-wrappers logging), Previous: (safsaf handler-wrappers csrf), Up: API [Contents][Index]
Return a render-error procedure that content-negotiates between RENDER-HTML and RENDER-JSON based on the request’s Accept header.
Default HTML error renderer. In dev mode, shows a rich backtrace page. In production, returns a minimal HTML page.
Default JSON error renderer. In dev mode, includes the backtrace. In production, returns only the error message.
Handler wrapper that catches exceptions from HANDLER and returns an error response.
The response format is content-negotiated from the request’s Accept header, choosing between HTML and JSON.
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.
Rendering can be customised at three levels:
#:render-error — full override. A procedure (request code message backtrace-string dev?) -> (values response body) that bypasses content negotiation entirely.
#:render-html — custom HTML rendering. A procedure with the same signature, called when content negotiation selects HTML.
#:render-json — custom JSON rendering. A procedure with the same signature, called when content negotiation selects JSON.
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.
Return a handler wrapper that catches exceptions and returns an error response. See exceptions-handler-wrapper for details.
Next: (safsaf handler-wrappers max-body-size), Previous: (safsaf handler-wrappers exceptions), Up: API [Contents][Index]
Handler wrapper that logs each request and response.
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!.
Next: (safsaf handler-wrappers security-headers), Previous: (safsaf handler-wrappers logging), Up: API [Contents][Index]
Return a handler wrapper that rejects requests whose Content-Length exceeds MAX-BYTES with a 413 Payload Too Large response.
HANDLER-413 is a handler (request body-port) -> (values response body) called when the limit is exceeded; the default returns plain text.
Note: this checks the Content-Length header only. Chunked transfers without Content-Length are not limited by this wrapper.
Next: (safsaf handler-wrappers sessions), Previous: (safsaf handler-wrappers max-body-size), Up: API [Contents][Index]
Handler wrapper that adds security headers to every response.
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
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
Next: (safsaf handler-wrappers trailing-slash), Previous: (safsaf handler-wrappers security-headers), Up: API [Contents][Index]
Default value:
#f
Create a session manager for use with session-handler-wrapper.
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.
Return a handler wrapper that binds session data from SESSION-MANAGER. See session-handler-wrapper for details.
Return a Set-Cookie header that expires the session cookie. Include in a response headers list: (redirect-response "/" #:headers (list (session-delete manager)))
Session handler wrapper using signed cookies via (webutils sessions).
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.
Handlers read session data via: (current-session) → session data or #f
To set or delete the session, handlers include the appropriate header in their response using session-set and session-delete:
(redirect-response "/" #:headers (list (session-set manager data))) (redirect-response "/" #:headers (list (session-delete manager)))
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)))))
Next: (safsaf params), Previous: (safsaf handler-wrappers sessions), Up: API [Contents][Index]
Return a handler wrapper that normalizes trailing slashes.
MODE is either ’strip (default) or ’append: ’strip — redirect /foo/ to /foo ’append — redirect /foo to /foo/
The root path / is always left alone.
CODE is the HTTP status code for the redirect (default 301).
Use with wrap-routes: (wrap-routes routes (make-trailing-slash-handler-wrapper #:mode ’append))
Handler wrapper that normalizes trailing slashes in request paths.
MODE is either ’strip (default) or ’append: ’strip — redirect /foo/ to /foo ’append — redirect /foo to /foo/
The root path / is always left alone.
CODE is the HTTP status code for the redirect (default 301).
Next: (safsaf response-helpers), Previous: (safsaf handler-wrappers trailing-slash), Up: API [Contents][Index]
Undocumented macro.
Undocumented macro.
Undocumented macro.
Undocumented macro.
Return #t if any values in PARSED-PARAMS are invalid.
Undocumented procedure.
Undocumented procedure.
Return a processor that accepts values matching REGEX.
Undocumented procedure.
Return a processor that accepts only values in CHOICES (a list of strings).
Return a processor that accepts values for which PRED returns true.
Undocumented procedure.
Return a list of error message strings for NAME, or ’(). Convenient for rendering form fields with per-field errors.
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.
Return the <invalid-param> record for NAME, or #f if valid or absent.
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.
Like parse-params but prepends a CSRF token check. Uses current-csrf-token from (safsaf handler-wrappers csrf).
Parse and transform parameters from RAW-PARAMS according to PARAM-SPECS.
RAW-PARAMS is an alist of (string . string) pairs, as returned by parse-query-string or parse-form-body.
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:
(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
Returns an alist of (symbol . value) pairs. Values that fail validation appear as <invalid-param> records inline. Missing optional params without defaults are omitted.
This record type has the following fields:
valuemessageNext: (safsaf router), Previous: (safsaf params), Up: API [Contents][Index]
Return a 400 Bad Request response.
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.
Use this in handler wrappers that need to adjust headers on an inner handler’s response without losing any response fields.
Return a Set-Cookie header pair that expires cookie NAME. Wraps (webutils cookie) delete-cookie.
Return a 403 Forbidden response.
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".
Return a 500 Internal Server Error response.
Return a JSON response. STR is the JSON string to send.
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.
Return a handler that serves static files from ROOT-DIR.
The handler expects route params to contain a wildcard capture (the file path segments). Use with a wildcard route:
(route ’GET ’(. path) (make-static-handler "/path/to/public"))
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)).
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.
Return the most appropriate MIME type symbol for REQUEST from SUPPORTED.
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.
EXTENSIONS is an alist mapping file extension strings to MIME type symbols, used for path-based negotiation. Defaults to %negotiation-extensions.
Return a 404 Not Found response.
Return a 413 Payload Too Large response.
Return a redirect response to PATH (a string).
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.
Return a Set-Cookie header pair suitable for inclusion in a response headers alist. Wraps (webutils cookie) set-cookie.
Example: (values (build-response #:headers (list (set-cookie-header "session" token #:path "/" #:http-only #t #:secure #t))) "ok")
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.
Return a plain text response. STR is the text string to send.
Next: (safsaf templating), Previous: (safsaf response-helpers), Up: API [Contents][Index]
Undocumented macro.
Return the list of child routes and groups of ROUTE-GROUP.
Return the name of ROUTE-GROUP, or #f if unnamed.
Return the prefix pattern of ROUTE-GROUP.
Return #t if OBJ is a <route-group>.
Return the handler procedure of ROUTE.
Return the HTTP method of ROUTE.
Return the name of ROUTE, or #f if unnamed.
Return the URL pattern of ROUTE.
Return #t if OBJ is a <route>.
Default value:
#f
Default value:
()
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.
The last route must be a catch-all (’* pattern with a rest parameter) so that every request is handled.
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.
Create an empty route group with PREFIX. Children can be added later with route-group-add-children!.
Find the first matching route for METHOD and PATH-SEGMENTS. Returns (values handler bindings) on match, or (values #f #f) on no match.
Generate a URL path for a named route within GROUP.
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.
(path-for routes ’users) (path-for routes ’user ’((id . "42"))) (path-for routes ’(api items) ’((id . "7")))
PARAMS is an alist mapping capture symbols to string values, or to a list of strings for rest parameters.
Optional keyword arguments: #:query — alist of query parameters ((key . value) ...) #:fragment — fragment string (without the leading #) #:relative? — if #t, omit the leading /
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.
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.
Append NEW-CHILDREN to GROUP’s child list.
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).
Next: (safsaf utils), Previous: (safsaf router), Up: API [Contents][Index]
Return an HTML response that streams SHTML to the client.
SHTML is an SHTML tree that may contain procedures. Each procedure is
called as (proc port) during output and should write HTML to the
port. Static parts are rendered via htmlprag.
(streaming-response
`(*TOP*
(*DECL* DOCTYPE html)
(html (head (title "My Page"))
(body (h1 "Hello")
,(lambda (port)
(write-shtml-as-html '(p "dynamic") port))))))
Write SHTML to PORT, like write-shtml-as-html from htmlprag, but
any procedure encountered in the tree is called as (proc port)
and may write directly to PORT.
This allows mixing static SHTML with dynamic streaming sections:
(write-shtml-as-html/streaming
`(html (body (h1 "Title")
,(lambda (port) (display "dynamic" port))
(footer "bye")))
port)
Static parts are rendered via htmlprag’s shtml->html, then
interleaved with procedure calls at output time.
Previous: (safsaf templating), Up: API [Contents][Index]
Extract text fields from multipart PARTS as an alist of (name . value). File upload parts (those with a filename parameter) are excluded.
Read and parse a URL-encoded form body from REQUEST. Returns an alist of string key-value pairs.
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.
Parse the query string from REQUEST. Returns an alist of string key-value pairs, or ’() if no query string.
Return the value of cookie NAME from REQUEST, or DEFAULT if not found.
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).
Next: Copying Information, Previous: API, Up: Overview [Contents][Index]
Next: Concept Index, Previous: Version History, Up: Overview [Contents][Index]
Copyright © 2026 Christopher Baines <mail@cbaines.net>
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.
Next: Procedure Index, Previous: Concept Index, Up: Overview [Contents][Index]
| Jump to: | < |
|---|
| Index Entry | Section | ||
|---|---|---|---|
| | |||
| < | |||
<invalid-param>: | safsaf_params | ||
| | |||
| Jump to: | < |
|---|
Next: Variable Index, Previous: Data Type Index, Up: Overview [Contents][Index]
| Jump to: | A B C D E F G H I J L M N P R S T W |
|---|
| Jump to: | A B C D E F G H I J L M N P R S T W |
|---|
Previous: Procedure Index, Up: Overview [Contents][Index]
| Jump to: | C |
|---|
| Index Entry | Section | ||
|---|---|---|---|
| | |||
| C | |||
current-csrf-token: | safsaf_handler-wrappers_csrf | ||
current-reverse-routes: | safsaf_router | ||
current-route-params: | safsaf_router | ||
current-session: | safsaf_handler-wrappers_sessions | ||
| | |||
| Jump to: | C |
|---|