///|
/// HTTP Context for request/response handling
pub(all) struct Context {
/// The incoming HTTP request
request : @http.Request
/// URL parameters extracted from route matching
params : @router.Params
/// Platform-specific context data
priv platform : PlatformContext
/// Custom data storage (string only, legacy)
priv data : Map[String, String]
/// Typed variables storage
vars : Variables
/// Execution context for background tasks (trait object)
exec_ctx : &ExecCtx
/// Environment bindings (trait object)
priv env : &Env
/// Cached request body (lazy loaded)
priv mut cached_body : String?
/// Cached query parameters (lazy loaded)
priv mut cached_query : Map[String, String]?
/// Cached cookies (lazy loaded)
priv mut cached_cookies : Map[String, String]?
/// Response headers to send
priv response_headers : Map[String, String]
/// Response status (set before sending)
priv mut status : Int
/// Whether response has been sent
priv mut response_sent : Bool
}
///|
/// Get a URL parameter by name
pub fn Context::param(self : Context, name : String) -> String? {
self.params.get(name)
}
///|
/// Get request path
pub fn Context::path(self : Context) -> String {
self.request.path
}
///|
/// Get request method
pub fn Context::meth(self : Context) -> @http.RequestMethod {
self.request.meth
}
///|
/// Get raw HTTP request
pub fn Context::request(self : Context) -> @http.Request {
self.request
}
///|
/// Get a request header
pub fn Context::header(self : Context, name : String) -> String? {
self.request.headers.get(name)
}
///|
/// Set custom data
pub fn Context::set(self : Context, key : String, value : String) -> Unit {
self.data.set(key, value)
}
///|
/// Get custom data
pub fn Context::get(self : Context, key : String) -> String? {
self.data.get(key)
}
///|
/// Get environment variable
pub fn Context::env_var(self : Context, name : String) -> String? {
self.env.get_var(name)
}
///|
/// Get environment binding as JSON
pub fn Context::env_binding(self : Context, name : String) -> Json? {
self.env.get_binding(name)
}
///|
/// Schedule a background task (like Cloudflare's waitUntil)
pub fn Context::wait_until(
self : Context,
task : async () -> Unit,
name? : String = "background",
) -> Unit {
self.exec_ctx.wait_until(task, name~)
}
///|
/// Set response status code
pub fn Context::set_status(self : Context, status : Int) -> Unit {
self.status = status
}
///|
/// Set a response header
pub fn Context::set_header(
self : Context,
name : String,
value : String,
) -> Unit {
self.response_headers.set(name, value)
}
///|
/// Parse request body as JSON
pub async fn Context::body_json(self : Context) -> Json? {
let body = self.body()
let result = @json.parse(body) catch { _ => return None }
Some(result)
}
///|
/// Get a query parameter by name
pub fn Context::query(self : Context, name : String) -> String? {
let query_map = match self.cached_query {
Some(q) => q
None => {
let q = parse_query_string(self.request.path)
self.cached_query = Some(q)
q
}
}
query_map.get(name)
}
///|
/// Get all query parameters
pub fn Context::queries(self : Context) -> Map[String, String] {
match self.cached_query {
Some(q) => q
None => {
let q = parse_query_string(self.request.path)
self.cached_query = Some(q)
q
}
}
}
///|
/// Get a cookie by name
pub fn Context::cookie(self : Context, name : String) -> String? {
let cookies = match self.cached_cookies {
Some(c) => c
None => {
let c = parse_cookies(self.request.headers.get("Cookie").unwrap_or(""))
self.cached_cookies = Some(c)
c
}
}
cookies.get(name)
}
///|
/// Get all cookies
pub fn Context::cookies(self : Context) -> Map[String, String] {
match self.cached_cookies {
Some(c) => c
None => {
let c = parse_cookies(self.request.headers.get("Cookie").unwrap_or(""))
self.cached_cookies = Some(c)
c
}
}
}
///|
/// Set a cookie
pub fn Context::set_cookie(
self : Context,
name : String,
value : String,
max_age? : Int = 86400,
path? : String = "/",
http_only? : Bool = true,
same_site? : String = "Lax",
) -> Unit {
let cookie = build_set_cookie(
name,
value,
max_age~,
path~,
http_only~,
same_site~,
)
self.response_headers.set("Set-Cookie", cookie)
}
///|
/// Delete a cookie
pub fn Context::delete_cookie(self : Context, name : String) -> Unit {
let cookie = "\{name}=; Path=/; Max-Age=0; HttpOnly"
self.response_headers.set("Set-Cookie", cookie)
}
///|
/// Parse query string from path
fn parse_query_string(path : String) -> Map[String, String] {
let result : Map[String, String] = {}
// Find query string start
let query_start = find_char(path, '?')
if query_start < 0 || query_start + 1 >= path.length() {
return result
}
// Extract query string
let query = substring_from(path, query_start + 1)
// Parse key=value pairs
let pairs = split_by_char(query, '&')
for pair in pairs {
let eq_pos = find_char(pair, '=')
if eq_pos >= 0 {
let key = url_decode(substring(pair, 0, eq_pos))
let value = url_decode(substring_from(pair, eq_pos + 1))
result.set(key, value)
}
}
result
}
///|
/// Parse cookies from Cookie header
fn parse_cookies(header : String) -> Map[String, String] {
let result : Map[String, String] = {}
let pairs = split_by_char(header, ';')
for pair in pairs {
let trimmed = trim(pair)
let eq_pos = find_char(trimmed, '=')
if eq_pos >= 0 {
let key = substring(trimmed, 0, eq_pos)
let value = substring_from(trimmed, eq_pos + 1)
result.set(key, value)
}
}
result
}
///|
/// Build Set-Cookie header value
fn build_set_cookie(
name : String,
value : String,
max_age~ : Int,
path~ : String,
http_only~ : Bool,
same_site~ : String,
) -> String {
let buf = StringBuilder::new()
buf.write_string(name)
buf.write_char('=')
buf.write_string(value)
buf.write_string("; Path=")
buf.write_string(path)
buf.write_string("; Max-Age=")
buf.write_string(max_age.to_string())
if http_only {
buf.write_string("; HttpOnly")
}
buf.write_string("; SameSite=")
buf.write_string(same_site)
buf.to_string()
}
///|
/// Merge response headers
/// base: user-set headers (via set_header), these take precedence
/// extra: default headers, only applied if not already set
fn merge_headers(
base : Map[String, String],
extra : Map[String, String],
) -> Map[String, String] {
let result : Map[String, String] = {}
// First, add defaults
for key, value in extra {
result.set(key, value)
}
// Then, add user-set headers (overriding defaults)
for key, value in base {
result.set(key, value)
}
result
}
///|
/// Check if response has been sent
pub fn Context::is_response_sent(self : Context) -> Bool {
self.response_sent
}