跳转至

web.mw

web 中间件框架自带件:会话族(with_sessions/grant/drop)+ flash 读即清 + log_requests + timeout_ms(Plan 2b Task 1;设计 docs/superpowers/specs/2026-09-27-web-framework-design.md §4.1/§4.5/§7;纪律 §14-1)

收编口径(不复制):签名会话令牌与 Set-Cookie 行构造全走 http.frm.auth 现成面 —— auth_tok_issue/auth_tok_verify(签发/验签,恒时比较+过期域 frm 内铸)、 auth_cookie_set(注入面封口的 Set-Cookie 行构造:CR/LF/分号/DQUOTE 出域即拒)。 本模块零 crypto、零手工 cookie 拼接。auth_cookie_get 未收:web.core Req.cookies 已在 req_full 解析 Cookie 头(cookie 访问器直读),重复解析无增益。

时钟注入(§8-A5 可测性;测试传 fake,真钟归装配): - with_sessions(key, now) 第二参 = 时钟 fn() -> I64(2b Task 3 serve 以 | | now_ms() / 1000 装配); - grant_session 第 5 参 now —— spec §4.4 链式示例为 4 参,系伪码口径; exp = now+ttl 需真值,§8-A5 now 经参数注入条款优先(名字面不变,记录于 task-1-report)。 - 单位约定:now/ttl/时钟恒 unix 秒(todo_app 验收口径;auth 令牌 exp 同单位), Max-Age 恒秒(RFC 6265)——serve 侧装配须 /1000,勿直喂 now_ms()。

会话机制(§4.1/§4.3;方案 (a) 最小改动):Req 增 session: List[Pair] 槽 (core.ct;params 同款),with_sessions 验签后经 req_set_session 回填, handler 侧 §4.3 冻结名 session(r, "uid") 读取(缺回空)。验签失败(无 cookie/形坏/签名不合/过期)一律原样放行(session 槽空)——拦截属应用守卫 中间件(need_login 形),with_sessions 只管注入,不越权代拦。

flash 机制(读即清最小形态,记录):载体 cookie web_flash=<penc msg> (会话 cookie,无 Max-Age)。Req 是值、读侧无法就地清——清除挂在 with_sessions 响应侧:入请求携 web_flash 且响应未再写 flash(防 PRG 写后自吞)时自动补 web_flash=; Max-Age=0 清除行。不经 with_sessions 的路由 flash 不自清(登记边界;req_flash 单独可读)。

timeout_ms 诚实口径:返回透传中间件(上限已验,n<=0 装配即 panic); 真超时挂并发波(§8.2 v1 阻塞串行档,阻塞调用无中断点)——校验语义面, 并发 RT 落地后在此闭包内换真实现,调用点零改(§4.5)。

use 面:web.core(Resp/Req 取参族)+ http.frm.auth(收编面)+ std.str (starts_with);std.crypto 不直 use(auth 单叶,消费方再入即同叶双径 E5020)。

pub struct

struct Pair

Field Type
k Str
v Str

struct Resp

Field Type
status I32
headers List[Pair]
body Str

struct JObj

Field Type
parts List[Str]

struct Req[S]

Field Type
method Str
path Str
query Str
body Str
headers List[Pair]
cookies List[Pair]
params List[Pair]
session List[Pair]
state S

struct RouteSeg

Field Type
kind Str
text Str

struct Route[S]

Field Type
m Str
segs List[RouteSeg]
chain List[fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp]
h fn(&Req[S]) -> Resp

struct Router[S]

Field Type
state S
routes List[Route[S]]
mws List[fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp]
not_found_h List[fn(&Req[S]) -> Resp]
not_allowed_h List[fn(&Req[S]) -> Resp]

struct Match[S]

Field Type
kind I32
route Route[S]
params List[Pair]
splat Str
allow Str

struct AuthTok

Field Type
ok I64
why I64
tok Str

struct TokVer

Field Type
rc I64
why I64
uid Str

struct AuthClaim

Field Type
k I64
v I64

struct JwtVer

Field Type
rc I64
why I64

struct SessStore

Field Type
n I64
e0 I64
e1 I64
e2 I64
e3 I64
e4 I64
e5 I64
e6 I64
e7 I64
s0 Str
s1 Str
s2 Str
s3 Str
s4 Str
s5 Str
s6 Str
s7 Str
r0 Str
r1 Str
r2 Str
r3 Str
r4 Str
r5 Str
r6 Str
r7 Str

struct SessPut

Field Type
ok I64
why I64
st SessStore

struct SessGet

Field Type
rc I64
user Str

pub fn

Signature Returns Description
grant_session(r: Resp, key: Str, uid: Str, ttl: I64, now: I64) Resp 签发:Set-Cookie sid=<tok>(HttpOnly+SameSite=Lax 随,frm/auth 同款 §7-3;
Max-Age=ttl 秒)。uid 出域(frm 构造拒)→ 原样返回不签发。
drop_session(r: Resp, key: Str) Resp 注销:Max-Age=0 即刻删除面(frm/auth 口径)。key 形参留置:服务端吊销面
(存储对偶)属志向,当前无状态令牌注销 = 客户端删 cookie。
with_sessions[S](key: Str, now: fn() -> I64) fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp sid cookie → 验签 → 注入 session 槽;失败原样放行(见头注)。响应侧顺带
执行 flash 读即清(flash_sweep)。
flash(r: Resp, msg: Str) Resp 写:cookie web_flash=&lt;penc msg&gt;(会话 cookie,无 Max-Age;值经百分号编码
出 frm cookie-octet 域雷区,读侧 pdec 还原——core UTF-8 安全解码)
req_flash[S](r: &Req[S]) Str 读(§4.3 冻结名 req.flash()):读即清的「读」半边,清半边在 with_sessions
响应侧(头注 flash 机制)。缺回空。
log_requests[S](r: &Req[S], next: fn(&Req[S]) -> Resp) Resp 访问日志:next 后落一行(此时才有状态可印;§4.5 形态 ① 纯函数)。
行形 "GET /x 200";行缓冲/逐条刷盘政策归 serve(Task 3),本件只 println。
timeout_ms[S](ms: I64) fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp 组级超时档位(§4.5/§8.1):n<=0 装配即 panic(失败在启动,不在半夜);
现阶段透传(头注诚实口径),真实现随并发波在闭包内换血。
html(body: Str) Resp —
json(body: Str) Resp —
text(body: Str) Resp —
redirect(loc: Str) Resp —
status(n: I32, body: Str) Resp —
xml(body: Str) Resp —
yaml(body: Str) Resp —
err_json(n: I32, msg: Str) Resp —
attachment(r: Resp, filename: Str) Resp —
with(r: Resp, k: Str, v: Str) Resp —
with_status(r: Resp, n: I32) Resp —
resp_header(r: Resp, k: Str) Str —
resp_body(r: Resp) Str —
resp_status(r: Resp) I32 —
json_obj() JObj —
str(b: JObj, k: Str, v: Str) JObj —
i64(b: JObj, k: Str, v: I64) JObj —
bool(b: JObj, k: Str, v: Bool) JObj —
strs(b: JObj, k: Str, xs: List[Str]) JObj —
end(b: JObj) Str —
end_status(b: JObj, n: I32) Resp —
csv_rows(headers: List[Str], rows: List[List[Str]]) Str —
req_of[S](method: Str, path: Str, state: S) Req[S] —
req_full[S](method: Str, path: Str, body: Str, hdrs: List[Pair], state: S) Req[S] —
req_set_params[S](r: &Req[S], params: List[Pair]) Req[S] —
req_set_session[S](r: &Req[S], sess: List[Pair]) Req[S] —
pdec(s: Str) Str —
param[S](r: &Req[S], name: Str) Str —
pair_get(ps: List[Pair], k: Str) Str —
param_i64[S](r: &Req[S], name: Str) Option[I64] —
param_f64[S](r: &Req[S], name: Str) Option[F64] —
query[S](r: &Req[S], name: Str) Str —
query_all[S](r: &Req[S], name: Str) List[Str] —
form[S](r: &Req[S], name: Str) Str —
form_all[S](r: &Req[S], name: Str) List[Str] —
header[S](r: &Req[S], name: Str) Str —
cookie[S](r: &Req[S], name: Str) Str —
session[S](r: &Req[S], name: Str) Str —
router[S](state: S) Router[S] —
get[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) Router[S] —
post[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) Router[S] —
put[S](r: Router[S], p: Str, h: fn(&Req[S]) -> Resp) Router[S] —
middleware[S](r: Router[S], mw: fn(&Req[S], fn(&Req[S]) -> Resp) -> Resp) Router[S] —
mount_at[S](r: Router[S], prefix: Str, sub: Router[S]) Router[S] —
has_conflict[S](r: Router[S]) Bool —
install[S](r: Router[S], p: fn(Router[S]) -> Router[S]) Router[S] —
not_found[S](r: Router[S], h: fn(&Req[S]) -> Resp) Router[S] —
method_not_allowed[S](r: Router[S], h: fn(&Req[S]) -> Resp) Router[S] —
test_call[S](r: Router[S], method: Str, path: Str, body: Str) Resp —
route_match[S](r: Router[S], m: Str, path: Str) Match[S] —
dispatch_h[S](r: Router[S], method: Str, path: Str, body: Str, hdrs: List[Pair]) Resp —
dispatch[S](r: Router[S], method: Str, path: Str, body: Str) Resp —
auth_err_ok() I64 —
auth_err_empty() I64 —
auth_err_shape() I64 —
auth_err_uid() I64 —
auth_err_expnum() I64 —
auth_err_sigform() I64 —
auth_err_sig() I64 —
auth_err_expired() I64 —
auth_err_b64() I64 —
auth_err_alg() I64 —
auth_err_nbf() I64 —
auth_err_iat() I64 —
auth_err_claim() I64 —
auth_err_full() I64 —
auth_cookie_get(cookie_hdr: Str, name: Str) Str —
auth_cookie_set(name: Str, value: Str, maxage: I64, httponly: I64) Str —
at_ok(t: AuthTok) I64 —
at_tok(t: AuthTok) Str —
auth_tok_issue(key: Str, uid: Str, now: I64, ttl: I64) AuthTok —
tv_rc(v: TokVer) I64 —
tv_why(v: TokVer) I64 —
tv_uid(v: TokVer) Str —
auth_tok_verify(key: Str, tok: Str, now: I64) TokVer —
auth_b64url_encode(src: Str) Str —
auth_jwt_hdr() Str —
ac_k(c: AuthClaim) I64 —
ac_v(c: AuthClaim) I64 —
auth_jwt_claim(payload: Str, name: Str) AuthClaim —
auth_jwt_sign(key: Str, payload: Str) AuthTok —
auth_jwt_hdr_b64() Str —
jwt_rc(v: JwtVer) I64 —
jwt_why(v: JwtVer) I64 —
auth_jwt_verify(key: Str, tok: Str, now: I64) JwtVer —
auth_sess_cap() I64 —
auth_sess_new() SessStore —
sp_ok(p: SessPut) I64 —
sp_why(p: SessPut) I64 —
sp_st(p: SessPut) SessStore —
auth_sess_put(t: SessStore, sid: Str, user: Str, now: I64, ttl: I64) SessPut —
sg_rc(g: SessGet) I64 —
sg_user(g: SessGet) Str —
auth_sess_get(t: SessStore, sid: Str, now: I64) SessGet —
auth_sess_del(t: SessStore, sid: Str) SessStore —
auth_sess_gc(t: SessStore, now: I64) SessStore —
auth_sess_len(t: SessStore) I64 —