@codefusion-cc/workers-http

中文文档
0.1.4发布于 2026/10/26MITTypeScriptES Module近 30 天下载 2141

Cloudflare Worker的API的HTTP管道: 错误被抛出作为答案, API的路线, JSON 的响应, 绑定了 JSON 和身体读取, 客户端的地址和网络, 同源检查, 费率限制 a RatLimit 绑定, cookie, SHA-256 和 a s

中文使用文档
文档目录11 节
  1. 一个地方回答错误
  2. 应用语言
  3. 路线
  4. 阅读机构
  5. 谁打电话
  6. 费率限额
  7. 单页应用程序的页面
  8. 保存在缓存中的答案
  9. 预览和制作
  10. 饼干和大麻
  11. 在应用测试中

@ codumation-cc/ works- http (中文(简体) )

一个Cloudflare工人API的HTTP管道:抛出错误作为答案,一个API的路线,JSON的响应,JSON和身体读取 有限制的,客户的地址和网络,同源的cookie通话检查,对工人的费率限制 RaseLimit绑定,JSON的答案保存在工人的缓存, cookie, SHA-256, 以及一个单页的应用程序的页面 访问者写了地址 它运行在工人 和任何运行时间 Request,流和WebCrypto(20+节点) 一个依赖是 @codefusion-cc/workers-crypto。 。 。 .

npm install @codefusion-cc/workers-http

一个地方回答错误

手摇 HttpError 任何不是幸福的答案, 也一样 readJson, (中文(简体) ), readBody, (中文(简体) ), requireSameOrigin, (中文(简体) ), rateLimit页:1 Router是因为 route 财务报告和审定财务报表 serveSinglePageApp工人将任何被扔到一个地方的反应:

import { clientNetwork, errorResponse, HttpError, json, rateLimit, readJson, requireSameOrigin } from '@codefusion-cc/workers-http'

export default {
  async fetch(request, env) {
    try {
      return await route(request, env)
    } catch (error) {
      return errorResponse(error, { request }) // HttpError: its status and body; anything else: a 500 that reveals nothing, logged
    }
  },
} satisfies ExportedHandler<Env>

async function signIn(request: Request, env: Env) {
  requireSameOrigin(request, [env.DEV_ORIGIN])                     // 403 for a page on another site
  await rateLimit(env.AUTH_LIMITER, clientNetwork(request))        // 429 with retry-after
  const { email } = await readJson<{ email?: unknown }>(request)   // 415, 413, 400
  if (typeof email !== 'string') throw new HttpError(422, 'Enter your e-mail', { fieldErrors: { email: 'Required' } })
  return json({ ok: true })
}

一个 HttpError 答题 { error, code?, fieldErrors?, ...data } 及其地位和 headers它的地位必须是: 400至599(其他任何东西都投出RangeError). errorResponse 还回答另一个安装的复制件所作的一个 包( 哪个) instanceof 因此,在另一个软件包中抛出的错误仍然得到其状态.

鉴于 request, (中文(简体) ), errorResponse 也取消了一个没有读到的身体, 所以一个上传在读之前被拒绝了 (a 401 on a PUT) 即刻停止,而不会跑到它的尽头.

json(data, init) 发送 application/json; charset=utf-8 财务报告和审定财务报表 cache-control: no-store 除非 init 套装 他们. init.headers 可能是一个 Headers 数个 set-cookie。 。 。 . jsonError(status, message) 是同一个身体 作为被丢弃的HtpError, 为返回而不是扔.

应用语言

包裹信息为英语. errorResponse 用代码交换应用程序的措辞,留下任何 信件, 应用程序单独写( e)readBody是因为 message,它自己的 Http 错误 :

errorResponse(error, {
  messages: {
    unsupported_media_type: 'Wyślij dane jako JSON.',
    body_too_large: 'Za duże zapytanie.',
    length_required: 'Wyślij zapytanie z nagłówkiem Content-Length.',
    unreadable_body: 'Nie udało się odczytać zapytania.',
    invalid_json: 'Nieprawidłowe dane.',
    cross_origin: 'Odrzucono zapytanie z innej strony.',
    rate_limited: 'Zbyt wiele prób. Odczekaj minutę i spróbuj ponownie.',
    not_found: 'Nie znaleziono.',
    method_not_allowed: 'Ten adres nie przyjmuje takiego zapytania.',
    server_error: 'Wystąpił błąd serwera. Spróbuj ponownie.',
  },
  onError: error => console.error('unhandled', error), // console.error by default; a throwing logger cannot break the answer
})

路线

Router 保留 API 的路径, 方法和路径 任何应用程序称为处理器 用于:

import { errorResponse, json, Router } from '@codefusion-cc/workers-http'

type Handler = (request: Request, env: Env, params: Record<string, string>) => Response | Promise<Response>

const api = new Router<Handler>()
  .on('GET', '/api/orders/new', () => json({ draft: true }))       // before the parameter that would take "new"
  .on('GET', '/api/orders/:id', (_request, _env, { id }) => json({ id }))
  .on('DELETE', '/api/orders/:id', removeOrder)

export default {
  async fetch(request, env) {
    try {
      const { handler, params } = api.route(request.method, new URL(request.url).pathname)  // 404, 405
      return await handler(request, env, params)
    } catch (error) {
      return errorResponse(error)
    }
  },
} satisfies ExportedHandler<Env>
  • A 类 :name 段是一个参数: 它需要一个非空段, 它的值会到达 已解码的% (%)Bosch%20%2B 实值 Bosch +; 其编码被破损者将保持发送状态,解码值可以 拿着 /) (中文(简体) ). 所有其它部分都碰巧碰面 信箱和后面的斜线 以及ASCII以外的文本 写得很清楚: /api/zamówienia 满足浏览器的 /api/zam%C3%B3wienia。 。 。 .
  • 在对固定文本进行比较之前,不读取请求中的脱逃: /api/%61dmin/users 没有 /api/admin/users (该词 是一个404,或者一个参数的值。 因此,一个检查应用程序 在路径之前的路径,例如 pathname.startsWith('/api/admin/'),而且路由器总是同意一个请求的路径。 参数 值被解码,所以 /api/:area/users 获得 admin 从 %61dmin: 处理器从不为 路径.
  • 第一条路线增加了符合答案的路线. 一个方法被满足了,但是 HEAD: 一个路径的 GET 路由应答 当路径没有时 HEAD 它自己的路线,运行时间发送 答案没有它的躯体.
  • route(method, pathname) 掷出404枚 not_found 对于一条没有路径,和 405 method_not_allowed 与 allow (路径确实回答的方法) 对于一个只有其他方法的路径. match 返回为无效 对于两者, 对于一个应用 答案自己.
  • on 为编程错误扔出 TypeError : 一个不是大写字母的方法, 一个不是一个路径 (无领导) /页:1 ?页:1 #页:1 ..页:1 : 部分不是 :name,在一个路径中两次参数,和一个 第二条路由 对于一个方法 和路径有一个, 它永远无法回答.

阅读机构

打电话拒绝
readJson(request, options)第415条 unsupported_media_type: 内容类型不 application/json 或者说 application/*+json. 413 (中文(简体) ) body_too_large. 400 (中文(简体) ) unreadable_body. 400 (中文(简体) ) invalid_json:没有身体,没有UTF-8,没有JSON,或者没有物体.
readBody(request, options)413号 body_too_large、400个 unreadable_body (上传中断). 解析字节;无正文为空字节. 也需要一个答复,例如从另一个服务处得到的.
streamBody(request, { maxBytes }, store)411号 length_required:无Content-Length,或非整数. 413号 body_too_large. 400 (中文(简体) ) unreadable_body: 或多或少比所申报的字节. 电话 store(body, size) 身体是一条溪流 size 字节,用于一个需要一串已知长度的流的商店,解决它所解决的问题.
  • maxBytes (默认) MAX_BODY_BYTES, 256 KiB) 计数字节,而不是字符. 一个更大的内容 - Length 是 在读完任何内容之前被拒绝。 身体变长(被挤出,或被骗取头)被取消为 一旦超过限制,所以无尽的上传成本最多只能达到限制. 它拒绝的每个身体都被取消 在每一个出路 readBody 让我们去身体的溪流, 所以它从来没有被锁.
  • message 413个字 'The photo is larger than 3 MB.'。 。 。 .
  • Content- Type 被检查, 因为跨站点表单可以张贴 text/plain 没有飞行前,但没有 application/json。 。 。 . anyContentType: true 读取 navigator.sendBeacon 体 (单位:千人)text/plain;仅限打电话 没有饼干认证.
  • 只有对象通过,而不是数组或赤值,所以 const { name } = await readJson(request) 无法 丢入类型错误.
  • __proto__ 密钥被丢入每个深处, 所以 Object.assign(row, body) 无法改变原型.
  • T 是对编译器的保证,而不是检查:在使用字段之前先验证字段.

streamBody 将一个上传到商店而不保存在内存, R2 为一个:

const limit = { maxBytes: 100e6, message: 'An artifact is at most 100 MB.' }
await streamBody(request, limit, body => env.BUCKET.put(key, body))  // 411, 413, 400 when the upload broke
  • 两次拒绝都会在字节读取前出现,并取消身体. 在工人中,溪流是一个 FixedLengthStream这样 R2知道它的长度.
  • 断断续续的躯体, 或带来多少字节 比它的内容 - Length,错误流,所以商店 而不是保存一个剪接副本,而且 streamBody 丢出400 unreadable_body:发件人的错. 任何其他 商店的故障(它停止阅读,R2已倒下)是按原样扔出,一500.

谁打电话

  • clientIp(request, fallback = 'unknown')数字 : cf-connecting-ip云花板,没有人能伪造 通过它. x-forwarded-for 财务报告和审定财务报表 x-real-ip 永远不读.
  • clientNetwork(request, fallback):按地址限制费率的密钥. IPv4地址保持完整. IPv6 地址 因为一个家庭或电话 持有整个64, 并且可以为每次尝试取一个新的地址. 地址的每个拼写都提供同样的钥匙 ::ffff:a.b.c.d 给IPv4一个.
  • requireSameOrigin(request, allowedOrigins):用于呼叫饼干认证。 它允许一个 Origin 这是 (b) 请求本身或所列(http://localhost:5173 或无 Origin 与 Sec-Fetch-Site: same-origin. 其他一切获得403 cross_origin,包括: Origin: null 没有标题 一点也不 它覆盖了WebSocket的升级,它们携带饼干跨站点. 所列条目不是来源 扔出类型错误.

费率限额

// wrangler.jsonc
"ratelimits": [{ "name": "AUTH_LIMITER", "namespace_id": "1001", "simple": { "limit": 10, "period": 60 } }]

rateLimit(env.AUTH_LIMITER, key, { message, retryAfter }) 数到1个请求和429 rate_limited 与 retry-after (默认60,约束期限)一旦达到限制. 一个束缚 错误无法拒绝, 因此一个已断开的限制器拒绝请求, 而不是让每个人都通过 .

单页应用程序的页面

"not_found_handling": "single-page-application" 回答页面路径, 资产拼写为 307 到 他们自己的拼写。 他们解码每个段 并再次编码它与 encodeURIComponent数字 :

页面写资产改用于页面读取
/Mac/search/house+of+the+dragon/Mac/search/house%2Bof%2Bthe%2Bdragon外加, 而不是空格
/rooms/AC%2FDC/rooms/AC/DC两个部分
/a//b/a/b减少一个部分
/s/a:b, (中文(简体) ), /s/%c5%82, (中文(简体) ), /s/100%/s/a%3Ab, (中文(简体) ), /s/%C5%82, (中文(简体) ), /s/100%25同样的文字,经过不必要的往返

html_handling: "none" 不变。 因此,一个将文本保留在路径中的应用程序(搜索,名称)读取 重装后的其他东西。 没有这种处理方式("none",默认值)资产回答路径为无 带有平地 404 的文件,从不重定向,路由器将其交给工人. serveSinglePageApp 接电话 与应用程序的页面,地址为:

// wrangler.jsonc
"assets": {
  "directory": "./dist",
  "binding": "ASSETS"
  // No not_found_handling: the Worker answers pages (serveSinglePageApp). Files never invoke the Worker.
}
async function route(request: Request, env: Env) {
  const path = new URL(request.url).pathname
  if (path.startsWith('/api/')) return (await handleApi(request, env)) ?? jsonError(404, 'Not found')
  return serveSinglePageApp(request, env.ASSETS) // last, for every path the API does not own
}
  • 它首先要求资产获取路径: 他们的答案是文件, 他们的307 要求的文件由 另一种拼写方式 (/sw%2Ejs 改为 /sw.js, (中文(简体) ), /index.html 改为 /) (中文(简体) ). 只有404个成为了应用的一页,所以有效 相同的时候 run_worker_first 向工人发送文件( E)true,或 ["/*", "!/assets/*"] 为工人 必须看到每个页面载荷),每个文件的工人引用.
  • 与 "single-page-application" 资产会自己解决所有问题 把它拿出来.
  • 只有GET和HEAD是页面。 任何其他方法投出405 method_not_allowed 与 allow: GET, HEAD,则 不要求资产.
  • 页面是用请求的方法和标题询问的,所以重新装入 If-None-Match 仍然得到304.
  • _headers 规则适用于网页,如同适用于网页 /数字 : /* 规则确实如此,对于一个页面本身路径的规则没有. 保留页面 信头(CSP, x-frame-options继续 /*,或者把它们放在工人.
  • { shell: '/admin/' } 用另一页的答案, 对于一个页面未在 /外壳不是 此站点的路径以 TypeError 拒绝 .

保存在缓存中的答案

cachedJson 用JSON来回答一个价格昂贵的或头部,对每个人都一样(目录,产品, (a) 价格清单)。 它将答案保存在工人的缓存中,所以大多数请求从不运行 build数字 :

import { cachedJson } from '@codefusion-cc/workers-http'

async function catalog(request: Request, env: Env, ctx: ExecutionContext) {
  const key = new URL('/api/catalog', request.url)                // one spelling of the question, however a visitor writes it
  return cachedJson(request, { cache: caches.default, waitUntil: (work) => ctx.waitUntil(work), maxAge: 60, staleFor: 600, key }, () => readTheCatalog(env.DB))
}
  • 它发出 application/json 带有弱的ETag(正文的SHA-256)并 cache-control: public, max-age=…, stale-while-revalidate=…时 max-age 是保留答案的秒数。 请求 If-None-Match 名称当前 ETag( 微弱的比较, 列表, 或 *得到304 没有一具尸体.
  • 虽然答案是新鲜的(在 maxAge 旧的) build 不运行。 过去的和内部 staleFor 保留答案 马上出去 build 在背景中运行一次 waitUntil; 查找同样僵硬答案的请求 在15秒内,没有第二次续约。 无 waitUntil 固态的答案在发送前被更新.
  • 过去 maxAge + staleFor,或者没有保留任何东西, 请求等待 build,它的错误被抛出,因为 errorResponse如果 build 重现仍在其中的答案时抛出 staleFor,这个出去 onError 听说了console.error (默认情况下). 拒绝的缓存 put 失败 match 从不失败的答案.
  • key 答案被保存在那里。 默认是请求自己的地址,所以 ?a=1&b=2 财务报告和审定财务报表 ?b=2&a=1 有两个答案. 无 cache 没有任何东西被保存着,每一个请求都建立起来.
  • 永远不要用它来回答因访客而异的答案(一个推车,一个会话,一个按账号计价的答案): public,则 第一个访客的回答会到达下一个.
  • 仅限头部或头部 maxAge 至少为1和a staleFor 至少0整秒,并且一个值JSON可以 hold: 其它任何投出 TypeError, 因为这是一个编程错误 .

预览和制作

每个应用程序自行部署两本,预览和制作(tools/ci). answerCopy 财务报告和审定财务报表 markCopy 让每个 说出是哪一个,并把它排除在搜索结果之外:

import { answerCopy, type CopyEnv, markCopy } from '@codefusion-cc/workers-http'

export default {
  fetch: async (request: Request, env: Env) => markCopy(answerCopy(request, env) ?? await answer(request, env), env),
}
  • answerCopy 答案获取和标题 /version.json 与 { commit, environment } (单位:千美元)COMMIT,该部署 套装和 APP_ENV 从抄本"Wrangler vars";无所取用),并 /robots.txt。所有其他请求 这是无效的,所以它保持应用的。 部署和小时生产表都读了.
  • markCopy 添加 X-Robots-Tag: noindex, nofollow 对每一个答案,和 /robots.txt 拒绝一切, 除非应用程序通过 { indexed: true } 财务报告和审定财务报表 APP_ENV 没错 production预览,缺少 APP_ENV 或者里面的打字没有搜索结果 一个WebSocket升级的通行证是它是什么。 网站的静态文件从不 以工人们所享受的 _headers 发送相同的信头.

饼干和大麻

getCookie(request, '__Host-session')                                  // string, or null (missing or empty)
serializeCookie('__Host-session', token, { maxAge: 30 * 86_400 })     // Path=/; Max-Age=…; HttpOnly; Secure; SameSite=Lax
serializeCookie('__Host-session', '', { maxAge: 0 })                  // clears it
await sha256(token)                                                   // 64 hex characters
await sha256(token, 'base64url')                                      // 43 characters

serializeCookie 丢弃浏览器会拒绝或错误读取的内容: 带有空格、 引号、 逗号的值, 分号或反斜线(先编码), SameSite=None 或一个 __Host-编号 :__Secure- 无名称 secure页:1 __Host- 名称带有域或其它路径。 留一个 sha256 每被存储一列编码:两者不相匹配.

在应用测试中

import { testRateLimiter } from '@codefusion-cc/workers-http/testing'

const limiter = testRateLimiter({ limit: 2 })       // or { fail: new Error('down') }
await rateLimit(limiter, 'k'); await rateLimit(limiter, 'k')
await expect(rateLimit(limiter, 'k')).rejects.toMatchObject({ status: 429 })
limiter.keys // ['k', 'k', 'k']
import { testAssets } from '@codefusion-cc/workers-http/testing'

const assets = testAssets({ files: { '/index.html': '<!doctype html>…', '/sw.js': '…' } }) // or { fail: new Error('down') }
await worker.fetch(new Request('https://app.test/Mac/search/a+b'), { ...env, ASSETS: assets })
assets.paths // ['/Mac/search/a+b', '/']: what was asked; assets.requests holds the requests

testAssets 回答如静态资产 not_found_handling: "none": 路径文件和文件夹 index.html 在文件夹,一个307 来自任何其他拼写 两者,一个404 对于一个路径,没有文件, 405 对于其他 在文件上使用方法,在 ETag 上使用 304。 货包的工人测试 把它控制在真正的约束.

运行工人的测试 使用真正的约束 从Wrangler配置.

版本与发布记录

@codefusion-cc/workers-http 共发布 8 个版本,首次发布于 2026/10/02;过去 12 个月发布了 8 个版本。

版本发布日期距今
0.1.4latest2026/10/2614 天前
0.1.32026/10/091 个月前
0.1.22026/10/041 个月前
0.1.12026/10/031 个月前
0.1.02026/10/021 个月前
1.1.02026/10/021 个月前

关于 @codefusion-cc/workers-http 的常见问题

@codefusion-cc/workers-http 是什么?

Cloudflare Worker的API的HTTP管道: 错误被抛出作为答案, API的路线, JSON 的响应, 绑定了 JSON 和身体读取, 客户端的地址和网络, 同源检查, 费率限制 a RatLimit 绑定, cookie, SHA-256 和 a s

如何安装 @codefusion-cc/workers-http?

在项目目录运行 npm install @codefusion-cc/workers-http,或使用 pnpm add / yarn add / bun add。

@codefusion-cc/workers-http 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@codefusion-cc/workers-http 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@codefusion-cc/workers-http 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@codefusion-cc/workers-http 可以商用吗?

@codefusion-cc/workers-http 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@codefusion-cc/workers-http 的下载量有多少?

根据 npm 官方下载统计,@codefusion-cc/workers-http 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,141 次,日均约 71 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@codefusion-cc/workers-http 还在维护吗?

最新版本 0.1.4 发布于 2026/10/26(14 天前);至今共发布 8 个版本,其中过去 12 个月发布了 8 个;首次发布于 2026/10/02。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。