会话安全管理
本文引用的文件
- inproc_vjsx_host_session_store_api.v
- inproc_vjsx_websocket_session_store_js.v
- state_store.v
- store.v
- session.v
- dispatch_session.v
- websocket_session_delivery_projection.v
- hub_runtime.v
- state.v
- db_runtime_pool.v
目录
简介
本文件围绕 vhttpd 的“会话”能力,系统性梳理其存储机制、并发控制、安全性与清理策略。重点覆盖: - 内存存储、持久化存储(文件系统)以及面向分布式扩展的设计要点 - 并发控制:锁机制、版本控制与冲突解决 - 安全性:会话固定攻击防护、CSRF 保护、会话劫持防护 - 清理策略与性能优化建议
项目结构
vhttpd 将“会话”抽象为可插拔的存储接口,并通过宿主 API 暴露给 VJSX 应用层;同时提供基于文件的持久化实现用于管理态观测与调试。
graph TB
subgraph "VJSX 应用层"
JS["WebSocket/业务逻辑<br/>使用 runtime.sessionStore()"]
end
subgraph "宿主桥接"
HostAPI["宿主会话存储 API<br/>解析请求/响应"]
WSFacade["WS 会话存储 JS 门面<br/>封装 host 调用"]
end
subgraph "存储后端"
Mem["内存状态存储<br/>带 TTL 与 CAS"]
File["文件持久化存储<br/>命名空间+键=文件"]
end
JS --> WSFacade --> HostAPI --> Mem
HostAPI --> File
图示来源 - inproc_vjsx_websocket_session_store_js.v:1-207 - inproc_vjsx_host_session_store_api.v:1-136 - state_store.v:1-274 - store.v:1-194
章节来源 - inproc_vjsx_websocket_session_store_js.v:1-207 - inproc_vjsx_host_session_store_api.v:1-136 - state_store.v:1-274 - store.v:1-194
核心组件
- 内存状态存储(MemoryStateStore)
- 线程安全:内部互斥锁保护所有读写
- 支持 TTL 过期判断与惰性删除
- 提供 compare-and-swap 原子更新/删除,用于无锁乐观并发控制
- 宿主会话存储 API(Host Session Store API)
- 暴露 get/set/patch/delete/exists/keys 等操作
- patch 操作通过 CAS 返回冲突信号,供上层重试或降级
- WebSocket 会话存储 JS 门面
- 在 VJSX 侧提供 sessionStore(namespace) 统一入口
- 自动序列化/反序列化 JSON,并透传 TTL
- 管理态文件存储(FileStore)
- 以命名空间+键映射到文件,便于审计与排障
- 事件追加写入 events.jsonl,支持按类型查询
章节来源 - state_store.v:1-274 - inproc_vjsx_host_session_store_api.v:1-136 - inproc_vjsx_websocket_session_store_js.v:1-207 - store.v:1-194
架构总览
下图展示从 VJSX 应用到宿主 API 再到存储后端的完整调用链,以及 WebSocket 会话生命周期中的关键节点。
sequenceDiagram
participant App as "VJSX 应用"
participant Facade as "WS 会话存储 JS 门面"
participant Host as "宿主会话存储 API"
participant Store as "内存/文件存储"
App->>Facade : sessionStore("namespace").get(key, fallback)
Facade->>Host : {op : "get", namespace, key}
Host->>Store : get(full_key)
Store-->>Host : value or not found
Host-->>Facade : ok/found/value
Facade-->>App : 解析后的值或回退
App->>Facade : sessionStore("namespace").set(key, value, {ttlMs})
Facade->>Host : {op : "set", namespace, key, value, ttl_ms}
Host->>Store : set_with_ttl/full_key
Store-->>Host : ok
Host-->>Facade : ok
Facade-->>App : true/false
图示来源 - inproc_vjsx_websocket_session_store_js.v:1-207 - inproc_vjsx_host_session_store_api.v:1-136 - state_store.v:1-274
详细组件分析
内存状态存储(并发与一致性)
- 并发控制
- 所有方法均持有互斥锁,保证单进程内线性一致
- keys/list/prune_expired 等扫描类操作会惰性清理过期项
- 版本控制与冲突解决
- compare_and_swap_set_with_ttl / compare_and_swap_delete 提供 CAS 语义
- 上层 patch 操作若失败,返回 conflict 标志,由应用层决定重试或回退
- 复杂度
- get/set/delete/exists:O(1)
- keys/list:O(n) 且含过期清理
- CAS:O(1)
flowchart TD
Start(["进入 patch"]) --> BuildKey["构造 full_key = namespace:key"]
BuildKey --> Op{"操作类型"}
Op --> |set_with_ttl| CASSet["CAS 设置(期望存在/值, 新值, TTL)"]
Op --> |delete| CASDel["CAS 删除(期望存在/值)"]
CASSet --> SwapOK{"交换成功?"}
CASDel --> SwapOK
SwapOK --> |是| ReturnOk["返回 ok=true"]
SwapOK --> |否| ReturnConflict["返回 ok=false, conflict=true"]
图示来源 - inproc_vjsx_host_session_store_api.v:73-100 - state_store.v:212-273
章节来源 - state_store.v:1-274 - inproc_vjsx_host_session_store_api.v:1-136
WebSocket 会话存储 JS 门面
- 职责
- 在 VJSX 运行时注入 sessionStore(namespace)
- 将 get/set/delete/exists/keys 包装为 JSON 请求,调用宿主 API
- 对返回值进行 JSON 解析与错误回退
- 设计要点
- 空命名空间时直接返回默认值,避免无效 I/O
- 所有宿主调用均做防御性检查(是否存在、是否函数)
classDiagram
class WS_Store_Facade {
+get(namespace, key, fallback)
+set(namespace, key, value, options)
+delete(namespace, key)
+exists(namespace, key)
+keys(namespace, fallback)
}
class Host_API {
+session_store_builder(ctx, idx)
}
class Memory_Store {
+get(key)
+set_with_ttl(key, val, ttl)
+compare_and_swap_set_with_ttl(...)
+compare_and_swap_delete(...)
+keys()
}
WS_Store_Facade --> Host_API : "JSON 请求/响应"
Host_API --> Memory_Store : "调用"
图示来源 - inproc_vjsx_websocket_session_store_js.v:1-207 - inproc_vjsx_host_session_store_api.v:1-136 - state_store.v:1-274
章节来源 - inproc_vjsx_websocket_session_store_js.v:1-207
管理态文件存储(持久化与审计)
- 数据模型
- Entry:包含 namespace、key、value、updated_at_unix
- Event:事件记录,追加至 events.jsonl
- 特性
- 命名空间与键经白名单清洗,防止路径穿越
- 原子写入(临时文件+重命名),保障一致性
- 列表/读取按文件名排序,便于观察
flowchart TD
Put(["put(namespace,key,value)"]) --> Sanitize["清洗 namespace/key"]
Sanitize --> Path["计算 entry_path(ns,key).json"]
Path --> AtomicWrite["原子写入(写tmp+mv)"]
AtomicWrite --> ReturnEntry["返回 Entry(updated_at_unix)"]
图示来源 - store.v:57-72 - store.v:187-194
章节来源 - store.v:1-194
中继与会话路由(连接级会话)
- 作用
- 维护 session_id -> endpoints 映射,按 link_id 聚合端点
- 缓冲未决帧,限制上限,避免内存膨胀
- 清理
- 当某 session 的 endpoints 与 pending 均为空时,自动移除
classDiagram
class SessionRegistry {
+open_endpoint(endpoint)
+close_endpoint(session_id, endpoint_id) bool
+targets(session_id, link_id, role, except) []Endpoint
+buffer_pending(session_id, link_id, frame, limit) !
+drain_pending(session_id, link_id) []Frame
}
class RelaySession {
+id string
+endpoints map[string]RelayEndpoint
+endpoints_by_link map[string][]string
+pending_by_link map[string][]WireFrame
}
SessionRegistry --> RelaySession : "维护"
图示来源 - session.v:1-142
章节来源 - session.v:1-142
WebSocket 会话分发与交付
- 生命周期
- 建立连接后注册 conn_id,触发 open 处理与激活
- 本地关闭流程标记 closing,并通知远端
- 交付结果
- 根据 worker 返回的命令集构建交付结果,携带元数据(如 affinity_key)
sequenceDiagram
participant Hub as "Hub Runtime"
participant Bridge as "Dispatch Bridge"
participant RT as "Runtime"
participant Worker as "Worker"
Bridge->>RT : register_conn(conn_id, ...)
Bridge->>Bridge : process_open()
Bridge->>Bridge : activate()
Bridge->>RT : flush_pending(conn_id)
Worker-->>Bridge : dispatch response (commands)
Bridge->>RT : followup_failure(..., failures) ?
图示来源 - dispatch_session.v:37-89 - hub_runtime.v:34-43 - websocket_session_delivery_projection.v:1-42
章节来源 - dispatch_session.v:37-89 - websocket_session_delivery_projection.v:1-42
MCP 会话管理(示例:内存会话表)
- 功能
- ensure_session 创建/更新会话,维护 last_activity_unix
- 最大会话数阈值下触发驱逐
- 并发
- 使用互斥锁保护临界区
flowchart TD
Ensure["ensure_session(session_id,...)"] --> Lock["加锁"]
Lock --> Prune["prune_sessions_locked(now)"]
Prune --> Exists{"已存在?"}
Exists --> |是| Update["更新字段与时间戳"]
Exists --> |否| Evict["达到上限则驱逐一个"]
Evict --> Create["新建会话并加入表"]
Update --> Unlock["解锁并返回"]
Create --> Unlock
图示来源 - state.v:55-100
章节来源 - state.v:55-100
依赖关系分析
- 组件耦合
- VJSX 仅依赖 JS 门面,不感知宿主与存储细节
- 宿主 API 作为唯一适配层,屏蔽不同后端差异
- 内存存储与文件存储彼此独立,可通过宿主 API 组合使用
- 外部依赖
- 数据库会话绑定:通过 db_runtime_acquire_conn 支持按 session_id 获取事务会话句柄,便于跨请求关联
graph LR
VJSX["VJSX 应用"] --> Facade["WS 会话存储 JS 门面"]
Facade --> Host["宿主会话存储 API"]
Host --> Mem["内存状态存储"]
Host --> File["文件持久化存储"]
DBPool["DB 运行时池"] --> |按 session_id 获取| Conn["会话句柄"]
图示来源 - inproc_vjsx_websocket_session_store_js.v:1-207 - inproc_vjsx_host_session_store_api.v:1-136 - state_store.v:1-274 - store.v:1-194 - db_runtime_pool.v:58-103
章节来源 - db_runtime_pool.v:58-103
性能与扩展性
- 性能特征
- 内存存储:O(1) 读写,适合热点会话;TTL 惰性清理降低后台压力
- 文件存储:适合审计与调试,不适合高频热路径
- CAS 操作避免全局锁竞争,提升并发吞吐
- 扩展建议
- 分布式存储:可在宿主 API 层替换为 Redis/Memcached 等,保持 get/set/patch/exists/keys 契约不变
- 批量 keys:当前 keys 为全量过滤前缀,高基数场景建议引入索引或分片命名空间
- 清理策略:定期 prune_expired 或在 keys/list 时惰性清理,避免集中式 GC 抖动
[本节为通用指导,无需源码引用]
安全配置指南
- 会话固定攻击防护
- 建议在会话创建后轮换会话标识(例如在认证成功后重新生成 session_id),并在旧标识失效前拒绝使用
- 结合 TLS 终止与严格 Cookie 属性(Secure、HttpOnly、SameSite)降低泄露风险
- CSRF 保护
- 对状态变更的表单/接口启用 CSRF 校验(同源策略、Samesite、双重提交或自定义 Token)
- 参考文档中关于 veb.csrf 的使用说明,仅在必要路由启用中间件
- 会话劫持防护
- 强制 HTTPS、设置 Strict-Transport-Security
- 绑定用户代理指纹/IP 段(谨慎使用,考虑 NAT/移动网络)
- 短 TTL、频繁刷新会话令牌,异常行为检测与主动注销
- 输入与路径安全
- 管理态文件存储对命名空间与键进行白名单清洗,避免路径穿越
- 对外暴露的管理接口应鉴权与限流
章节来源 - store.v:167-185
故障排查
- 常见问题定位
- 会话不存在/过期:检查 TTL 设置与惰性清理时机
- 并发冲突:patch 返回 conflict 时,应用层需重试或降级
- WebSocket 会话未激活:确认 register_conn 与 flush_pending 是否执行
- 诊断手段
- 使用管理态文件存储查看命名空间下的键值与更新时间
- 通过事件日志 events.jsonl 追踪 put/delete 等操作
- 观察 WebSocket 交付结果元数据(如命令数量、亲和键)
章节来源 - state_store.v:186-210 - store.v:111-153 - websocket_session_delivery_projection.v:25-41
结论
vhttpd 的会话体系以“宿主 API + 多后端存储”为核心,提供线程安全的内存实现与可审计的文件持久化,并通过 CAS 支持乐观并发。结合 WebSocket 分发与 MCP 会话管理等模块,形成从协议接入到业务状态的闭环。在生产环境中,建议: - 将热路径迁移至高性能分布式存储(Redis 等) - 完善会话固定与 CSRF 防护策略 - 建立会话清理与监控告警机制,确保资源可控与安全合规