
iii 浏览器即 Worker用 iii-browser-sdk 与 RBAC 监听器把前端接入引擎实时总线【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii本文是 Linkly 短链接教程章节概览第 7 章的技术深读让一个浏览器标签页成为 iii 引擎上的普通 Worker——通过 WebSocket 直连引擎直接调用link::create无需 REST API 网关、订阅clicks实时流更新计数器并注册一个供服务端回调的user::confirm_destructive_op函数完成人工确认。读完本文你将掌握iii-worker-manager双监听器 RBAC 鉴权函数的安全接入模型、iii-browser-sdk的完整用法以及浏览器 Worker 与服务端 Worker 无本质区别的 iii 核心心智模型。为什么浏览器要成为 Workeriii 的架构思想是每个能力都由一个 Worker 提供Worker 之间通过引擎互相调用函数。第 7 章把这个思想延伸到浏览器前端不再用fetch打 REST 接口而是建立一个持久的 WebSocket 连接像其他任何 Worker 一样直接调用服务端函数浏览器端worker.trigger({ function_id: link::create, ... })请求/响应与服务器 Worker 之间完全一致订阅实时流订阅第 5 章Stream live clicks建立的clicks流每次重定向命中都实时推送到页面注册被回调函数浏览器注册user::confirm_destructive_op服务端删除前先向浏览器征求人工确认。因为浏览器是不可信的客户端不像本地 Worker 在可信环境iii 用iii-worker-manager的 RBAC 能力为它单独开一条受控的监听通道并用一个专门的authWorker 做连接准入。添加 workersworker-manager 与 auth浏览器 Worker 通过iii-worker-manager的 RBAC 监听器连接与本地 Worker 使用的可信端口分开。鉴权逻辑封装在一个独立的authWorker 中这样linkWorker 保持专注。按第 1 章搭建link的方式同样脚手架出authiii worker add iii-worker-manager iii worker init auth --language typescriptiii worker add name从 iii 注册中心把已发布的 Worker 拉进当前项目iii worker init生成一个 TypeScript Worker 骨架。iii-worker-manager是引擎强制持有的 Worker负责为 SDK Worker 打开 WebSocket 监听器——关于它的完整职责与配置可阅读 iii-worker-manager 实现文档。跑两个监听器可信端口与浏览器端口引擎内置端口49134是可信监听器本地 Workerlink Worker、analytics Worker都连到这里浏览器绝不能连它。为此在config.yaml中配置两个iii-worker-manager条目一个可信的本地 Worker 继续用替代引擎内置 49134一个 RBAC 门控的3110端口专门给浏览器workers: # ... # Trusted listener for local workers. Replaces the engines built-in 49134. - name: iii-worker-manager config: port: 49134 # Browser-facing listener. The auth function gates every connection; only the # functions in expose_functions are reachable from sessions it admits. - name: iii-worker-manager config: host: 127.0.0.1 port: 3110 rbac: auth_function_id: auth::browser expose_functions: - match(link::create) - match(link::request_delete) - match(stream::*)其中expose_functions是白名单声明浏览器会话可以调用哪些函数。支持两种过滤器形态match(pattern)通配符匹配*匹配任意字符、两端锚定见 rbac_config.rs 中的 WildcardPattern 实现以及按函数注册metadata匹配多个过滤器之间是 OR 关系auth_function_id指定一个函数iii-worker-manager在每个连接上调用它以决定放行或拒绝下一节就写它。从引擎仓库的真实配置也能看到这种engine 内置 worker的组织方式worker-compose.yaml 中engine.workers下挂载 iii-stream、configuration 等引擎生命周期 WorkerDockerfile 暴露的端口49134 3111 3112也印证了 49134 作为主监听端口的地位。iii-worker-manager支持多条#instance独立监听器典型生产形态就是内部 49134 外部 RBAC 端口见 README 的 Multiple Listeners 一节。用 auth 函数门控连接authWorker 全权负责连接准入。auth::browser在每次浏览器连接时运行一次它收到该请求的headers、query_params和ip_address返回会话的权限允许/拒绝的函数增量、任意上下文抛异常即拒绝。替换生成的auth/src/index.tsimport { registerWorker } from iii-sdk; import { Logger } from iii-dev/observability; const worker registerWorker(process.env.III_URL ?? ws://localhost:49134, { workerName: auth, }); const logger new Logger(); worker.registerFunction( auth::browser, async (input: { headers: Recordstring, string; query_params: Recordstring, string[]; ip_address: string; }) { const token input.query_params.token?.[0]; if (!token || token ! (process.env.LINKLY_BROWSER_TOKEN ?? dev-token)) { throw new Error(unauthorized); } return { allowed_functions: [], forbidden_functions: [], allow_trigger_type_registration: false, allow_function_registration: true, context: { source: browser }, }; }, ); logger.info(auth worker ready);生产环境会在会话存储中查 token但返回结构不变。token 之所以放在查询参数里是因为浏览器无法发送自定义 WebSocket 头——这是浏览器环境与 Node 环境的天然差异SDK 文档也明确指出浏览器连接通过 query parameter 或 cookie 鉴权、headers选项会被忽略见 iii-browser-sdk README。返回值即 RBAC 的AuthResult引擎侧解析逻辑见 rbac_session.rs各字段语义如下默认值以 worker README 的 AuthResult 表 为准字段默认值含义allowed_functions[]在expose_functions之外额外放行的函数 IDforbidden_functions[]即使命中expose_functions也拒绝的函数 ID优先级最高allowed_trigger_types省略全部放行该 Worker 可注册的触发器类型allow_trigger_type_registrationfalse是否允许注册新的触发器类型allow_function_registrationtrue是否允许注册新函数function_registration_prefix省略设置后该会话注册的函数自动加{prefix}::前缀实现会话级私有命名空间context{}随每次调用转发给中间件/注册钩子的任意上下文每次经过 RBAC 监听器的调用按固定顺序判定见 Access Resolution Order先查forbidden_functions拒绝→allowed_functions放行→ 基础设施函数放行 →expose_functions命中放行→ 否则拒绝。其中基础设施放行是iii-worker-manager公开契约里固定的函数切片如engine::workers::register、engine::log::*、engine::baggage::*等保证连接建立、日志、上下文传播不依赖运维者的过滤器。注册 auth Workeriii worker add ./auth服务端发起的删除先征求浏览器确认先给linkWorker 一个link::delete同时删除数据库记录和iii-state缓存。加到link/src/index.tsworker.registerFunction(link::delete, async (payload: { code: string }) { await worker.trigger({ function_id: database::execute, payload: { db: DB, sql: DELETE FROM links WHERE code ?, params: [payload.code] }, }); await worker.trigger({ function_id: state::delete, payload: { scope: links, key: payload.code }, }); logger.info(link deleted, { code: payload.code }); return { deleted: true }; });再包一层link::request_delete先调用浏览器注册的函数征求确认只有确认通过才真正删除。服务端worker.trigger一个浏览器注册的函数与服务器 Worker 之间互相 trigger 是同一个原语只是方向反转worker.registerFunction(link::request_delete, async (payload: { code: string }) { const { confirmed } await worker.trigger { code: string; action: string }, { confirmed: boolean } ({ function_id: user::confirm_destructive_op, payload: { code: payload.code, action: delete link ${payload.code} }, }); if (!confirmed) { return { deleted: false }; } await worker.trigger({ function_id: link::delete, payload: { code: payload.code } }); return { deleted: true }; });注意这里user::confirm_destructive_op不在expose_functions白名单里——白名单管的是浏览器能调用哪些函数而引擎/服务端回调浏览器注册的函数由浏览器侧的registerFunction与会话的allow_function_registration决定方向相反、互不冲突。脚手架前端初始化 Vite 项目在linkly/frontend/下创建 Vite React TypeScript 应用npm create vitelatest frontend -- --template react-tsVite 可能会询问 Install with npm and start now这里选 no因为我们首先要安装iii-browser-sdk。安装依赖cd frontend npm install npm install iii-browser-sdkiii-browser-sdk是专为浏览器环境设计的 SDK纯 WebSocket、无 Node.js 依赖、无 OpenTelemetry在任何提供原生WebSocket的浏览器环境都能运行见 iii-browser-sdk README。registerWorker会自动建立连接可选的InitOptions支持invocationTimeoutMs默认 30000ms、reconnectionConfig指数退避重连等见 src/iii.ts 中 InitOptions 定义。配置客户端 Worker在src/iii.ts中接入 SDKimport { registerWorker } from iii-browser-sdk; const TOKEN import.meta.env.VITE_LINKLY_TOKEN ?? dev-token; export const worker registerWorker(ws://localhost:3110?token${encodeURIComponent(TOKEN)});连接地址指向 RBAC 监听器3110token 走查询参数浏览器无法设自定义头与 auth 函数读取query_params.token的方式对应。VITE_LINKLY_TOKEN是 Vite 约定的环境变量前缀通过.env文件注入。编写应用下面分片构建src/App.tsx可直接替换模板文件。导入与类型Click是clicks表的一行StreamEvent是iii-stream投递给订阅者的包装结构import { useEffect, useState } from react; import { worker } from ./iii.js; type Click { code: string; clicked_at: string }; type StreamEvent { event: { type: create | update | delete; data: Click }; };客户端状态表单字段、新建的链接、实时点击计数export default function App() { const [url, setUrl] useState() const [code, setCode] useState() const [created, setCreated] useState{ code: string; url: string } | null(null) const [clicks, setClicks] useState(0) const [latest, setLatest] useStateClick | null(null)订阅clicks流useEffect注册浏览器暴露的函数ui::on_click和一个stream触发器把每条新记录路由给它清理函数在卸载时反注册两者useEffect(() { const fn worker.registerFunction(ui::on_click, async (event: StreamEvent) { setClicks((n) n 1); setLatest(event.event.data); return null; }); const trig worker.registerTrigger({ type: stream, function_id: ui::on_click, config: { stream_name: clicks, group_id: all }, }); return () { trig.unregister(); fn.unregister(); }; }, []);stream_name: clicks、group_id: all正是第 5 章click-streamer用stream::set广播的目标流见 streaming 章节。iii-browser-sdk/stream子路径导出了完整的流 API 类型StreamTriggerConfig、StreamSetInput等可参考 Browser SDK 参考文档。注册人工确认函数注册服务端回调的人工确认函数弹原生window.confirm并返回用户决定。该函数与其他章节注册的函数在注册与运行机制上完全一致。除了鉴权与权限管理之外客户端与服务端函数没有任何功能差异。useEffect(() { const fn worker.registerFunction( user::confirm_destructive_op, async (data: { action: string; code: string }) { const confirmed window.confirm(Confirm: ${data.action}?); return { confirmed }; }, ); return () fn.unregister(); }, []);直接创建链接无网关提交表单直接调用link::create。这里没有fetch或 REST API 挡路浏览器里的客户端 Worker 与其他任何 Worker 的工作方式完全相同。async function onSubmit(e: React.FormEvent) { e.preventDefault(); const link await worker.trigger{ url: string; code?: string }, { code: string; url: string }({ function_id: link::create, payload: { url, code: code || undefined }, }); setCreated(link); setUrl(); setCode(); }worker.trigger的泛型签名TriggerRequestTInput PromiseTOutput支持同步请求/响应、TriggerAction.Void()即发即忘、TriggerAction.Enqueue({ queue })入队三种路由见 sdk-browser 参考。渲染 UI最后是 UI短链表单、最近创建的链接、实时流式点击计数return ( main h1Linkly/h1 form onSubmit{onSubmit} labelURL input value{url} onChange{(e) setUrl(e.target.value)} required //label labelCode (optional) input value{code} onChange{(e) setCode(e.target.value)} //label button typesubmitShorten/button /form {created ( p Created code{created.code}/code → code{created.url}/code. /p )} section h2Live clicks: {clicks}/h2 {latest ( pLast: code{latest.code}/code at code{latest.clicked_at}/code/p )} /section /main ) }运行验证启动前端npm run dev打开浏览器Vite 通常把本地站点托管在http://localhost:5173。缩短链接实时观察访问流在表单里缩短一个链接然后访问http://localhost:3111/s/code几次你会看到 Live clicks 计数器实时上涨——每次重定向由link::record_click发布link.clicked事件、click-streamer广播到clicks流、浏览器触发器ui::on_click递增计数整条链路都是 iii 总线上的函数调用。从后端直接请求用户确认通过 iii 在浏览器里运行一个函数iii trigger link::request_delete codecode浏览器会弹出确认框只有点击 OK 后服务端才执行删除。小结客户端的本质是一个与其他 Worker 完全相同的 Worker。区别只在于接入路径它通过iii-worker-manager的 RBAC 门控监听器auth_function_id准入 expose_functions白名单接入因为浏览器不像其他本地 Worker 那样可信。而这种门控方式可以推广到任何Worker——任何不可信或半可信的 Worker 都可以用同样的 RBAC 配置包一层。一切就绪后客户端直接调用服务端函数、订阅流获得实时更新、注册函数供服务端回调——全部发生在与 Linkly 其余部分相同的 iii 总线上。这正是 iii 的核心心智一个系统就是一组小 Worker 通过引擎互相调用函数浏览器只是其中的又一个 Worker完整七章路径见 教程概览。【免费下载链接】iiiEffortlessly compose, extend, and observe every service in real-time for the first time ever.项目地址: https://gitcode.com/GitHub_Trending/mo/iii创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考