出站 HTTP(Wretch)
从不可变的 Wretch 基础实例派生业务客户端,并统一应用宿主的出站策略。
@pluxel/plugin-wretch面向插件作者的公开入口是.与./workbench。可安装版本以 npm registry 为准;仓库内边界见 Package 矩阵。
@pluxel/plugin-wretch 提供一个原生、不可变的 Wretch 基础实例,并在请求真正发出前应用宿主级出站策略。URL 构造、addon、middleware、catcher 和响应链仍使用原生 Wretch API。
以下命令在快速开始生成的工作区根目录执行;按 添加插件 选择直接使用依赖的包,再运行 vp install。
vp exec pluxel pncat add @pluxel/plugin-wretch普通 HTTP client 与宿主出站策略可在 services: [] 的 Host 中运行。只有显式启用受管出站设置时才需安装 Persistence 服务;未安装时,受管设置读写明确失败,不使用临时内存存储代替。
第一个 HTTP consumer
import { , type } from '@pluxel/plugin-wretch'
import { , } from '@pluxel/core'
import { , } from '@pluxel/core/better-result'
import * as from 'valibot'
const = .({ : .(), : .() })
type = .<typeof >
export class extends ('CustomerNotFound')<{
: string
: string
}> {}
const = .({
: .(.(), .()),
})
@({ : 'Customer' })
export class extends {
private readonly = this..()
private !:
constructor(private readonly : ) {
super()
}
protected override (): void {
this. = this...(this.., true)
}
(: string): <<, >> {
return this.
.(`/customers/${()}`)
.(() =>
.(new ({ , : `Customer ${} was not found` })),
)
.(() => .(.(, )))
}
}host 安装 provider:
以下 host 是 测试宿主,用于验证装配。应用入口按 添加插件 配置清单、配置记录和自动启动。
await host.start(WretchPlugin, {
catalog: [CustomerPlugin],
initialConfig: {
timeoutMs: 30_000,
maxConcurrentRequests: 64,
maxQueuedRequests: 256,
allowedOrigins: ['https://catalog.example'],
},
})
await host.start(CustomerPlugin, {
initialConfig: { baseUrl: 'https://catalog.example' },
})client 本身就是 Wretch。Wretch 的 immutable 语义保证不同 consumer 通过 .url()、.options()、.headers()、.auth()、.addon() 或 .middlewares() 派生 client 时不会互相污染。
将可预期失败返回给调用方
上面的 CustomerPlugin.find() 是本地插件能力:上游返回 404 时,Wretch 的 .notFound() catcher 把它变成
CustomerNotFound,调用方用同一个 @pluxel/core/better-result 入口检查结果。成功响应先用 schema 验证;
其他 HTTP 状态、网络错误、超时和 lifecycle 撤回继续抛出,不能误报为“客户不存在”。
完整 Result 用法见 better-result 共享入口。
假设上面的 CustomerPlugin 从 @acme/customer 根入口导出,另一个插件可以直接依赖它并发布 HTTP route:
import { BasePlugin, Plugin } from '@pluxel/core'
import { Result } from '@pluxel/core/better-result'
import { ElysiaApp } from '@pluxel/services/elysia'
import { CustomerPlugin } from '@acme/customer'
@Plugin()
export class CustomerHttpPlugin extends BasePlugin {
constructor(private readonly customers: CustomerPlugin) {
super()
}
protected override init(): void {
this.ctx.require(ElysiaApp).get('/customers/:id', async ({ params }) => {
const result = await this.customers.find(params.id)
if (Result.isError(result)) {
return Response.json(
{ code: 'customer_not_found', message: result.error.message },
{ status: 404 },
)
}
return Response.json(result.value)
})
}
}把 CustomerHttpPlugin 加入宿主清单并启动;宿主还需安装 ElysiaApp 服务。
跨插件本地调用保留 Result 实例和 CustomerNotFound 类型;HTTP/Workbench/Worker 边界只传普通数据,
按传输协议选择状态码或 DTO。不要直接序列化 Result 或错误实例。发布 CustomerPlugin 时,
按插件包指南设置包含 /better-result 子入口的 Core peer 下限。
主入口导出 WretchPlugin、WretchConfig 及相关类型,不重新导出裸 wretch() factory 或 addons。需要 query-string addon、retry middleware 等上游扩展时,由 consumer 直接安装 wretch:
vp exec pluxel pncat add wretch宿主 outbound policy
WretchConfig 是 configs.use() 使用的同一 schema。部署时可直接绑定,无需启用 Workbench:
import { envBinding } from '@pluxel/host'
import { WretchPlugin, WretchConfig } from '@pluxel/plugin-wretch'
// 放入应用的 envBindings 数组;HTTP_ORIGINS 使用 JSON 数组。
envBinding(WretchPlugin, {
config: {
schema: WretchConfig,
mapping: {
timeoutMs: 'HTTP_TIMEOUT_MS',
allowedOrigins: 'HTTP_ORIGINS',
},
},
})配置来源和只读规则遵循 Host 配置优先级。provider 的 origin、容量与 timeout 始终由这份配置控制;consumer 保存值不能放宽这些限制。
WretchPlugin 只有四个 Plugin config 字段:
| 字段 | 默认值 | 语义 |
|---|---|---|
timeoutMs | 30000 | 每次底层 fetch attempt 的超时;0 关闭 host timeout |
maxConcurrentRequests | 64 | 所有 consumer 共享的进行中 fetch attempt 上限 |
maxQueuedRequests | 256 | 并发满时最多等待多少 attempt;0 禁止排队 |
allowedOrigins | [] | 空数组不限制;非空时只允许列出的精确 HTTP(S) origin |
allowedOrigins 中每项必须是没有 path、query、hash 或 credential 的 HTTP(S) origin。实际 request URL 也必须是 absolute HTTP(S) URL;不支持相对 URL 或其他协议。
policy 通过 Wretch defer() 在请求发送前安装。consumer 的 retry middleware 位于外层,所以每次 retry attempt 都会单独经过:
- absolute URL 与 origin 检查;
- 全局并发/队列 admission;
- timeout 与 lifecycle AbortSignal;
- 实际 fetch;
- 释放并发槽。
队列满时 reject Error('Outbound HTTP request queue is full');origin 不允许时 reject 包含 origin 的 Error;timeout 使用名为 TimeoutError 的 DOMException。这些目前不是带稳定 code 的 Pluxel error hierarchy,transport 不应依赖完整 message 做协议映射。
retry、dedupe、缓存、认证刷新和业务错误解析不属于 host policy。它们应由 consumer 用 Wretch middleware/addon 明确组合;需要缓存时由业务显式组合缓存实现;仓库内部可以使用 Cache 预览,它目前不是公开安装包。
生命周期与 cancellation
取得的 client 与 consumer owner 和 provider generation 绑定:
- consumer stop/replacement 后,旧 client 的新请求会失败;
- provider stop/replacement 后,所有旧 client 都会失败;
- 正在等待并发槽的请求会从队列移除并 reject;
- 已进入 fetch 的请求会收到 lifecycle abort signal;
- consumer 自己传入的
options.signal会和 lifecycle/timeout signal 合并。
自定义 .fetchPolyfill() 必须遵守标准 AbortSignal,才能在 teardown 或 timeout 时及时结束。不要清空受管 client 的 deferred callbacks,否则会移除 provider policy。
可选的 managed settings
默认情况下,consumer 只受 host policy 管理。若希望操作者在 Workbench 为某个 consumer 配置普通 headers、HTTP(S) proxy 和更短 timeout,consumer 必须显式调用:
protected override async init(): Promise<void> {
await this.http.enableManagedSettings()
this.api = this.http.client.url(this.config.baseUrl, true)
}managed settings 按 caller 的完整 Plugin node address 隔离并持久化。client 每次发送请求前读取最新状态,所以设置保存后,已缓存的 immutable client 也会自动生效。
无管理界面时,consumer 在启用后通过同一依赖读取、保存或重置;失败继续抛出,调用方决定是否重试:
await this.http.enableManagedSettings()
const saved = await this.http.updateManagedSettings({
headers: { 'X-Region': 'hk' },
timeoutMs: 5_000,
})
const current = this.http.managedSettings
await this.http.resetManagedSettings()保存是显式操作,不在每次启动时自动覆盖旧值。reset 删除 caller 保存值,恢复原生 client 的 headers/proxy 和 provider timeout。应用 endpoint、鉴权与重试仍属于 consumer 自身配置或 Vault;此文件不是第二份 provider 部署配置。后续启动若降低 provider timeout,已有 caller timeout 会按新上限截断。
WretchManagedSettings 的真实形状是:
type = <{
: <<string, string>>
?: string
?: number
}>- managed header 会覆盖请求中同名普通 header;最多 32 项,单值最长 4096 字符;
authorization、cookie、proxy-authorization、set-cookie、x-api-key等 secret-bearing header 会被拒绝;- transport-owned header(如
host、content-length、connection)也会被拒绝; - proxy 必须是无 credential、path、query 或 hash 的 HTTP(S) origin;authenticated proxy 不受支持;
- consumer
timeoutMs必须是正整数,并且只能收紧 host timeout;host timeout 为0时,它成为有效 timeout。
managed settings 使用 runtime persistence,而不是 secret store。不要把 access token、API key 或 cookie 放入该 contract。Workbench disabled 时不会创建 UI backend,但已显式启用的 persisted settings 仍会应用于核心 HTTP client。
放置 Workbench Attachment
从 @pluxel/plugin-wretch/workbench 导入 provider-owned Attachment,并在 consumer 的 Workbench 定义中选择
placement:
import { workbench } from '@pluxel/workbench'
import { WretchWorkbench } from '@pluxel/plugin-wretch/workbench'
export const CustomerWorkbench = workbench.define({
http: WretchWorkbench.settings.place(
workbench.tab({ label: 'HTTP', icon: workbench.icons.Settings }),
),
})consumer 启动时启用设置,并直接绑定 constructor-injected required dependency:
protected override async init(): Promise<void> {
await this.http.enableManagedSettings()
this.api = this.http.client.url(this.config.baseUrl, true)
this.ctx.workbench?.publish(CustomerWorkbench, {
http: { provider: this.http },
})
}View 打开时,WretchPlugin 根据 Workbench 提供的 exact consumer.node 找到已经初始化的 managed state,
并创建 fresh WretchSettingsApi capability。若没有先完成 enableManagedSettings(),打开 View 会失败。
provider-owned zero-props renderer 通过 descriptor-bound scope 的 query/mutation resources 取得 provider stub;
它提供 snapshotDto()、updateDto(settings) 与 resetDto(),Framework 自动 detach 返回 DTO 并在 mutation settle 后刷新 snapshot。
snapshot 同时包含 hostTimeoutMs 和最终 effectiveTimeoutMs。
同一 caller 并发执行 enableManagedSettings() 会共享一次初始化。View 关闭、session 结束或
caller/provider 停止后,旧 capability 被撤销;provider cleanup 也会关闭 managed ProxyAgent。UI 不是核心
HTTP 请求能够运行的前提。
最后更新于