Frequently asked questions

These answers describe the current repository releases (Spock 0.18 and Spock-core 0.16). Start with Getting Started, then use the API reference to look up individual functions.

For typed file extensions, distinct /foo and /foo/ handlers, or canonical redirects, see slash policies and canonical routes.

Can I use regular expressions or custom types in routes?

Routes use typed path captures. In get ("number" <//> var) handler, a handler taking Int makes var parse an integer. A failed parse skips that route; if nothing else matches, the response is 404. A static route takes precedence over a capture at the same position. Use AltVar for an explicit choice between capture types, as shown in the routing guide.

There is no built-in regex route constructor. Define a type with a FromHttpApiData instance to validate a decoded path segment. This example uses regex-tdfa for a lowercase slug:

newtype Slug = Slug Text

slugPattern :: Regex
slugPattern = makeRegexOpts (defaultCompOpt { multiline = False }) defaultExecOpt
  ("^[a-z][a-z0-9-]*$" :: String)

instance FromHttpApiData Slug where
  parseUrlPiece piece
    | matchTest slugPattern (unpack piece) = Right (Slug piece)
    | otherwise = Left "Expected a lowercase slug"

app = get ("slug" <//> var) $ \(Slug slug) -> text slug

Use OverloadedStrings, and import Data.Text (Text, unpack), Text.Regex.TDFA, Web.HttpApiData, and Web.Spock. Add regex-tdfa and http-api-data to the executable’s dependencies. The expression is constant and matches the entire segment, including when the URL contains an encoded newline. You can instead write any pure parser in parseUrlPiece; a regex dependency is optional. Add ToHttpApiData to support reverse routing with renderRoute.

Use "rest" <//> wildcard to capture the remaining path as Text, including multiple segments. A wildcard must come last. hookAny receives all path segments when a typed route is unsuitable. A match only selects a handler; it does not grant authorization or make a captured value safe as a file path.

The complete FAQ example compiles and tests these routes, plus the CSRF examples below. Run it from the repository with cabal run spock-faq-example, then visit /slug/hello-spock or /form on http://localhost:8080.

Where does Spock store sessions?

The default store is an in-process STM map. The browser receives an opaque session ID in the spockcookie cookie; session values and CSRF tokens stay on the server. The map disappears on process restart and is not shared between separately configured workers.

The optional PostgreSQL session adapter persists sessions and shares them between workers using the same database and namespace. A custom SessionStore must provide atomic transactions for the complete ss_runTx action, including multi-operation updates.

Does every request create a session?

Since Spock 0.16, the default is SessionsOnDemand. A request loads, creates, or renews a session only when it calls a session action or performs a CSRF check. Serving a stateless page does not allocate a session or set its cookie. Even readSession and getCsrfToken can create a session.

Mode Behavior
SessionsOnDemand Use sessions when an action needs them; the default
SessionsAlways Load or create a session on every request; the pre-0.16 behavior
SessionsDisabled No session middleware or housekeeping; session actions fail

Full Spock still provides database pooling and shared state with sessions disabled. Spock-core lets you choose your own base monad and does not include full Spock’s session manager. See the monad guide.

When do sessions expire?

sc_sessionTTL defaults to 3600 seconds. With the default sc_sessionExpandTTL = True, loading a valid session renews its server-side deadline to at least one hour from that access. In on-demand mode, visits that do not use a session do not renew it. Set expansion to False for a fixed lifetime from session creation.

Expired sessions are rejected immediately when accessed. The default server backend’s ssc_housekeepingInterval is 600 seconds; its sweep reclaims expired records that nobody accesses. It is not an extra ten-minute validity window.

Cookie lifetime is a separate browser setting (sc_cookieSettings.cs_EOL). defaultSpockCfg uses a long-lived cookie, so a browser may send an ID after its server session has expired; Spock then creates an empty session if needed. defaultBrowserSpockCfg uses a browser-session cookie. Do not rely on closing a browser as logout: use a protected sessionDestroy action to revoke the server session and expire its cookie.

How do sessions and CSRF protection fit together?

CSRF protection compares a token supplied by the caller with the token in that caller’s session. Enable it for cookie-authenticated browser actions using defaultBrowserSpockCfg, or set spc_csrfProtection = True explicitly. The older defaultSpockCfg leaves it off. Protection works with on-demand sessions; combining it with SessionsDisabled is a startup error.

Full Spock’s route helpers check unsafe methods, including POST, PUT, PATCH, and DELETE. GET, HEAD, and OPTIONS must not change application state. Core route helpers, including those used by Spock-api-server, need an explicit CSRF check when they are used with cookie authentication; the configuration flag alone does not wrap routes registered through another layer.

For a form, obtain getCsrfToken while rendering and include it in an input named __csrf_token. POST it as application/x-www-form-urlencoded with the session cookie. For JSON, return the token from a same-origin endpoint and send it in the X-Csrf-Token header with the cookie; putting it inside JSON does not satisfy the check. The names are configurable through spc_csrfPostName and spc_csrfHeaderName. Keep tokens out of URLs and logs.

The runnable example serves both /form and /csrf. Its /json endpoint accepts a JSON string:

token=$(curl -s -c cookies.txt http://localhost:8080/csrf)
curl -b cookies.txt -H "X-Csrf-Token: $token" \
  -H 'Content-Type: application/json' -d '"hello"' http://localhost:8080/json

Both the token and its session cookie are needed. Missing, incorrect, or expired-session tokens receive 403. CSRF protection does not authenticate a user or replace permission checks, HTTPS, or HTML escaping.

Which browser configuration should I start with?

defaultBrowserSpockCfg enables CSRF checks and sets Secure, HttpOnly, SameSite=Lax cookies. Deploy it behind HTTPS, including when a trusted proxy terminates TLS. For local HTTP development only, the FAQ executable explicitly disables the Secure flag. Preserve it in production. Rotate the session ID with sessionRegenerateId when signing in and use a CSRF-protected POST route calling sessionDestroy for logout. See the session configuration reference. For a complete tested login, form, JSON, and logout flow, follow the browser security guide.

For session values stored entirely in an encrypted cookie, see cookie sessions and backend migration. That backend checks expiry on the server but cannot revoke copied cookies or merge concurrent request updates; server storage remains the default.