响应
H3 响应实用工具。
#事件流
#EventStream()
#isEventStream(input)
#清理
#sanitizeStatusCode(statusCode?, defaultStatusCode)
确保状态码是有效的 HTTP 状态码。
#sanitizeStatusMessage(statusMessage)
确保状态消息安全可用于响应。
允许的字符:水平制表符、空格或可见 ASCII 字符:https://www.rfc-editor.org/rfc/rfc7230#section-3.1.2
#静态资源服务
#serveStatic(event, options)
根据请求路径动态提供静态资源。
安全性——路径遍历: serveStatic 会解析 ./.. 片段,但会故意将编码后的分隔符(%2f、%5c)在传递给 getMeta/getContents 的 id 中保持百分号编码状态,这与 event.url.pathname 的行为完全一致。因此,id 具有与路由器和按路径限定范围的 use() 守卫所匹配的相同片段结构:/private%5cx 会保持为一个不透明片段,不会绕过 use("/private/**") 守卫而作为 /private/x 提供服务。请将 id 作为不透明字符串解析到资源根目录中——对其进行解码的后端会重新引入分隔符,从而重新打开漏洞。
非规范路径名不会被提供服务(返回 404,或在设置了 fallthrough 时继续处理):包括多个前导分隔符(//private/x、/\\private/x),或 URL 规范化后仍保留的点片段,也就是使用 %25 嵌套转义表示的路径(/pub/%252e%252e/private/x)。这两种情况都会在缺少更具体的 use("/private/**") 守卫时分派到捕获所有路由,而 serveStatic 能够从中构建的唯一 id 会解析回受保护的路径。资源只能通过其规范拼写访问——也就是路由和 use() 守卫所匹配的拼写。
其他所有内容都会被解码一次,用于磁盘上的查找,因此文件的真实名称可以传递给后端:/50%25.png → /50%.png、/a%20b → /a b,并且嵌套分隔符中的一层 %25 会被剥离(/a%252fb → /a%2fb,仍然是字面量 %2f,绝不会成为边界)。RFC 3986 的保留字符集会保持编码状态,因此 id 绝不会增长出 ? 或 #,也就不会在 URL 中截断它。
对于基于文件系统的资源,serveStatic 有两件事无法强制执行:不区分大小写的文件系统(macOS、Windows)需要将允许/拒绝检查的两侧都进行大小写折叠(否则 /SECRET.env 会绕过针对 /secret.env 的检查);而对于符号链接,需要在跟随链接后再次确认解析出的路径位于资源根目录下(例如 realpath(target))。
#更多响应实用工具
#html(first)
以 HTML 内容响应。
示例:
app.get("/", (event) => html(event, "<h1>Hello, World!</h1>"));#iterable(iterable)
遍历一个数据块源,按顺序发送每个数据块。支持异步操作与数据块的混合发送。
每个数据块必须是字符串或缓冲区。
对于生成器(yield)函数,返回值与 yield 的值处理方式相同。
第一个数据块会在创建响应之前等待完成,因此在生成该数据块期间暂存的状态和标头(event.res.status、event.res.headers)仍会生效。第一个数据块之后设置的所有内容都会被忽略——届时标头已经发送到线路上。(返回原始 ReadableStream 不存在这样的时间窗口:它的响应会在读取流之前创建。)
示例:
return iterable(async function* work() {
// 打开文档主体
yield "<!DOCTYPE html>\n<html><body><h1>Executing...</h1><ol>\n";
// 执行工作 ...
for (let i = 0; i < 1000; i++) {
await delay(1000);
// 报告进度
yield `<li>Completed job #`;
yield i;
yield `</li>\n`;
}
// 关闭报告
return `</ol></body></html>`;
});
async function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}#noContent(status)
响应一个空负载。
示例:
app.get("/", () => noContent());#onDispose(event, cb)
注册一个回调,在事件完全结束后运行:响应正文完成流式传输、客户端断开连接或正文发生错误——适用于所有运行时环境,而不仅仅是 Node.js。
回调在正常完成时接收 undefined,否则接收取消/中止原因。回调按照注册顺序、在全局 onResponse 钩子之后运行;同步抛出和异步拒绝会被吸收(除非应用配置了 silent,否则会通过 console.error 报告),待处理的异步回调会传递给 waitUntil。
在事件销毁后注册会立即调用该回调。只有在请求处理期间(处理器、中间件或 onResponse 中)注册时,才能保证观察到事件的结束。
注意:这表示的是 “h3 已完成对该事件的处理”,而不是 “客户端已收到响应” ——对于非 Node.js 运行时中的非流式正文,它会在响应交给运行时后触发。若要在仍然生成响应期间对客户端断开连接作出反应(例如中止上游 fetch),请改用 event.req.signal。
示例:
app.get("/sse", (event) => {
const interval = setInterval(() => {}, 1000);
onDispose(event, () => clearInterval(interval));
// ... 返回一个流式响应
});#raw(value)
将字符串标记为可信的、已预转义的 HTML,使其被 {@link html} 工具使用时不会再次转义。
仅对完全由你控制的标记使用此方法——将用户输入传递给 raw 会重新引入 XSS 风险。
示例:
// `heading` 是可信的标记;`userName` 会自动转义。
app.get("/", () => html`<div>${raw(heading)}<span>${userName}</span></div>`);示例:
// 原样发送可信的标记字符串:
app.get("/", () => html(raw("<h1>Hello, World!</h1>")));#redirect(location, status, statusText?)
向客户端发送重定向响应。
它会在响应中添加 location 头,默认状态码为 302。
响应体发送一个简单的 HTML 页面,包含 meta 刷新标签,以防客户端忽略响应头时进行重定向。
安全性: 如果 location 来源于用户输入(查询参数、表单字段、标头等),请在重定向前根据允许的目标列表对其进行验证。未经检查地传递用户控制的值会导致开放重定向漏洞。对于“返回上一页”的流程,建议使用 redirectBack,它只接受同源的 referer。
示例:
app.get("/", () => {
return redirect("https://example.com");
});示例:
app.get("/", () => {
return redirect("https://example.com", 301); // 永久重定向
});#redirectBack(event)
使用 referer 头将客户端重定向回上一个页面。
如果 referer 头缺失或是不同的来源,则回退到提供的 URL(默认为 "/")。
默认情况下,只使用 referer 的 pathname(查询字符串和哈希被剥离)以防止伪造的 referer 携带意外参数。设置 allowQuery: true 以保留查询字符串。
安全性: fallback 值必须是一个可信的硬编码路径——绝不要使用用户输入。将用户控制的值(例如查询参数)作为 fallback 会导致开放重定向漏洞。
示例:
app.post("/submit", (event) => {
// 处理表单...
return redirectBack(event, { fallback: "/form" });
});#writeEarlyHints(event, hints)
向客户端写入 HTTP/1.1 103 Early Hints。
在本身不支持早期提示的运行时环境中,此函数会退回到设置响应头,这些响应头可以被 CDN 使用。