web API reference
An ergonomic HTTP framework over the httpd server engine. Register routes against handler func values, then web.run owns the accept loop, matches each request, and dispatches to the handler. A handler is a func value - func name(ctx as web.Context) { ... }, passed by its bare name (web.get($app, "/x", showUser)) - and it is called through its home interpreter, so an entry-program handler runs in the entry program's context across the module boundary. Routing supports :param segments, a middleware chain, and a custom not-found handler. Response and request helpers hang off the web.Context so a handler rarely reaches for httpd directly. Needs the default jennifer binary (the httpd engine is net-backed; the constrained jennifer-tiny has no network stack).
CONCURRENCY: the serve loop handles requests concurrently - it accepts a request, hands it to its own spawned worker, and immediately accepts the next, so a handler that blocks (a sql query, an outbound http call, a slow filesystem read) no longer stalls every other request. In-flight concurrency is bounded by the httpd engine's accept rate. Dispatch calls each handler func value, which runs in its home (entry-program) context, and that cross-boundary call path is race-safe: each worker carries its own call-depth counter, and reads of shared program state (globals, constants, maps) are safe. The one thing that is not safe by construction is a handler writing a shared top-level variable of the entry program: those workers run against the same host globals (unlike a top-level spawn, which snapshots), so two handlers mutating one global race. Keep per-request mutable state in the request/response or a synchronized store (the session module), not in a top-level def a handler assigns.
Import with import "web.j" as web;. See the web guide for prose and examples.
Functions
web.basicAuth(ctx as Context)
Parse the request's HTTP Basic credentials. Checking them (against a user store) and sending a 401 challenge are the app's - web only decodes the header.
Parameters
ctx{Context}- the request context
Returns {BasicCredentials} - the decoded credentials (present false if absent / invalid)
web.bearerToken(ctx as Context)
Return the request's bearer token (the <token> in Authorization: Bearer <token>), or "" when absent. Validate it yourself (an opaque lookup, or jwt.verify once the jwt module lands).
Parameters
ctx{Context}- the request context
Returns {string} - the bearer token, or "" when absent
web.before(app as App, handler as func)
Register a middleware handler, run before each route handler. A middleware is func name(ctx as web.Context) { ...; return true; }: return true to continue to the route handler, or respond and return false to halt.
Parameters
app{App}- the router to extendhandler{func}- the middleware func value
Returns {App} - a new App with the middleware appended to the chain
web.body(ctx as Context)
Return the raw request body as bytes (binary-safe). Use web.form / web.bodyJson for the common structured bodies, or convert.stringFromBytes for text.
Parameters
ctx{Context}- the request context
Returns {bytes} - the request body
web.bodyJson(ctx as Context)
Decode the request body as JSON. Errors (invalid JSON) propagate - catch them in the handler or let the framework's 500 net answer.
Parameters
ctx{Context}- the request context
Returns {json.Value} - the decoded request body
web.cookie(ctx as Context, name as string)
Return the value of the request cookie named name, or "" if absent.
Parameters
ctx{Context}- the request contextname{string}- the cookie name
Returns {string} - the cookie value, or "" when absent
web.cors(app as App, opts as CorsOptions)
Set the CORS policy for the whole app, returning a new App. When set (a non-empty opts.allowOrigin), the serve loop adds the Access-Control-* headers to every response and answers a preflight OPTIONS request with a 204 before routing.
Parameters
app{App}- the router to configureopts{CorsOptions}- the CORS policy
Returns {App} - a new App with the policy set
web.csrfCheck(ctx as Context, secret as string)
Validate the request's CSRF token: the submitted token (the X-CSRF-Token header, else the csrf form field) must equal the csrf cookie and its signature must verify. Guard unsafe methods (POST / PUT / PATCH / DELETE) with a web.before middleware that calls this and rejects on false.
Parameters
ctx{Context}- the request contextsecret{string}- the app's CSRF secret
Returns {bool} - true when the request carries a valid token
web.csrfToken(ctx as Context, secret as string)
Mint a CSRF token, set it in the csrf cookie, and return it for embedding in a form (a hidden csrf field) or handing to the client for an X-CSRF-Token header. Call from the GET handler that renders the form.
Parameters
ctx{Context}- the request contextsecret{string}- the app's CSRF secret (stable per deployment)
Returns {string} - the token to embed in the form / send as a header
web.delete(app as App, pattern as string, handler as func)
Register a DELETE handler for a pattern, returning a new App.
Parameters
app{App}- the router to extendpattern{string}- the route pattern, with optional:paramsegmentshandler{func}- the handler func value
Returns {App} - a new App with the route added
web.etag(ctx as Context, tag as string)
Set an ETag and honour a conditional GET. Sets the ETag response header to tag (quoted) and, if the request's If-None-Match matches, answers 304 Not Modified and returns true - the handler should then stop. Returns false when the client has no matching cached copy, so the handler sends the full body. tag is the app's choice of validator: a content hash (via hash), a row version, an mtime - so web needs no hashing of its own.
Parameters
ctx{Context}- the request contexttag{string}- the entity tag identifying this version of the response
Returns {bool} - true if a 304 was sent (stop), false to send the full response
web.form(ctx as Context)
Parse an application/x-www-form-urlencoded request body into a map of decoded field names to values.
Parameters
ctx{Context}- the request context
Returns {map of string to string} - the form fields
web.formValue(ctx as Context, name as string)
Return one form field's value, or "" when absent.
Parameters
ctx{Context}- the request contextname{string}- the field name
Returns {string} - the field value, or "" when absent
web.get(app as App, pattern as string, handler as func)
Register a GET handler for a pattern, returning a new App.
Parameters
app{App}- the router to extendpattern{string}- the route pattern, with optional:paramsegmentshandler{func}- the handler func value
Returns {App} - a new App with the route added
web.header(ctx as Context, name as string)
Return a request header value.
Parameters
ctx{Context}- the request contextname{string}- the header name
Returns {string} - the header value, or "" if absent
web.html(ctx as Context, status as int, body as string)
Answer with a text/html body.
Parameters
ctx{Context}- the request contextstatus{int}- the HTTP status codebody{string}- the HTML body
web.joinRoute(prefix as string, pattern as string)
Concatenate a mount prefix and a route pattern into one clean pattern: drops empty segments (so stray or doubled slashes never matter) and always yields a single leading slash. :param and trailing *wildcard segments carry no slash, so they pass through intact. "" + "/deck" -> "/deck"; "/v1" + "/" -> "/v1"; "" + "" -> "/". Exposed so a layer above (e.g. webapi) can reconstruct the full pattern a mounted route is served under.
Parameters
prefix{string}- the mount prefixpattern{string}- the route pattern
Returns {string} - the joined pattern
web.method(ctx as Context)
Return the request's HTTP method.
Parameters
ctx{Context}- the request context
Returns {string} - the HTTP method (e.g. "GET")
web.mount(app as App, prefix as string, sub as App)
Mount a sub-router's routes under a path prefix, returning a new App. Every route in sub is re-registered on app with prefix prepended to its pattern (web.mount($app, "/v1", $apiV1) serves apiV1's /deck at /v1/deck). A prefix of "" (or "/") mounts at the root, so the same sub-router can be mounted under several prefixes to serve one route set at many base paths.
Only the sub-router's routes are composed; app-level middleware, the not-found handler, CORS, and the error handler stay app's. :param and trailing *wildcard segments are preserved.
Parameters
app{App}- the router to extendprefix{string}- the base path to mount under ("" / "/" = root)sub{App}- the sub-router whose routes are mounted
Returns {App} - a new App with the sub-router's routes added under the prefix
web.multipartForm(ctx as Context)
Parse a multipart/form-data request body (a file-upload form) into its parts, using the request's Content-Type header (with the boundary) and the raw body. The completion of the body-parser family alongside web.form (urlencoded) and web.bodyJson (JSON). Each multipart.Part is a field or a file (multipart.isFile / multipart.text distinguish them); the handler imports multipart.j to name the type. A missing boundary or malformed part throws Error{kind: "multipart"}.
Parameters
ctx{Context}- the request context
Returns {list of multipart.Part} - the form parts (fields and files)
web.new()
Return an empty App with no routes or middleware.
Returns {App} - a fresh, empty router
web.notFound(app as App, handler as func)
Set a custom handler for unmatched requests (default: a plain 404).
Parameters
app{App}- the router to extendhandler{func}- the not-found handler func value
Returns {App} - a new App with the not-found handler set
web.onError(app as App, handler as func)
Register an error handler, returning a new App. When a handler or middleware throws, web catches it (so one failing request never stops the server), writes the failure to stderr, and then also invokes this handler with the thrown value - the Error struct {kind, message, file, line, col} for a runtime failure or a thrown Error (which crosses the module boundary intact, so the handler may declare its parameter as Error; a handler that throws some other value delivers that value instead). The hook is additive: stderr always gets the error, so registering a handler adds alerting / struct- ured logging without ever losing the default diagnostic. The handler runs after the 500 response is already committed, so it cannot change the response; it is for observability. An error from the error handler is itself caught and logged, never re-raised.
Parameters
app{App}- the router to extendhandler{func}- the error-handler func value
Returns {App} - a new App with the error handler set
web.param(ctx as Context, name as string)
Return a captured path parameter, or "" if the route had none by that name.
Parameters
ctx{Context}- the request contextname{string}- the path parameter name
Returns {string} - the captured value, or "" if absent
web.patch(app as App, pattern as string, handler as func)
Register a PATCH handler for a pattern, returning a new App.
Parameters
app{App}- the router to extendpattern{string}- the route pattern, with optional:paramsegmentshandler{func}- the handler func value
Returns {App} - a new App with the route added
web.path(ctx as Context)
Return the request's URL path.
Parameters
ctx{Context}- the request context
Returns {string} - the request path
web.post(app as App, pattern as string, handler as func)
Register a POST handler for a pattern, returning a new App.
Parameters
app{App}- the router to extendpattern{string}- the route pattern, with optional:paramsegmentshandler{func}- the handler func value
Returns {App} - a new App with the route added
web.put(app as App, pattern as string, handler as func)
Register a PUT handler for a pattern, returning a new App.
Parameters
app{App}- the router to extendpattern{string}- the route pattern, with optional:paramsegmentshandler{func}- the handler func value
Returns {App} - a new App with the route added
web.query(ctx as Context, name as string)
Return a query-string parameter value.
Parameters
ctx{Context}- the request contextname{string}- the query parameter name
Returns {string} - the parameter value, or "" if absent
web.redirect(ctx as Context, status as int, location as string)
Redirect to location with the given status (301 / 302 / 303 / 307 / 308).
Parameters
ctx{Context}- the request contextstatus{int}- the redirect status codelocation{string}- the target URL for the Location header
web.remoteAddr(ctx as Context)
Return the client's network address (host:port).
Parameters
ctx{Context}- the request context
Returns {string} - the remote address
web.renewSession(ctx as Context, cookieName as string)
Rotate the session id: mint a fresh UUID, set it as the session cookie, and return the new id. Call this right after any privilege change - a successful login, a logout, a role change - so a session id an attacker may have fixed before authentication is thrown away. This is the standard session-fixation defence; the application must also move its own session data to the new id.
Parameters
ctx{Context}- the request contextcookieName{string}- the session-id cookie name (e.g. "sid")
Returns {string} - the freshly minted session id
web.respond(ctx as Context, status as int, body as string)
Answer the request with a status code and body.
Parameters
ctx{Context}- the request contextstatus{int}- the HTTP status codebody{string}- the response body
web.route(app as App, method as string, pattern as string, handler as func)
Register a handler for a method + pattern, returning a new App.
Parameters
app{App}- the router to extendmethod{string}- the HTTP method to matchpattern{string}- the route pattern, with optional:paramsegmentshandler{func}- the handler func value
Returns {App} - a new App with the route added
web.run(app as App, addr as string)
Listen on addr and serve forever, dispatching each request to its matched handler. Blocks; interrupt to stop.
Parameters
app{App}- the routeraddr{string}- the listen address (e.g. ":8080")
web.sendGzip(ctx as Context, status as int, body as string)
Answer with a body, gzip-compressed when the client accepts it. Sets Vary: Accept-Encoding always; if the request's Accept-Encoding names gzip, the body is compressed and sent with Content-Encoding: gzip, otherwise it is sent as-is. Set the Content-Type yourself (via web.setHeader) before calling. Worth it for large text / JSON / HTML; skip it for already-compressed payloads (images, archives).
Parameters
ctx{Context}- the request contextstatus{int}- the HTTP status codebody{string}- the response body
web.sendJson(ctx as Context, status as int, doc as json.Value)
Answer with an application/json body encoded from a json.Value. (Named sendJson, not json, because a method may not shadow the json namespace this module imports.)
Parameters
ctx{Context}- the request contextstatus{int}- the HTTP status codedoc{json.Value}- the JSON document to encode
web.serveDir(ctx as Context, root as string)
Serve static files from a directory root (path-safe; 404 for a missing file).
Parameters
ctx{Context}- the request contextroot{string}- the directory to serve from
web.serveFile(ctx as Context, path as string)
Answer with a file from disk.
Parameters
ctx{Context}- the request contextpath{string}- the filesystem path to serve
web.serveOn(app as App, srv as httpd.Server)
Serve on an already-listening httpd.Server, dispatching each request to its matched handler. Blocks until the server is shut down (httpd.accept then errors and the loop exits). Use this when you want to hold the server handle yourself - e.g. to shut it down from another task, or to serve from a spawn. web.run is the listen-and-serve convenience over it.
Parameters
app{App}- the routersrv{httpd.Server}- the already-listening server handle
web.sessionId(ctx as Context, cookieName as string)
Return the request's session id, minting a new one on first use. If the cookieName cookie is present its value is returned; otherwise a fresh UUID v4 (crypto-grade random, so unguessable as a session token) is generated and set as a Secure, HttpOnly, SameSite=Lax, path-/ cookie (Secure is on by default; set JENNIFER_WEB_INSECURE_COOKIES=1 for local plaintext-HTTP dev). web manages only the id cookie - the session data itself lives in a store the app owns (e.g. the session module over memcache), so web forces no store or network dependency. Call once per request and capture the returned id.
Parameters
ctx{Context}- the request contextcookieName{string}- the session-id cookie name (e.g. "sid")
Returns {string} - the session id (existing or newly minted)
web.setCookie(ctx as Context, name as string, value as string, opts as CookieOptions)
Set a response cookie with the given attributes (a Set-Cookie header).
Parameters
ctx{Context}- the request contextname{string}- the cookie namevalue{string}- the cookie valueopts{CookieOptions}- the cookie attributes (zero-value = a session cookie)
web.setHeader(ctx as Context, name as string, value as string)
Set a response header.
Parameters
ctx{Context}- the request contextname{string}- the header namevalue{string}- the header value
web.text(ctx as Context, status as int, body as string)
Answer with a text/plain body.
Parameters
ctx{Context}- the request contextstatus{int}- the HTTP status codebody{string}- the response body
Structs
web.App
The value-semantic router state: the routes, the middleware chain (handler func values run before each route), and an optional not-found handler (hasNotFound false = the built-in 404). Every registrar returns a fresh App.
| Field | Type | Description |
|---|---|---|
routes | list of Route | the registered routes |
middleware | list of func | handler func values run before each route |
notFound | func | the not-found handler (only when hasNotFound) |
hasNotFound | bool | whether a custom not-found handler is set (else built-in 404) |
cors | CorsOptions | the CORS policy applied by the serve loop (zero-value = off) |
onError | func | the error-handler (only when hasOnError); see web.onError |
hasOnError | bool | whether an error handler is set (stderr always logs regardless) |
web.BasicCredentials
Parsed HTTP Basic credentials from the request. present is false when the request carried no valid Authorization: Basic header.
| Field | Type | Description |
|---|---|---|
user | string | the username |
password | string | the password |
present | bool | true when valid Basic credentials were supplied |
web.Context
What a handler receives: the underlying request handle plus the path parameters captured from the matched route (:id -> params["id"]).
| Field | Type | Description |
|---|---|---|
req | httpd.Request | the underlying request handle |
params | map of string to string | the captured path parameters |
web.CookieOptions
Attributes for a Set-Cookie header. A zero-value struct is a session cookie (no attributes); set the fields you need.
| Field | Type | Description |
|---|---|---|
path | string | the Path attribute ("" omits it) |
domain | string | the Domain attribute ("" omits it) |
maxAge | int | the Max-Age in seconds; 0 omits it, a negative value expires the cookie now |
httpOnly | bool | add HttpOnly (hide the cookie from JavaScript) |
secure | bool | add Secure (send only over HTTPS) |
sameSite | string | the SameSite attribute: "Lax", "Strict", "None", or "" to omit |
web.CorsOptions
A cross-origin (CORS) policy. When set on an App via web.cors, the serve loop adds the Access-Control-* headers to every response and answers a preflight OPTIONS request with 204. A zero-value struct (empty allowOrigin) leaves CORS off.
| Field | Type | Description |
|---|---|---|
allowOrigin | string | the Access-Control-Allow-Origin value ("*" or an origin; "" = off) |
allowMethods | string | the Access-Control-Allow-Methods value ("" omits it) |
allowHeaders | string | the Access-Control-Allow-Headers value ("" omits it) |
allowCredentials | bool | add Access-Control-Allow-Credentials: true |
maxAge | int | the Access-Control-Max-Age in seconds (0 omits it) |
web.Route
One registered (method, pattern, handler) triple. Exported only to satisfy the referential-closure rule (App exposes a list of Route); programs never build one directly - they call web.get / web.route.
| Field | Type | Description |
|---|---|---|
method | string | the HTTP method (e.g. "GET") |
pattern | string | the route pattern, with optional :param segments |
handler | func | the handler func value to dispatch to |