豫言 / 豫言操作系统授权服务 @ 0.3.0

向获授权的内部服务发送限定请求,增量读取或原流转交其回应。

源码浏览已生成;不能解析的文件以纯文本展示,不表示通过类型检查。

规范.汉语.md

# 授权服务接口规范 0.3.0

此能力组调用已经由宿主资源许可授予的内部服务绑定。应用给出绑定名、绝对 HTTPS 网址、`GET` 或 `POST` 方法、要从当前入站请求复制的标头名列表、固定标头键值列表,以及是否直接转送入站正文流。宿主须先检查绑定名授权,再发请求;目标网址仍由应用选择,不授予普通外网 `fetch`。服务回应的重定向不自动跟随,3xx 状态和 `Location` 作为原回应交给应用,避免凭据随重定向离开授权目标。无当前入站请求时调用失败。

可复制的标头只限 `Cookie`、`Authorization`、`Content-Type`、`Idempotency-Key`、`CF-Connecting-IP`;固定标头只限 `Origin`、`Accept`。两列表合计最多 16 项,同名重复时报错,未出现的入站标头略过。宿主不得复制其他入站标头。`GET` 不得转送正文;`POST` 可以转送,保留原始流,不先解析或缓冲。绑定、网址、方法、标头或正文状态不合规时调用失败。

返回的字符串是仅在当前请求有效的私有回应句柄,不是可持久化的服务地址。`读取授权服务状态` 返回原 HTTP 状态码;`读取授权服务癸象正文文` 在回应副本上解析 JSON,正文上限为 2 MiB,原回应流仍能交给 `回应入站以授权服务原答`。后者直接交付原 Response,保留状态、标头及正文流;一次入站请求只能交付一个最终响应。调用方若已消耗原流或复用失效句柄,宿主应报错。资源授权与接口版本须分别核对。

## 0.3.0 新增:带正文请求与增量读取

0.3.0 只新增五个函数,0.2.0 的四个函数签名与语义不变。新增部分服务于必须“边读边处理”上游流式响应(SSE、NDJSON)的应用:向内部模型服务、编译服务发 POST,逐块读取答复,中途可取消,全程受期限约束。

### 带正文请求

`请求授权服务带正文(绑定名, 网址, 方法, 固定标头, 正文, 超时毫秒)` 返回“(成功,响应柄或错误文)”。它不需要当前入站请求,可用于定时、队列、持久对象等事件。

- 绑定名:非空,且已获 `SERVICE` 许可;未授权或不存在属部署错误,由宿主直接报错。
- 方法:精确的 `GET` 或 `POST`。`GET` 的正文必须为空字符串。
- 网址:绝对 HTTPS,长 1 至 2048 字节,可被 URL 标准解析,无用户名和口令,不含空白、控制字符与反斜线。主机部分只是服务绑定的内部占位名,宿主不据主机名选路。
- 固定标头:至多 16 项。名只允许 `Content-Type`、`Accept` 和以 `X-Yuyan-` 开头(其后至少一个令牌字符)者,不区分大小写,不得重复(不区分大小写),须是合法标头令牌。值至多 512 字节,只含可见 ASCII 字符(0x20 至 0x7E),首尾不得为空格。`Authorization`、`Cookie`、`Origin` 等不在此列,不得经此函数设定。
- 正文:UTF-8 文字,至多 8 MiB(8388608 字节)。宿主按字节发送,不经 JSON 转义,任意字符(含引号、反斜线、换行、制表、四字节字符)原样送达;不自动补 `Content-Type`。
- 超时毫秒:1 至 900000。宿主以 `AbortSignal.timeout` 令期限覆盖“发请求、等响应头、读响应正文”的整次交换;宿主自身另有事件时限,二者取先到者。
- 不跟随重定向。3xx 状态与 `Location` 原样交给应用,由应用视为失败或另行处理。

违反上列任一规则抛豫言异常,且不发出请求。传输失败,或响应头到达前期限已满,返回“阴”和以 `授权服务请求失败或超时:` 或 `授权服务请求超时` 开头的错误文,此为可恢复情形。成功返回“阳”和响应柄。

### 响应柄

响应柄是不透明字符串,仅在创建它的事件内有效,不可保存、序列化或伪造。`请求授权服务` 与 `请求授权服务带正文` 签发的柄,都可用于 `读取授权服务状态` 与下列读取函数;不是本包签发的字符串(包括其他能力包的柄、入站请求的句柄号)一律抛豫言异常。同一柄的读取应当串行;不同柄互不干扰,可交错读取。

增量读取会消耗并锁定响应正文。某柄一经开始增量读取(或已定局),`读取授权服务癸象正文文` 与 `回应入站以授权服务原答` 对它抛豫言异常,不再崩溃于宿主;要整体读取应改用 `读取授权服务响应有限文字`。

### 标头与状态

`读取授权服务响应标头(柄, 名)` 返回响应中该标头的值,名不区分大小写,缺失返回空字符串;名须是 1 至 128 字节的合法标头令牌,否则抛豫言异常。`读取授权服务状态(柄)` 沿用 0.2.0,返回原 HTTP 状态码。

### 增量读取文字块

`读取授权服务响应块(柄, 最大字节)` 返回“(状态,文字)”。最大字节须在 1 至 65536 之间,否则抛豫言异常。

- 状态 0:数据块。文字是响应正文的下一段。宿主做流式 UTF-8 解码:跨上游块拆开的多字节字符正确拼接,非法序列各换成 U+FFFD,流首的字节序标记去掉,返回的文字绝不含被截断的半个字符。不足一个完整字符的上游块(解码所得为空)不单独返回,宿主继续读取直到得到非空文字、流结束、失败或期满(若上游连续送来 64 个以上空块,宿主可返回文字为空的状态 0,应用照常继续读取即可)。
- 文字长度:每次返回文字的 UTF-8 字节数不超过最大字节(最大字节小于 4 时按 4 计,因单个字符至多 4 字节)。上游一块过大,或非法字节换成三字节的 U+FFFD 而超出,多出部分留在宿主,由后续调用依序返回,宿主不再为此读取上游。
- 状态 1:正文已读尽,文字为空。响应没有正文(如 204、HEAD)时首次读取即得此状态。流末尾残缺的字符序列先以一个 U+FFFD 作状态 0 的块返回,再返回状态 1。
- 状态 2:读取错误,文字为平台报告的原因,形如“名称: 消息”。
- 状态 3:期限已满,或应用已取消,或有限读取因超限已取消。文字是简短说明(`超时`、`已取消`、`正文超过上限,已取消`),仅供诊断。期满时宿主同时取消上游。
- 状态 1、2、3 是终态:此后再调用返回同样的终态与文字。

### 有限整读

`读取授权服务响应有限文字(柄, 字节上限)` 一次读完响应剩余正文,返回“(状态,文字)”。字节上限须在 1 至 8388608 之间,否则抛豫言异常。

- 状态 0:成功,文字是剩余正文(含此前增量读取尚未交付的部分)。
- 状态 1:超过上限,文字为空,宿主已取消上游。先看响应的 `Content-Length`,声明超过上限则一字不读;无声明或声明不实则流式累计,累计原始字节超过上限即取消并返回状态 1,超出的部分不返回给应用;平台流内部的预读缓冲不计入。恰等于上限不算超限。
- 状态 2:读取错误,文字为原因。状态 3:期限已满或已取消。
- 上限按上游原始字节计;解码规则同增量读取(宽松解码,非法序列成 U+FFFD,字节序标记去掉)。

### 取消

`取消授权服务响应(柄)` 取消对响应正文的读取,并对上游流执行 cancel(对外发连接即断开连接);上游服务能否即时回调其流的 cancel 取决于平台,不作保证(云工本地 workerd 对服务绑定与持久对象不回调,与直接用 JavaScript 相同);此后读取返回状态 3 与 `已取消`。幂等:已定局或重复取消无害。无效的柄抛豫言异常。应用在放弃读取前应当取消,以免上游继续生产。

### 失败分类

| 情形 | 类别 |
| --- | --- |
| 绑定名、方法、网址、标头、正文、超时、块上限、字节上限、标头名等参数违规;无效响应柄;已增量读取后再整读或转原答 | 抛豫言异常,不发请求 |
| 服务绑定未授权或不存在 | 部署错误,宿主报错 |
| 发请求失败,或响应头到达前期满 | 返回“阴”和原因 |
| 读取中上游出错、期满、取消、超限 | 读取函数的状态 2、3、1 |
| HTTP 4xx、5xx、3xx | 成功响应,由应用按状态码处理 |

### 限额

固定标头 16 项、值 512 字节;网址 2048 字节;请求正文 8 MiB;超时 900000 毫秒;块 65536 字节;有限整读 8 MiB;`读取授权服务癸象正文文` 沿用 2 MiB。

### 跨宿主一致性

不同宿主须满足:流式 UTF-8 解码与拼接;块内不含半个字符;期限覆盖全程且期满后读取返回状态 3 并对上游流执行 cancel;取消对上游流执行 cancel(上游能否观察到取决于平台);先看声明长度再读;参数违规不发请求;句柄仅本事件有效;不跟随重定向。宿主的事件时限若短于所给超时,以先到者为准,并按状态 3 或事件失败处理。

### 一致性样例

- 5 帧 SSE,按 7 字节切块(切开汉字与帧),逐块读取,累计原始字节,等于整个流的字节数;文字拼起来等于整体解码。
- 上游先给首块,其后长久不出块,超时 300 毫秒:先得首块,再得状态 3,宿主对上游流执行 cancel(原因 `TimeoutError`;能回调 cancel 的宿主可观察到)。
- 上游中途出错:先得已到的块,再得状态 2 与原因。
- 读一块后取消:宿主对上游流执行 cancel(能回调 cancel 的宿主可观察到);再读得状态 3;再取消无害。
- 响应带 `Content-Length: 5000`,字节上限 1000:得状态 1,上游一次也未被读取。
- 无声明长度、共 20 块每块 500 字节,字节上限 1000:得状态 1,宿主已对上游流执行 cancel,且远未读尽。
- 恰 1000 字节、上限 1000:得状态 0;1001 字节:得状态 1。
- 正文 `甲"乙\丙<换行>丁<制表>戊<表情>`,POST 送达,上游收到的字节与原文 UTF-8 逐字节相同。
- 固定标头含 `Authorization`、含换行的值、重复的 `Accept`、超过 16 项:均抛异常,上游未收到请求。
- 正文 8388608 字节可发,多一字节抛异常。

### 边界

宿主不保证上游服务观察到取消或断开,只保证读取一方立即得到状态 3、不再拉取上游。本包不提供:选择多个流中先到者的原语;写入服务的请求流(正文一次给足);跨事件保存响应柄;HTTP/2 推送与升级;自动重试。响应正文只读取一次,不可回绕。宿主执行事件的总时限由宿主配置,本包只保证按给定期限令读取失败,不延长宿主时限。

## 补充:服务绑定失败可捕获(路由代理修订)

`请求授权服务`(0.2.0 起的旧函数)在服务绑定的 `fetch` 同步抛出或返回被拒绝的 Promise 时(目标 Worker 抛出异常、部署切换、连接中断等),此前宿主异常会穿过 Wasm 边界使整个事件失败,应用无法捕获,也就无法向客户端回应 503。现在适配用安全调用,把它转为可捕获的豫言异常,消息以 `授权服务请求失败:` 起首,其后是 `名称: 消息`。参数违规仍在发请求之前抛出(不发请求);带正文版本 `请求授权服务带正文` 一直返回(阴,原因),不受影响。`读取授权服务癸象正文文` 同理:响应正文不是有效 JSON(例如网关返回 200 与 HTML)时,此前宿主的 `SyntaxError` 会使整个事件失败;现在抛出可捕获的豫言异常,消息以 `授权服务 JSON 正文无效:` 起首,其后是 `名称: 消息`。这一修订不改任何函数签名,不升版本。

一致性样例(补充):服务绑定的 `fetch` 抛出 `Error('服务挂了')`(或返回被拒绝的 Promise):`请求授权服务` 抛出消息为 `授权服务请求失败:Error: 服务挂了` 的豫言异常,应用可用 `尝试运行` 捕获并改答 503,事件不失败。

规范.文言.md

# 授权服务公约 0.3.0

此包惟请宿主已授之内部服务。客献绑定名、HTTPS 绝对址、`GET` 或 `POST` 之法、应从当前请取之白头名列、定头键值列,及原流转正文之爻。宿主先核绑定之权,乃发请;目标仍由客定,此权不等于外网任取。遇改向不自追,三百号与 `Location` 仍归客,以免凭据随改向逸出所授之址。无当前入站请则败。

可复制者惟 `Cookie`、`Authorization`、`Content-Type`、`Idempotency-Key`、`CF-Connecting-IP`;可定者惟 `Origin`、`Accept`。两列合至多十六,同名重出则败,原请无头则略。宿主不得暗渡他头。`GET` 不可带正文;`POST` 可原流转之,不先析亦不缓冲。绑定、网址、法、头或流失规则败。

所返文为私柄,惟当前请有效,不可久存。`读取授权服务状态` 取原状态;`读取授权服务癸象正文文` 克隆而析 JSON,至多二 MiB,原流仍可回。`回应入站以授权服务原答` 以原回应径交当前请,状态、头及正文流皆守。一请惟一终答;原流已耗或柄失效则败。资源之权与接口之版当各核。

## 〇点三新增:带正文之请与渐读

〇点三惟增五术,〇点二之四术,其型与义不变。所增者,为必边读边处上游流答(SSE、NDJSON)之客而设:向内模型、编译之服发 POST,逐块读其答,中途可止,全程受期限之约。

### 带正文之请

`请求授权服务带正文(绑定名, 网址, 方法, 固定标头, 正文, 超时毫秒)` 返「(成否,答柄或误文)」。不须当前入站请,定时、队列、持久对象诸事皆可用。

- 绑定名:非空,且已受 `SERVICE` 之许;未授或无此绑定,乃部署之误,宿主径报。
- 法:正为 `GET` 或 `POST`。`GET` 之正文必空。
- 网址:HTTPS 绝对址,长一至二〇四八字节,可依 URL 标准析之,无用户名口令,无空白、控字、反斜线。其主机惟服务绑定之内名,宿主不据以选路。
- 定头:至多十六项。名惟 `Content-Type`、`Accept`,及以 `X-Yuyan-` 起而其后至少一令牌字者,不分大小写,不得重出(亦不分大小写),须为合法头令牌。值至多五一二字节,惟可见 ASCII 字(自空格至 `~`),首尾不得为空格。`Authorization`、`Cookie`、`Origin` 等不在其列,不得由此术定。
- 正文:UTF-8 文,至多八 MiB(八三八八六〇八字节)。宿主以字节发之,不经 JSON 转义,凡字(引号、反斜线、换行、制表、四字节字)皆原样而达;不自补 `Content-Type`。
- 超时毫秒:一至九〇〇〇〇〇。宿主以 `AbortSignal.timeout` 令期限贯“发请、候答头、读答文”之全程;宿主自有事时之限,二者先至者胜。
- 不随改向。三百号与 `Location` 原样归客,或视为败,或另处之。

违上列任一则告豫言异常,且不发请。传输败,或答头未至而期满,则返“阴”与误文,误文以 `授权服务请求失败或超时:` 或 `授权服务请求超时` 始,此为可复之情。成则返“阳”与答柄。

### 答柄

答柄为不透明之文,惟创之之事有效,不可存、不可序、不可伪。`请求授权服务` 与 `请求授权服务带正文` 所发之柄,皆可用于 `读取授权服务状态` 及下列诸读术;非本包所发之文(他能力包之柄、入站请之句柄号)皆告豫言异常。同一柄之读当有序;异柄互不相扰,可错综而读。

渐读则耗而锁答文。柄一旦始渐读(或已定局),`读取授权服务癸象正文文` 与 `回应入站以授权服务原答` 于其皆告豫言异常,不复崩于宿主;欲整读者,当改用 `读取授权服务响应有限文字`。

### 头与状态

`读取授权服务响应标头(柄, 名)` 返答中该头之值,名不分大小写,无则空文;名须为一至一二八字节之合法头令牌,否则告豫言异常。`读取授权服务状态(柄)` 承〇点二,返原 HTTP 状态号。

### 渐读文块

`读取授权服务响应块(柄, 最大字节)` 返「(状态,文)」。最大字节当在一至六五五三六之间,否则告豫言异常。

- 状态零:得一块。文为答文之次段。宿主流式解 UTF-8:跨上游块而裂之多字节字,必正相衔;非法之序各易为 U+FFFD;流首之字节序记去之;所返之文绝无半字。上游块不足一整字(解得空文)者不单返,宿主续读,至得非空之文、流终、败或期满(若上游连送六十四空块以上,宿主可返文空之状态零,客照常续读可也)。
- 文长:每返之文,其 UTF-8 字节数不逾最大字节(最大字节不足四者,以四计,以单字至多四字节也)。上游一块过大,或坏字节易为三字节之 U+FFFD 而溢者,其余留于宿主,由后续之调依次返之,宿主不为此复读上游。
- 状态一:答文已读尽,文空。无正文之答(如二〇四、HEAD)首读即得此状态。流末残缺之字序,先以一 U+FFFD 为状态零之块返之,乃返状态一。
- 状态二:读败,文为平台所报之因,形如“名称: 消息”。
- 状态三:期限已满,或客已止之,或有限整读因逾限已止。文为简短之说(`超时`、`已取消`、`正文超过上限,已取消`),惟供察验。期满时宿主同止上游。
- 状态一、二、三为终态:此后再调,返同一终态与文。

### 有限整读

`读取授权服务响应有限文字(柄, 字节上限)` 一次读毕答文之余,返「(状态,文)」。字节上限当在一至八三八八六〇八之间,否则告豫言异常。

- 状态零:成,文为余下之正文(含此前渐读未交之部)。
- 状态一:逾上限,文空,宿主已止上游。先观答之 `Content-Length`,所称已逾上限则一字不读;无所称或所称不实,则流式累计,累计原字节逾上限即止而返状态一,逾出之部不归客;平台流内部之预读不计。恰等于上限不为逾。
- 状态二:读败,文为因。状态三:期满或已止。
- 上限以上游原字节计;解码之则同渐读(宽解,非法序为 U+FFFD,去字节序记)。

### 止

`取消授权服务响应(柄)` 止读答文,且对上游流行 cancel(于外发之连则断其连);上游服务能否即回调其流之 cancel,视平台而定,不作保证(云工本地 workerd 于服务绑定与持久对象不回调,与径用 JavaScript 同);此后之读返状态三与 `已取消`。幂等:已定局或重止皆无害。柄无效则告豫言异常。客欲弃读,当先止之,免上游徒产。

### 败之分类

| 情形 | 类 |
| --- | --- |
| 绑定名、法、址、头、正文、时限、块限、字节上限、头名诸参违规;柄无效;渐读之后复整读或转原答 | 告豫言异常,不发请 |
| 服务绑定未授或无此绑定 | 部署之误,宿主报之 |
| 发请败,或答头未至而期满 | 返“阴”与因 |
| 读时上游出错、期满、已止、逾限 | 读术之状态二、三、一 |
| HTTP 四百、五百、三百号 | 得答之成,由客按状态号处之 |

### 限额

定头十六项、值五一二字节;址二〇四八字节;请正文八 MiB;时限九〇〇〇〇〇毫秒;块六五五三六字节;有限整读八 MiB;`读取授权服务癸象正文文` 仍二 MiB。

### 异宿主之一致

异宿主必守:流式解 UTF-8 而相衔;块内无半字;期限贯全程,期满则读返状态三并对上游流行 cancel;止则对上游流行 cancel(上游能否得见,视平台);先观所称之长而后读;参违则不发请;柄惟本事有效;不随改向。宿主之事限若短于所给时限,以先至者为准,按状态三或事败处之。

### 一致之例

- 五帧 SSE,以七字节切块(裂汉字与帧),逐块读之,累计原字节,等于全流之字节数;文拼之,等于整体解码。
- 上游先予首块,其后久不出块,时限三〇〇毫秒:先得首块,次得状态三,宿主对上游流行 cancel(因 `TimeoutError`;能回调 cancel 之宿主可察之)。
- 上游中途出错:先得已至之块,后得状态二与因。
- 读一块而止:宿主对上游流行 cancel(能回调 cancel 之宿主可察之);再读得状态三;再止无害。
- 答带 `Content-Length: 5000`,字节上限一〇〇〇:得状态一,上游一次未被读。
- 无所称之长,共二十块每块五〇〇字节,字节上限一〇〇〇:得状态一,宿主已对上游流行 cancel,且远未读尽。
- 恰一〇〇〇字节、上限一〇〇〇:得状态零;一〇〇一字节:得状态一。
- 正文 `甲"乙\丙<换行>丁<制表>戊<表情>`,POST 而达,上游所收之字节与原文 UTF-8 逐字节同。
- 定头含 `Authorization`、含换行之值、重出之 `Accept`、逾十六项:皆告异常,上游未得请。
- 正文八三八八六〇八字节可发,多一字节则告异常。

### 界

宿主不保证上游服务得见其止或断,惟保证读者一方立得状态三、不复拉取上游。此包不予:于诸流择先至者之术;写向服务之请流(正文一次予足);跨事而存答柄;HTTP/2 推送与升级;自动重试。答文惟读一次,不可回绕。宿主事之总限由宿主配之,此包惟保证依所给期限令读败,不延宿主之限。

## 补:服务绑定之败可捕(路由代理之修)

`请求授权服务`(〇点二起之旧术)遇服务绑定之 `fetch` 同步抛出或返被拒之 Promise(目标 Worker 抛异常、部署更替、连接中断之类),旧时宿主之异越 Wasm 之界而使全事败,应用不能捕,遂无从答客以五百零三。今适配以安全之调,转为可捕之豫言异常,辞以 `授权服务请求失败:` 起,继以 `名称: 消息`。参数违规仍先于发请而告(不发请);带正文之术 `请求授权服务带正文` 素返(阴,因),不受此累。`读取授权服务癸象正文文` 亦然:答文非有效 JSON(如关口返二百而文为 HTML)时,旧时宿主之 `SyntaxError` 使全事败;今告可捕之豫言异常,辞以 `授权服务 JSON 正文无效:` 起,继以 `名称: 消息`。此修不改术之型,不升版。

一致之例(补):服务绑定之 `fetch` 抛 `Error('服务挂了')`(或返被拒之 Promise):`请求授权服务` 告辞为 `授权服务请求失败:Error: 服务挂了` 之豫言异常,应用可以 `尝试运行` 捕之而改答五百零三,事不败。