Puter Worker 路由 handler 怎么接收 request、user、params 并返回不同响应类型?
2026/9/10 7:37:57 网站建设 项目流程

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 只接收一个可解构的参数对象——标题里的requestuserparams都从它来,而 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 路由——把它定义在最后,只让其他路由都没匹配上时执行。

userme:两个 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 响应
字符串纯文本响应
BlobBlob 响应(可指定 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-types
router.get('/posts/:postId/comments/:commentId', ({ params }) => { params.postId; // string params.commentId; // string });

该包声明的 handler 事件包括requestparamsuser/requestor(后者与user同源,仅在带puter-auth头调用时存在),全局对象含routermemymyself为别名)、puter_authputer_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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询