# 渲染与 HTTP 安全参考

SSR、服务器适配器、序列化和响应头的完整约定。

<span id="servidor-y-navegador-tienen-responsabilidades-distintas"></span>

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

## 服务器与浏览器各有职责

从 `wu-framework/server`、`wu-framework/server/adapters` 或 `wu-framework/server/security` 导入这些函数。渲染接收显式服务器模块，不挂载浏览器组件，也不自动下载 manifest 入口。

每个请求必须保持自己的状态和 nonce。renderer HTML 属于可信内容，外部数据用 `escapeHtml` 处理，不要通过 `renderStateScript` 发送秘密。[服务器示例](/zh-cn/docs/framework/server-recipes/)展示响应与 hydration 的组合。

本页 `RenderResult` 和 `RenderOutput` 名称描述结果形状；要导入实际公开的类型，请检查 `server` 子路径声明。

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

## renderApp

用服务器模块渲染应用，生成 WU hydration 可识别的容器。

`renderApp(appName, options): Promise<RenderResult>`

- **参数：** 非空名称；options.module 必填，props、styles、containerId、className、shadowMode 和 declarative；nonce 可选。
- **返回值：** 包含 html、inner、containerAttrs、state、appName 和 containerId 的对象。
- **错误：** 传播输入或 renderer 错误；发送 HTTP 响应前处理拒绝。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { renderApp, renderHtml, escapeHtml } from 'wu-framework/server';
const card = renderHtml(({ title }) => `<h2>${escapeHtml(title)}</h2>`);
const result = await renderApp('card', { module: card, props: { title: 'Resumen' } });
console.log(result.html);
```

模块必须产生可信 HTML。helper 转义属性和状态，但不净化 render 返回的 HTML，也不自动导入浏览器 URL。

参考：`src/server/wu-server-render.js`.

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

## renderApps

并行渲染列表，汇总 HTML、状态和各应用错误。

`renderApps(apps, options?): Promise<RenderAppsResult>`

- **参数：** {name, module, ...options} 列表；failFast 默认 false；可选共享 nonce。
- **返回值：** {html, byApp, state, errors}；failFast false 保留部分结果。
- **错误：** 默认按条目记录错误；failFast:true 拒绝整个操作，但不取消已运行的 renderers。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { renderApps, renderHtml } from 'wu-framework/server';
const result = await renderApps([
  { name: 'summary', module: renderHtml(() => '<p>Resumen</p>') },
  { name: 'activity', module: renderHtml(() => '<p>Actividad</p>') },
]);
console.log(result.errors);
```

参考：`src/server/wu-server-render.js`.

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

## renderStateScript

将状态序列化到 script，客户端 hydration 时可恢复。

`renderStateScript(state, options?): string`

- **参数：** 可序列化 state；JavaScript 标识符 varName、布尔 merge、可选 nonce。
- **返回值：** 完整 script 标签。
- **错误：** 无效 varName、无效 nonce 或 JSON.stringify 无法序列化的值会被拒绝。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { renderStateScript } from 'wu-framework/server';
const script = renderStateScript({ card: { title: 'Resumen' } }, { merge: true });
```

不要放入秘密，此状态在响应 HTML 中可见。

参考：`src/server/wu-server-render.js`.

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

## declarativeShadowDomPolyfill

缺少原生支持时，为带 shadowrootmode 的 templates 生成兼容 script。

`declarativeShadowDomPolyfill(options?): string`

- **参数：** 可选 options.nonce。
- **返回值：** script HTML；调用 helper 不会执行它。
- **错误：** 无效 nonce 在构造属性时失败。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { declarativeShadowDomPolyfill } from 'wu-framework/server';
const script = declarativeShadowDomPolyfill({ nonce });
```

script 应输出在待转换内容之后；它不是观察未来 templates 的 observer。

参考：`src/server/wu-server-render.js`.

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

## escapeHtml

转义 &、标签符号和引号，供 HTML 文本或属性使用。

`escapeHtml(value): string`

- **参数：** 任意值；null 和 undefined 转为空字符串。
- **返回值：** 转义后的字符串。
- **错误：** 字符串转换可能传播值自身的错误。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { escapeHtml } from 'wu-framework/server';
escapeHtml('<etiqueta>'); // '&lt;etiqueta&gt;'
```

不验证 URL，也不把任意 HTML 转为安全内容。

参考：`src/server/wu-server-render.js`.

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

## serializeState

编码 JSON，通过转义标签字符防止提前关闭 script。

`serializeState(value): string`

- **参数：** JSON 兼容值；根 null/undefined 表示为 null。
- **返回值：** 转义 <、> 和 Unicode 分隔符的 JSON。
- **错误：** 循环引用、BigInt 或不支持的序列化可能抛出。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { serializeState } from 'wu-framework/server';
serializeState({ title: '</script>' });
```

参考：`src/server/wu-server-render.js`.

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

## SSR_MARKER_ATTR / SSR_ROOT_ATTR

暴露协调 renderer 与 hydration 的属性名称。

`SSR_MARKER_ATTR: 'data-wu-ssr'; SSR_ROOT_ATTR: 'data-wu-root'`

- **参数：** wu-framework/server 导出的常量。
- **返回值：** 两个字符串。
- **错误：** 读取常量不验证容器内容。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { SSR_MARKER_ATTR, SSR_ROOT_ATTR } from 'wu-framework/server';
console.log(SSR_MARKER_ATTR, SSR_ROOT_ATTR);
```

参考：`src/server/wu-server-render.js`.

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

## createNonce

通过 crypto.getRandomValues 生成 128 位 nonce。

`createNonce(): string`

- **参数：** 无参数。
- **返回值：** 无 padding 的 base64url 字符串。
- **错误：** 需要 Web Crypto，不可用时抛出。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { createNonce } from 'wu-framework/server';
const nonce = createNonce();
```

参考：`src/server/wu-security-headers.js`.

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

## buildCsp

组合 scripts、styles、images、connections 和 frames 的 CSP 策略。

`buildCsp(options?): string`

- **参数：** nonce；scriptSrc/styleSrc/connectSrc/imgSrc/frameSrc 列表；unsafeInlineStyle；https 默认 true。
- **返回值：** 完整 Content-Security-Policy 值。
- **错误：** 拒绝无效 nonce；origin 列表必须来自可信配置。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { buildCsp } from 'wu-framework/server';
const policy = buildCsp({ nonce, connectSrc: ['https://api.example.com'] });
```

没有 nonce 和 styleSrc 时，策略为样式加入 unsafe-inline；connect-src 也包含 scriptSrc。https:false 省略 upgrade-insecure-requests。

参考：`src/server/wu-security-headers.js`.

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

## securityHeaders

构造 CSP、HSTS、X-Frame-Options、MIME、referrer、permissions 和可选 CORS 响应头。

`securityHeaders(options?): Record<string, string>`

- **参数：** CSP 选项，以及 hsts、frameOptions、referrerPolicy、permissionsPolicy、nosniff、corsOrigin 和 csp。
- **返回值：** 响应头映射，不修改响应。
- **错误：** 生成策略时传播 CSP 验证错误。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { securityHeaders } from 'wu-framework/server';
const headers = securityHeaders({ nonce, hsts: false, corsOrigin: 'https://shell.example.com' });
```

HSTS 默认启用；开发 HTTP 响应应显式关闭。csp:false 省略 CSP。

参考：`src/server/wu-security-headers.js`.

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

## applySecurityHeaders

向兼容 setHeader 的 Node 响应应用响应头。

`applySecurityHeaders(res, options?): response`

- **参数：** 含 setHeader 的 res；getHeader 用于合并现有 Vary。
- **返回值：** 同一响应。
- **错误：** res 缺少 setHeader 或策略无效时抛出。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { applySecurityHeaders } from 'wu-framework/server';
applySecurityHeaders(res, { nonce });
```

参考：`src/server/wu-security-headers.js`.

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

## wuSecurityMiddleware

创建每请求 nonce 的 Express 或 Connect 中间件。

`wuSecurityMiddleware(options?): middleware`

- **参数：**响应头选项；`nonce` 必须是每次请求调用的函数。省略时自动生成新 nonce；构造 wrapper 时会拒绝固定字符串。
- **返回值：** (req,res,next) 函数，写入 req.wuNonce，存在 locals 时写入 res.locals.nonce。
- **错误：** 执行中间件时传播 nonce 或响应头生成错误。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { wuSecurityMiddleware } from 'wu-framework/server';
app.use(wuSecurityMiddleware());
```

参考：`src/server/wu-security-headers.js`.

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

## withSecurityHeaders

包装 Fetch handler，添加响应头并保留响应体和 HTTP 状态。

`withSecurityHeaders(handler, options?): (request) => Promise<Response>`

- **参数：** handler(request,{nonce}) 必须返回 Response。
- **返回值：** 返回新 Response 的异步 handler。
- **错误：** 传播 handler 错误，并要求结果兼容 Response。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { withSecurityHeaders } from 'wu-framework/server';
const GET = withSecurityHeaders(async (_request, { nonce }) => {
  return new Response(`<p>Listo</p>`, { headers: { 'Content-Type': 'text/html' } });
});
```

参考：`src/server/wu-security-headers.js`.

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

## renderReact

将 React renderer 适配为 renderApp 契约。

`renderReact(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **参数：** React Component；可注入 styles、strictMode、react 和 renderToString。
- **返回值：** 带 render 的服务器模块，返回 html、styles 和 state。
- **错误：** 传播输入或 renderer 错误；发送 HTTP 响应前处理拒绝。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { renderReact, renderApp } from 'wu-framework/server';
import React from 'react';
import { renderToString } from 'react-dom/server';
const module = renderReact(Card, { react: React, renderToString });
const result = await renderApp('card', { module, props: { count: 2 } });
```

注入与组件相同的 React 实例，避免重复 runtime。

参考：`src/server/adapters.js`.

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

## renderVue

将 Vue renderer 适配为 renderApp 契约。

`renderVue(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **参数：** Vue Component；可注入 styles、setup(app,props)、createSSRApp 和 renderToString。
- **返回值：** 带 render 的服务器模块，返回 html、styles 和 state。
- **错误：** 传播输入或 renderer 错误；发送 HTTP 响应前处理拒绝。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { renderVue, renderApp } from 'wu-framework/server';
const module = renderVue(Card, { setup(app) { /* app.use(plugin) */ } });
const result = await renderApp('card', { module, props: { count: 2 } });
```

需要 vue 和 @vue/server-renderer，或注入两个函数。

参考：`src/server/adapters.js`.

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

## renderPreact

将 Preact renderer 适配为 renderApp 契约。

`renderPreact(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **参数：** Preact Component；可注入 styles、h 和 renderToString。
- **返回值：** 带 render 的服务器模块，返回 html、styles 和 state。
- **错误：** 传播输入或 renderer 错误；发送 HTTP 响应前处理拒绝。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { renderPreact, renderApp } from 'wu-framework/server';
const module = renderPreact(Card, { styles: ['h2{color:navy}'] });
const result = await renderApp('card', { module, props: { count: 2 } });
```

未注入函数时，需要 preact 和 preact-render-to-string。

参考：`src/server/adapters.js`.

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

## renderSvelte

将 Svelte renderer 适配为 renderApp 契约。

`renderSvelte(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **参数：** 为服务器编译的组件，以及可选 styles。
- **返回值：** 带 render 的服务器模块，返回 html、styles 和 state。
- **错误：** 传播输入或 renderer 错误；发送 HTTP 响应前处理拒绝。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { renderSvelte, renderApp } from 'wu-framework/server';
const module = renderSvelte(Card);
const result = await renderApp('card', { module, props: { count: 2 } });
```

Svelte 4 使用 Component.render；Svelte 5 导入 svelte/server。wrapper 不向文档添加 out.head。

参考：`src/server/adapters.js`.

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

## renderSolid

将 Solid renderer 适配为 renderApp 契约。

`renderSolid(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **参数：** 为 SSR 编译的组件，以及可选 styles。
- **返回值：** 带 render 的服务器模块，返回 html、styles 和 state。
- **错误：** 传播输入或 renderer 错误；发送 HTTP 响应前处理拒绝。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { renderSolid, renderApp } from 'wu-framework/server';
const module = renderSolid(Card);
const result = await renderApp('card', { module, props: { count: 2 } });
```

在服务器环境解析 solid-js/web，不像 React 那样接受注入 renderer。

参考：`src/server/adapters.js`.

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

## renderHtml

将 HTML renderer 适配为 renderApp 契约。

`renderHtml(ComponentOrFn, options?): { render(props): Promise<RenderOutput> }`

- **参数：** (props) => string|Promise<string> 函数，以及字符串/列表/函数形式的 styles。
- **返回值：** 带 render 的服务器模块，返回 html、styles 和 state。
- **错误：** 传播输入或 renderer 错误；发送 HTTP 响应前处理拒绝。
- **权限：** 不使用浏览器 grants；服务器应用控制模块、数据和响应头。
- **清理：** 不创建 listeners 或持久客户端 runtime。

```js
import { renderHtml, renderApp } from 'wu-framework/server';
const module = renderHtml(({ count }) => `<p>${Number(count)}</p>`);
const result = await renderApp('card', { module, props: { count: 2 } });
```

返回 HTML 不经过净化，插值前请转义外部内容。

参考：`src/server/adapters.js`.

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

## createServerRequest

`createServerRequest(options?): WuServerRequest`

创建每次请求私有的上下文，包含 `signal`、`locals`、`renderApp`、`renderApps`、`stream` 和 `dispose`。接受包括 `request` 在内的批处理选项，并将其信号与 `options.signal` 组合。不要在模块全局作用域创建该对象。`dispose(reason)` 会中止、释放 listeners 并清空 `locals`。

```js
import { createServerRequest, renderStateScript, escapeHtml, withSecurityHeaders } from 'wu-framework/server';

export const GET = withSecurityHeaders(async (request, { nonce }) => {
  const scope = createServerRequest({ request, nonce, timeout: 2000, concurrency: 2 });
  try {
    scope.locals.set('requestId', crypto.randomUUID());
    const rendered = await scope.renderApps([{
      name: 'greeting',
      load: async ({ signal }) => {
        signal.throwIfAborted();
        return { props: { label: 'Hello' }, publicState: { label: 'Hello' } };
      },
      module: { render: props => '<p>' + escapeHtml(props.label) + '</p>' },
    }]);
    return new Response(rendered.html + renderStateScript(rendered.state, { nonce }), {
      headers: { 'Content-Type': 'text/html; charset=utf-8' },
    });
  } finally {
    scope.dispose();
  }
});
```

`load(context)` 接收 `{ appName, signal, request, locals }`，返回 `{ props?, publicState? }`。使用 loader 时，仅 `publicState` 成为水合状态；私有 props 和 renderer 返回的 `state` 都不会自动发布。不使用 loader 时保留 renderer 的 `state`。这无法阻止 renderer 自身将秘密写入 HTML。

<span id="cancelación-plazos-y-concurrencia-ssr"></span>

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

## SSR 取消、时限与并发

`renderApp`、`renderApps` 和 `renderAppsStream` 接受 `signal` 和包含 loader 在内的每应用 `timeout`；`timeout: 0` 关闭时限。批次和流接受 `concurrency`，默认无限制。中止会保留 `signal.reason`；未提供原因时使用 `WU_SSR_ABORTED`。超时产生 `WU_SSR_TIMEOUT`。Renderers 和 loaders 必须配合 `context.signal` 停止工作。

未启用 `failFast` 时，单个应用失败允许部分降级；使用 `failFast: true` 时中止批次。取消流会将中止传播给待处理工作。返回 HTTP 流时，应保留上下文直到消费完成或取消；不要在构造响应后立即调用 `dispose()`。

```js
// Reusable wrappers: a function generates a new nonce for each request.
wuSecurityMiddleware({ nonce: () => createNonce() });
withSecurityHeaders(handler, { nonce: () => createNonce() });
// Low-level helpers still accept the nonce of this response.
const nonce = createNonce();
const headers = securityHeaders({ nonce });
```