# 使用 WU 连接服务

发布契约、授权客户端，并完整走通带版本、验证、取消和清理的 RPC 请求。

<span id="del-cliente-a-una-respuesta"></span>

<span class="section-anchor" id="section-1"></span>

## 从客户端到响应

首次集成 RPC 时，请先阅读[应用间求和](/zh-cn/docs/guides/rpc-between-apps/)，其中包含 workspace 所需全部文件。本章在此基础上加入契约发现、取消和实验室可运行服务。

在[服务与 RPC](/zh-cn/labs/backend/)中，两个 WU 应用使用不同权限协作。提供方发布服务并响应通道，客户端消费契约并请求响应。流程可测试有效数据、错误、版本不兼容、取消和提供方移除。

```text
Cliente WU
  → consume('docs:backend:service', '^1.0.0')
  → request('docs:backend:request', solicitud)
  → handler del proveedor WU
  → respuesta { status, headers, body, execution }
```

请求通过运行时内部的 WU RPC 传递。`status`、`headers` 和 `body` 是响应契约成员，不代表门户打开了 HTTP 连接或服务器端口。

<span id="solicitar-y-conceder-la-autoridad-necesaria"></span>

<span class="section-anchor" id="section-2"></span>

## 申请并授予所需权限

每个 manifest 声明应用申请的能力。提供方需要 `rpc.provide:docs:backend:*`，客户端需要 `rpc.call:docs:backend:*`。shell 初始化两个应用时授予这些 scopes：

```js
await wu.init({
  sandbox: 'module',
  apps: [
    {
      name: 'backend-service',
      url: '/microfrontends/backend-service',
      grants: ['rpc.provide:docs:backend:*'],
    },
    {
      name: 'backend-client',
      url: '/microfrontends/backend-client',
      grants: ['rpc.call:docs:backend:*'],
    },
  ],
});
```

manifest requests 与 shell grants 必须匹配，实际权限来自两者交集。应用使用 `mount` 收到的 scoped facade `context.wu`，无需捕获宿主特权 singleton。[权限参考](/zh-cn/docs/framework/security/)进一步解释 scopes 和撤销。

<span id="publicar-el-canal-y-el-contrato"></span>

<span class="section-anchor" id="section-3"></span>

## 发布通道与契约

提供方挂载过程中，`appWu` 是 scoped facade，`handleRequest` 实现服务：

```js
const release = appWu.handle(
  'docs:backend:request',
  handleRequest,
  { version: '1.0.0', allow: ['backend-client'] },
);

const contract = appWu.provide(
  'docs:backend:service',
  { channel: 'docs:backend:request', method: 'POST' },
  {
    version: '1.0.0',
    shape: { channel: 'string', method: 'string' },
  },
);
```

`handle` 注册通道响应方，`provide` 发布消费方发现服务所需的数据。处理函数 allowlist 将请求方限制为 `backend-client`，拥有 grant 也不能跳过这项额外检查。`shape` 检查契约成员及其类型，请求体验证则属于服务。

在 WU Framework 4.0.0 中，`wu.handle(channel, handler, options)` 和 `wu.eventBus.handle(channel, handler, options)` 均保留版本和允许列表。微应用内部应使用 `context.wu.handle`：除这些选项外，它还应用该应用的身份和授权。请参阅[版本页面](/zh-cn/sources/)以确认实验室的运行时。

通过 `provide` 发布的名称属于首个提供者。其他应用或试图替换它的宿主会收到 `WU_CAPABILITY_OWNED`，直到所有者调用 `revoke` 或通过 `unmount` 卸载。契约版本不能绕过这种所有权。

<span id="consumir-y-llamar-desde-el-cliente"></span>

<span class="section-anchor" id="section-4"></span>

## 从客户端消费并调用

客户端获取契约 proxy，并申请兼容范围。访问 `api.channel` 时会解析可用提供方并检查范围：

```js
const api = appWu.consume('docs:backend:service', '^1.0.0');
const channel = api.channel;
const controller = new AbortController();

const pending = appWu.request(channel, {
  method: api.method,
  path: '/validate',
  body: JSON.stringify({ name: 'catalog', entry: 'entry.js' }),
}, {
  version: '^1.0.0',
  timeout: 3500,
  signal: controller.signal,
});

const response = await pending;
console.log(response.status, response.body);
```

兼容性检查有两层：消费契约和 RPC handler。版本不兼容场景向 `1.0.0` 契约请求 `^2.0.0`，在解析阶段失败，服务尚未开始工作。提供方被移除后也不能处理新请求。签名和错误请参阅[事件、RPC 与契约](/zh-cn/docs/framework/communication/)。

<span id="diferenciar-una-respuesta-de-un-rechazo"></span>

<span class="section-anchor" id="section-5"></span>

## 区分响应与拒绝

服务要求 `POST`、已知路径，以及不超过 4096 UTF-8 字节的字符串请求体。

| 请求 | 服务响应 |
| --- | --- |
| `/validate` 使用有效 JSON，并包含 `name`/`entry` | 200，带 `ok: true` |
| `/validate` 的 manifest 不满足这些规则 | 422，带验证错误 |
| `/uppercase` | 200，返回转换为大写的 ASCII 文本 |
| `/failure` | 500，表示受控服务失败 |
| `/slow`，不取消 | 工作超过 2500 ms 限制时返回 504 |
| 未知路由 | 404，不启动工作 |
| 非 POST 方法 | 405 |
| 无效请求结构 | 400 |
| 请求体超出限制 | 413 |

这些响应把 RPC Promise 解析为对象，客户端必须检查 `response.status`。契约不兼容、提供方缺失或取消则作为拒绝，通过 `try`/`catch` 处理。

manifest 示例仅验证 `name` 和 `entry`。结果声明 `scope: "name+entry"` 和 `completeValidation: false`，不检查完整 manifest、权限、发布路由或 entry 存在性。继续诊断项目请参阅[配置与 manifests](/zh-cn/docs/framework/configuration/)和 [WU doctor](/zh-cn/docs/cli/doctor/)。

<span id="cancelar-y-desmontar-sin-conservar-trabajo-activo"></span>

<span class="section-anchor" id="section-6"></span>

## 取消和卸载后不保留活动工作

请求仍在等待时，客户端可以执行：

```js
controller.abort();
```

处理函数接收包含 `signal` 的 RPC 上下文。本示例中，提供方将信号连接到执行器中断。忽略 `signal` 的处理函数可能在客户端停止等待后继续工作。

3500 ms RPC 时限和 2500 ms 工作限制作用不同：前者限制 WU 等待，后者允许服务终止工作并响应 504。界面提供慢速示例，可在两项时限之前观察取消。

提供方卸载时，实现会停止活动请求并移除发布内容：

```js
backend.dispose();
release();
contract.revoke();
```

WU 也撤销应用关联权限。重新挂载时注册新的 handler 和契约。每个请求在完成、失败或取消时释放 abort 监听器、timer 和执行器。[生命周期指南](/zh-cn/docs/framework/lifecycle/)解释如何将资源所有权绑定到 `mount` 与 `unmount`。

<span id="leer-y-comprobar-el-ejemplo"></span>

<span class="section-anchor" id="section-7"></span>

## 阅读并验证示例

[提供方源码](/microfrontends/backend-service/service.js.txt)展示请求处理和清理。在门户仓库运行检查：

```sh
npm ci
npm run build
npm run dev:shell
# En otra terminal:
npm run test:backend
```

测试覆盖响应、版本不兼容、撤销、重新挂载、超时、取消及请求期间卸载，也检查结束后没有保留 Worker，且流程没有页面错误。

<span id="implementación-del-ejemplo"></span>

<span class="section-anchor" id="section-8"></span>

## 示例实现

提供方在 Worker 中执行项目包含的已编译 WASI 程序，因此能处理真实数据并从浏览器停止工作。宿主和虚拟文件属于服务实现；本章学习的公开契约是 WU，框架不包含通用 WASI 引擎。

可以将实现替换为应用的已授权服务，保持兼容契约，并测试响应、错误和清理。远程服务必须重新验证身份、权限和数据。Node 页面渲染或外部 MCP 客户端连接仍需要对应进程和传输，请参阅 [SSR](/zh-cn/docs/framework/server-recipes/)和 [AI/MCP](/zh-cn/docs/framework/ai-recipes/)。