Puter Worker 路由 handler 怎么接收 request、user、params 并返回不同响应类型?
【免费下载链接】puter🌐 The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puter
用 Puter Worker 写服务端 API 时,运行时会在你的代码里自动注入一个router对象,你用router.get/post/put/delete/options注册路由,每个 handler 只接收一个可解构的参数对象——标题里的request、user、params都从它来,而 handler 的返回值决定客户端拿到什么样的响应。本文基于 router 文档 及其关联文档,走一遍「定义 handler → 部署 worker → 逐项验证请求参数与响应类型」的完整路径。
准备条件
- 一个已验证邮箱的 Puter 账号,这是创建 worker 的前提(见 create 文档)。
- worker 的 JavaScript 文件不能大于 10MB;worker 名称只能包含字母、数字、连字符和下划线。
- worker 创建或更新后,完全生效需要 5~30 秒传播到所有边缘服务器,测试前需要等一段时间。
handler 的三个参数从哪来
路由 handler 接收一个对象参数,文档定义的可解构属性有三个:
| 属性 | 含义 |
|---|---|
request | 传入的 HTTP 请求,标准Request对象 |
user | 发起本次请求的用户对象,带puter属性(user.puter);仅在 worker 通过puter.workers.exec()被调用时可用 |
params | 从路由路径中捕获的路由参数 |
request:读 body、query 和 headers
request就是标准Request对象,读 JSON body、表单数据、query 字符串、headers 都走它自己的方法:
// JSON body router.post("/api/user", async ({ request }) => { const body = await request.json(); return { processed: true }; }); // 表单数据 router.post("/api/user", async ({ request }) => { const formData = await request.formData(); return { processed: true }; }); // query 字符串 router.get("/api/search", async ({ request }) => { const url = new URL(request.url); const query = url.searchParams.get("q"); return { query }; }); // headers router.post("/api/user", async ({ request }) => { const contentType = request.headers.get("content-type"); return { processed: true }; });以上四段均取自 router 文档的 Examples 部分,可原样照抄。注意 query 参数不在params里,而是从request.url解析。
params::name与*name两种捕获方式
路径中不固定的片段用冒号前缀捕获,每个捕获到的片段按你起的名字成为params上的属性:
router.get("/api/posts/:category/:id", async ({ params }) => { const { category, id } = params; return { category, id }; });按文档说明,请求/api/posts/tech/42匹配该路由后得到params.category→"tech"、params.id→"42"。捕获值永远是字符串,如果预期是数字要自己转换。
*name通配符匹配路径剩余部分(任意多个片段),匹配值同样挂在params上:
router.get("/files/*path", async ({ params }) => { // 请求 /files/images/avatars/me.png 时: // params.path === "images/avatars/me.png" return { path: params.path }; });通配符必须命名:写*path而不是裸*。/files/*里没名字的*会被当作字面字符,路由只能精确匹配/files/*这个路径。通配符的常见用途是兜底 404 路由——把它定义在最后,只让其他路由都没匹配上时执行。
user与me:两个 Puter 上下文
worker 代码里还有全局对象me,代表 worker 的拥有者(你)。两个上下文的分工文档写得很明确:
me.puter是 worker 上下文,操作你的 KV、FS、AI 等资源,计到你名下;user.puter是调用者上下文,只有当 worker 通过puter.workers.exec()执行时才存在——exec()会把用户的 Puter token 放在自定义puter-auth请求头里发过来,user.puter就是靠它填充的。
同一个 worker 里可以混用:有的端点读写自己的数据(me.puter),有的端点操作调用用户的数据(user.puter),操作计到实际调用的那个上下文名下,这是 Puter 的 User-Pays 模型。
handler 能返回哪些响应类型
handler 的返回值被运行时分发成不同响应,文档列出的形式如下:
| 返回什么 | 得到什么响应 |
|---|---|
JS 对象(如{ status: "ok" }) | 自动转换成 JSON 响应 |
| 字符串 | 纯文本响应 |
Blob | Blob 响应(可指定 MIME 类型) |
Uint8Array | 二进制响应 |
ReadableStream | 二进制流式响应 |
Response | 自己控制 status code 和 headers |
对应写法(均出自 router 文档 Examples):
// JSON:对象自动转换 router.get("/api/simple", async ({ request }) => { return { status: "ok" }; }); // 纯文本 router.get("/api/text", async ({ request }) => { return "Hello World"; }); // Blob router.get("/api/blob", async ({ request }) => { return new Blob(["Hello World"], { type: "text/plain" }); }); // Uint8Array router.get("/api/uint8array", async ({ request }) => { return new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]); }); // ReadableStream router.get("/api/binary-stream", async ({ request }) => { return new ReadableStream({ start(controller) { controller.enqueue( new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]) ); controller.close(); }, }); }); // 自定义 status 和 headers router.get("/api/custom", async ({ request }) => { return new Response(JSON.stringify({ data: "custom" }), { status: 200, headers: { "Content-Type": "application/json", "Custom-Header": "value", }, }); });要返回 400/401/404/500 这类错误状态码,同样得自己构造Response并设置status。文档给出的错误处理模式:
router.post("/api/risky-operation", async ({ request }) => { try { const body = await request.json(); const result = await someRiskyOperation(body); return { success: true, result }; } catch (error) { return new Response( JSON.stringify({ error: "Operation failed", message: error.message, }), { status: 500, headers: { "Content-Type": "application/json" }, } ); } });其中someRiskyOperation(body)是文档示例里的占位调用,使用时替换成你自己可能失败的业务逻辑。
写一个可验证的 worker
把上面各形态各取一个端点组装成一个文件,每个端点都能单独验证:
// 健康检查 router.get("/health", async () => { return { status: "ok", timestamp: new Date().toISOString(), }; }); // JSON 对象响应 router.get("/api/hello", async ({ request }) => { return { message: "Hello, World!" }; }); // 读 JSON body router.post("/api/user", async ({ request }) => { const body = await request.json(); return { processed: true }; }); // 路由参数 router.get("/api/posts/:category/:id", async ({ request, params }) => { const { category, id } = params; return { category, id }; }); // 纯文本 router.get("/api/text", async ({ request }) => { return "Hello World"; }); // Blob router.get("/api/blob", async ({ request }) => { return new Blob(["Hello World"], { type: "text/plain" }); }); // Uint8Array router.get("/api/uint8array", async ({ request }) => { return new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]); }); // 二进制流 router.get("/api/binary-stream", async ({ request }) => { return new ReadableStream({ start(controller) { controller.enqueue( new Uint8Array([72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100]) ); controller.close(); }, }); }); // 自定义 status 和 headers router.get("/api/custom", async ({ request }) => { return new Response(JSON.stringify({ data: "custom" }), { status: 200, headers: { "Content-Type": "application/json", "Custom-Header": "value", }, }); }); // user 上下文:读调用者自己的 KV(仅经 puter.workers.exec() 调用时可用) router.get("/api/kv/user/get", async ({ request, user }) => { const url = new URL(request.url); const key = url.searchParams.get("key"); const value = await user.puter.kv.get(key); return { value }; }); // 404 兜底 router.get("/*tag", async ({ params }) => { return new Response( JSON.stringify({ error: "Not found", path: params.tag, availableEndpoints: ["/health", "/api/hello", "/api/posts/:category/:id"], }), { status: 404, headers: { "Content-Type": "application/json" }, } ); });每个片段都来自文档示例;唯一改动是 404 兜底里availableEndpoints数组的内容——文档示例列的是它自己示例工程里的端点,这里换成本文件实际定义的端点,你自己写时列自己的即可。
部署 worker
在能使用 Puter.js 的环境(puter.com 网页、应用或 Node 脚本)里,先把代码写入你账号下的文件,再创建部署:
// 1. 把 worker 代码保存为你账号下的 my-worker.js await puter.fs.write('my-worker.js', workerCode); // 2. 按名称和文件路径部署 const deployment = await puter.workers.create('my-api', 'my-worker.js'); console.log(`Worker deployed at: ${deployment.url}`);workerName用'my-api'这类名称(字母、数字、连字符、下划线),filePath指向 Puter 账号里的 JS 文件路径。返回值是 WorkerDeployment 对象,包含success(Boolean,是否部署成功)、url(String,部署后的地址)、errors(Array,部署错误列表)三个字段。等 5~30 秒传播完成再测试。
替代路径(二选一即可,文档均支持):
- 在 puter.com 上建好
.js文件后,右键选择Publish as Worker,起个名字点击 Publish,worker 即上线在https://your-worker.puter.work(见 Workers 总览)。 - 用 Puter CLI:
npm install -g @heyputer/cli,然后puter worker deploy [file] [name],两个参数都可省略,省略时 CLI 会交互式提示输入文件与名称。文档注明 CLI 目前处于 beta(0.x),命令和行为可能变化。
另外,worker 的名称和 URL 终身不变:后续改代码要覆盖它的源文件(见 更新 worker),不要换新名再 create,否则旧 worker 还挂在旧 URL 上。
验证各端点
公开端点用普通fetch验证,文档给出的测试写法:
// GET const response = await fetch(`${deployment.url}/api/hello`); console.log(await response.text()); // 路由参数:请求 /api/posts/tech/42 const post = await fetch(`${deployment.url}/api/posts/tech/42`); console.log(await post.json()); // 文档示例:{ category: "tech", id: "42" }deployment.url即上一步create()返回的url。按各端点代码的返回值,预期能核对到:/api/hello返回 JSON{ message: "Hello, World!" };/api/posts/tech/42返回{ category: "tech", id: "42" }(文档明确给出该匹配结果);/api/text是纯文本Hello World;/api/custom的响应头里带Custom-Header: value;请求一个不存在的路径会命中/*tag兜底,得到 404 JSON。
POST 端点按 router 文档的测试模式:
const postResponse = await puter.workers.exec(`${workerUrl}/api/user`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ key: "test", value: "hello" }), }); const postData = await postResponse.json(); console.log(postData); // 返回 { processed: true }验证user上下文必须走puter.workers.exec(),因为只有它会自动带上用户会话(puter-auth头):
// workerUrl 即 create() 返回的部署 URL,例如 https://my-api.puter.work const response = await puter.workers.exec(`${workerUrl}/api/kv/user/get?key=hello`); const data = await response.json(); console.log(data); // 文档示例返回形态:{ value }key后的hello是要在调用者自己的 KV 里查询的键,可换成你已写入的值。value字段取到什么取决于该用户 KV 里实际的数据,文档只给出了{ value }这个返回形态,没有承诺固定取值。
边界与限制
user只在puter.workers.exec()调用链上存在。用裸fetch请求依赖user.puter的端点时拿不到调用者上下文;如果端点必须鉴权,文档示例的做法是判断user.puter缺失后返回 401 的Response。- 路由参数和通配符捕获的值永远是字符串,数值转换自己做。
- CORS 由运行时自动处理:每个响应都带
Access-Control-Allow-Origin: *,预检OPTIONS请求自动应答。只有当你自己定义OPTIONShandler 时才接管预检、需要自己补齐响应头;若还要配合puter.workers.exec()使用,必须把puter-auth列进Access-Control-Allow-Headers,否则预检失败、请求根本到不了 worker。 - worker 文件上限 10MB,名称规则、5~30 秒传播窗口见「准备条件」。
可选:给路由参数加类型推导。@heyputer/worker-types包是纯开发期工具,不进入部署产物,安装后从路径字面量推断params的键:
npm install --save-dev @heyputer/worker-typesrouter.get('/posts/:postId/comments/:commentId', ({ params }) => { params.postId; // string params.commentId; // string });该包声明的 handler 事件包括request、params、user/requestor(后者与user同源,仅在带puter-auth头调用时存在),全局对象含router、me(my、myself为别名)、puter_auth、puter_endpoint,详见 types 文档。
改动与重新部署
worker 名称和 URL 终身不变。要更新代码,用puter.workers.get('my-api')查到file_path,把新代码写回该文件即触发同 URL 重新部署,已有的调用方不需要改任何地址(见 create 文档 的 Updating a worker 一节):
const info = await puter.workers.get('my-api'); await puter.fs.write(info.file_path, updatedWorkerCode);【免费下载链接】puter🌐 The Internet Computer! Free, Open-Source, and Self-Hostable.项目地址: https://gitcode.com/GitHub_Trending/pu/puter
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考