web.static
web 静态文件适配:收编 http.frm.static(ETag/If-None-Match 304/
Range 206·416/穿越拒 全决策面零复制)+ Router .static(前缀, 目录) 装配挂点
(Plan 2b Task 2;设计 docs/superpowers/specs/2026-09-27-web-framework-design.md §4.1;纪律 §14-1)
收编口径(不复制):决策全在 frm——sd handler 把 req.path 剥前缀成 target 后
整体喂 st_serve(root, target, inm, range)(frm 唯一 IO 薄胶:fs_exists/read_file
内建,穿越拒/缺档 404/ETag/304/Range 全由它裁),回来只做「原始 HTTP 报文 → Resp」
翻译(sd_parse:frm 产报文形态确定,状态码恒 9..12 三字节、头区 K: V、\r\n\r\n 分体)。
web 侧补挂一条安全默认 X-Content-Type-Options: nosniff(frm 不发;web §4 纪律)。
边界:目录本尊请求(suffix 空 → target "/")落在 frm 穿越拒 → 403 fail-closed,
非 404——frm 从严即默认,如实继承不另裁。
前缀剥离:static_dir(dir) 产 handler 剥首段(单段前缀/直挂形态);
.static(prefix, dir) 按 prefix 实际段数剥(多段前缀 "/a/b" 逐段剥),经
prefix + "/*sfx" splat 路由装配——handler 读不到 splat(面在 Match,router 在册),
故从 req.path 侧剥。根挂载 prefix=""(0 段)即全树映射 dir 下。
use 面:web.core(Req/Resp/取头)+ web.router(Router/get/装配)+ std.str + http.frm.static 单叶(决策面);fs 内建直呼(fx_fs 夹具口径)。 实测登记(探针 t_probe/t_triv,2026-09-28):byte_slice(start > end) 引导/native 两口径皆死循环挂死——本件一切切片算式先证 start < end 才切(sd_parse 各臂守卫)。
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 |
pub fn
| Signature | Returns | Description |
|---|---|---|
static_dir[S](dir: Str) |
fn(&Req[S]) -> Resp |
§4.1 冻结名面:static_dir(dir) → handler(剥首段;直挂/单段前缀形态) |
static[S](r: Router[S], prefix: Str, dir: Str) |
Router[S] |
Router 装配挂点(§4.1 链式形态 r.static(前缀, 目录);挂 prefix + "/*sfx" splat路由,按前缀段数剥——多段前缀 "/a/b" 亦正)。与 .get 同款值语义返回新 Router; 前缀与既有路由撞表 → r_add 装配期 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 |
— |
st_rc(r: StRange) |
I64 |
— |
st_s(r: StRange) |
I64 |
— |
st_e(r: StRange) |
I64 |
— |
st_range_parse(range: Str, len: I64) |
StRange |
— |
st_slice(s: Str, start: I64, end: I64) |
Str |
— |
st_etag(content: Str) |
Str |
— |
st_inm_match(inm: Str, etag: Str) |
Bool |
— |
st_traversal_ok(target: Str) |
Bool |
— |
st_ctype(name: Str) |
Str |
— |
st_find(hay: Str, needle: Str) |
I32 |
— |
st_resp_from_content(name: Str, content: Str, inm: Str, range: Str) |
Str |
— |
st_serve(root: Str, target: Str, inm: Str, range: Str) |
Str |
— |