Context

The request, the parameters, and nothing below them.

Updated 2 min read

Context is what a handler sees: no file descriptors, no buffers, no C pointers.

record Context {
  var request: Request;
  var params: map(string, string);
  var locals: map(string, string);
  var startedAtMillis: int(64);
  var requestId: string;
}

Methods

MemberReturns
pathParam(name, fallback = "")A captured route segment
paramInt(name, fallback)The same, parsed as an integer
queryParam(name, fallback = "")A query-string value
setLocal(name, value)Stores per-request scratch state
getLocal(name, fallback = "")Reads it back
method()The request method
path()The normalised request path

The request

ctx.request.header("Accept")       // one header, with a fallback
ctx.request.contentType()
ctx.request.bodyText()
ctx.request.accepts("application/json")
ctx.request.clientIp()
ctx.request.query.get("q")
ctx.request.query.getInt("page", 1)
ctx.request.peerIp
ctx.request.peerPort

Query parameters are decoded with + as a space, and repeated keys keep the first value. getInt returns its fallback rather than throwing when a value is missing or unparsable, which is what makes ?page=banana a page-one request instead of a 500.

Reading cookies

There is no cookie map: read the header and parse what you need. Bound the header, then map the value onto an enum rather than carrying it around as a string:

enum Density { comfortable, compact }

proc densityFrom(header: string): Density {
  if header.isEmpty() || header.numBytes > 4096 then return Density.comfortable;

  for pair in header.split(";") {
    const entry = trim(pair);
    const eq = idx(entry, "=");
    if eq <= 0 then continue;
    if trim(sub(entry, 0, eq)) != "density" then continue;
    return if foldAscii(trim(sub(entry, eq + 1, entry.numBytes))) == "compact"
           then Density.compact else Density.comfortable;
  }
  return Density.comfortable;
}

The value never reaches the markup as a string. It becomes an enum, and the enum picks a class name from a fixed set — so a hostile cookie has nowhere to go.

Concurrency

Route handlers run concurrently on different tasks, each with its own Context. Module-level state must be immutable after start-up, or guarded — see concurrency.