请求
正文
assertBodySize(event, limit)
断言请求体大小在指定限制范围内。
该限制会在读取请求体时执行,而不是通过预先缓冲实现:请求会由 srvx 的 limitRequestBody 进行包装,该函数会在数据流动时统计字节数,并在累计总数超过 limit 的瞬间中止请求,同时抛出一个 413 {@link HTTPError}(错误通过 createError 注入)。这样既能保证字节数准确(即使 Content-Length 虚报较小,也能在数据流传输过程中被捕获),又无需将请求体保存在内存中或阻塞流式处理器。
如果真实的 Content-Length 已经超过限制,请求会立即以 413 被拒绝;同时携带 Content-Length 和 Transfer-Encoding 的请求会以 400 被拒绝(请求走私,RFC 7230)。
由于限制是在消费请求体时执行的,分块传输或长度未知的请求体发生溢出时,会在处理器读取请求体时暴露,而不是在处理器执行前返回 413;如果处理器从未读取请求体,则不会对其进行计数。
示例:
app.post("/", async (event) => {
assertBodySize(event, 10 * 1024 * 1024); // 10MB
const data = await event.req.formData();
});
readBody(event, options?)
读取请求体并尝试使用 JSON.parse 或 URLSearchParams 解析。
默认情况下,请求体会被解析为 JSON(当 Content-Type 为 application/x-www-form-urlencoded 时,则回退为 URL 编码解析)。其他请求体类型(例如 multipart/form-data)必须通过 options.type 显式启用,且不会根据请求头自动检测。
示例:
app.post("/", async (event) => {
const body = await readBody(event);
});
示例:
app.post("/upload", async (event) => {
const body = await readBody(event, { type: "formData" });
});
readValidatedBody(event, validate)
尝试通过 readBody 读取请求体,然后使用提供的验证模式或函数进行验证,验证失败抛出错误,成功返回结果。
你可以使用一个简单的函数验证请求体,或者使用与标准架构兼容的库如 zod 来定义验证模式。
示例:
function validateBody(body: any) {
return typeof body === "object" && body !== null;
}
app.post("/", async (event) => {
const body = await readValidatedBody(event, validateBody);
});
示例:
import { z } from "zod";
const objectSchema = z.object({
name: z.string().min(3).max(20),
age: z.number({ coerce: true }).positive().int(),
});
app.post("/", async (event) => {
const body = await readValidatedBody(event, objectSchema);
});
示例:
import * as v from "valibot";
app.post("/", async (event) => {
const body = await readValidatedBody(
event,
v.object({
name: v.pipe(v.string(), v.minLength(3), v.maxLength(20)),
age: v.pipe(v.number(), v.integer(), v.minValue(1)),
}),
{
onError: ({ issues }) => ({
statusText: "自定义验证错误",
message: v.summarize(issues),
}),
},
);
});
查询(HTTP QUERY 方法)
用于 HTTP QUERY 方法(RFC 10008) 的工具:声明资源接受的查询格式,并验证请求的 Content-Type。
appendAcceptQuery(event, mediaTypes)
通过设置 Accept-Query 响应标头,声明资源接受的查询格式(RFC 10008,HTTP QUERY 方法)。
媒体类型会被序列化为 结构化字段 列表:基本媒体类型会变为令牌,任何 ;name=value 参数都会以带引号的字符串形式输出。
示例:
app.query("/search", (event) => {
appendAcceptQuery(event, ["application/sql;charset=UTF-8", "application/jsonpath"]);
// Accept-Query: application/sql;charset="UTF-8", application/jsonpath
return handleSearch(event);
});
requireContentType(event, acceptedTypes)
断言请求中存在 Content-Type,且其值是接受的媒体类型之一,同时遵循 RFC 10008 对 HTTP QUERY 方法的要求。
抛出:
- 如果缺少
Content-Type标头,则抛出400 Bad Request。 - 如果
Content-Type标头格式错误,则抛出422 Unprocessable Content。 - 如果媒体类型不被接受,则抛出
415 Unsupported Media Type。
接受的类型可以使用通配符:* / */* 匹配任何内容,type/* 匹配 type 的任意子类型。
示例:
app.query("/search", async (event) => {
requireContentType(event, ["application/sql", "application/jsonpath"]);
const body = await readBody(event, { type: "text" });
// ...
});
缓存
handleCacheHeaders(event, opts)
检查请求缓存标头(If-None-Match、If-Modified-Since),并添加缓存标头(Last-Modified、ETag、Cache-Control)。
注意:默认会添加 public,但绝不会与调用方提供的 private/no-store 指令同时存在,因此传入 cacheControls: ["private"] 不会再生成相互矛盾的 public, private。
更多请求工具
assertMethod(event, expected, allowHead?)
断言传入请求的方法是否为预期类型,使用 isMethod 进行检查。
如果方法不允许,将抛出 405 错误并包含 Allow 响应头列出允许的方法,符合 RFC 9110 的要求。
如果 allowHead 为 true,且预期方法为 GET,则允许 HEAD 请求通过。
示例:
app.get("/", (event) => {
assertMethod(event, "GET");
// 处理 GET 请求,否则抛出 405 错误
});
getQuery(event)
从请求 URL 获取解析后的查询字符串对象。
示例:
app.get("/", (event) => {
const query = getQuery(event); // { key: "value", key2: ["value1", "value2"] }
});
getRequestHost(event, opts: { xForwardedHost? })
获取请求的主机名。
如果 xForwardedHost 为 true,则优先使用 x-forwarded-host 请求头(若存在)。
如果未找到主机头,将返回空字符串。
**安全性:**返回的主机名反映了客户端提供的 Host(或 X-Forwarded-Host)请求头,并且可能被伪造。除非已在上游固定或验证 Host 值(例如使用预期主机名的允许列表,或使用会覆盖该值的反向代理),否则不要将其用于安全决策(CSRF/来源检查、缓存键、生成发送给其他用户的绝对链接)。
示例:
app.get("/", (event) => {
const host = getRequestHost(event); // "example.com"
});
getRequestIP(event)
尝试从传入请求中获取客户端 IP 地址。
如果 xForwardedFor 为 true,则优先使用 x-forwarded-for 请求头(若存在)。
如果无法确定 IP,则返回 undefined。
示例:
app.get("/", (event) => {
const ip = getRequestIP(event); // "192.0.2.0"
});
getRequestProtocol(event, opts: { xForwardedProto? })
获取请求协议。
如果 xForwardedProto 为 true,则使用 x-forwarded-proto 请求头(若存在)。当该请求头包含以逗号分隔的协议列表时,将使用第一个条目。
注意:此请求头默认未启用(默认为 false),因为客户端可能伪造它。只有当应用运行在会设置此请求头的可信反向代理或 CDN 后面时,才应启用它。此默认值已调整为与 getRequestHost(xForwardedHost)和 getRequestIP(xForwardedFor)保持一致。
如果无法确定协议,则默认为 "http"。
示例:
app.get("/", (event) => {
const protocol = getRequestProtocol(event); // "https"
});
getRequestURL(event, opts: { xForwardedHost?, xForwardedProto? })
生成完整的传入请求 URL。
如果 xForwardedHost 为 true,则使用 x-forwarded-host 请求头(若存在)。
如果 xForwardedProto 为 true,则使用 x-forwarded-proto 请求头(若存在)。
**安全性:**返回 URL 的 .origin 和 .host 来源于客户端提供的 Host(或 X-Forwarded-Host)请求头,并且可能被伪造。除非已在上游固定或验证 Host 值(例如使用预期主机名的允许列表,或使用会覆盖该值的反向代理),否则不要将它们用于安全决策(CSRF/来源检查、缓存键、生成发送给其他用户的绝对链接)。.pathname 和 .search 并非来源于可伪造的主机,但仍属于不可信的客户端输入——请根据其最终使用场景对其进行验证或编码(例如文件系统查找、HTML 输出、下游查询)。
示例:
app.get("/", (event) => {
const url = getRequestURL(event); // "https://example.com/path"
});
getRouterParam(event, name, opts: { decode? })
根据名称获取匹配的路由参数。
如果 decode 选项为 true,则会解码匹配的路由参数(类似于 decodeURIComponent),但编码后的路径分隔符(%2f、%5c)会保持编码状态,因此解码绝不会重新引入路由未匹配到的 / 或 \。
示例:
app.get("/", (event) => {
const param = getRouterParam(event, "key");
});
getRouterParams(event, opts: { decode? })
获取匹配的路由参数集合。
默认情况下,参数会按照其在 URL 路径中的原样返回,仍保持百分号编码。
当 decode: true 时,每个参数会被解码一次(类似于 decodeURIComponent),但编码后的路径分隔符(%2f、%5c,无论处于哪一层 %25 嵌套深度)会保持编码状态,因此解码绝不会重新引入路由从未匹配到的 / 或 \。
单次解码不等同于“完全解码”:%25XX 会解码为字面文本 %XX,因此结果仍可能包含百分号转义序列,包括点号段(%252e%252e -> %2e%2e)和控制字符(%2500 -> %00)。不要再次解码结果:第二次解码会将其还原为路由和中间件层从未看到的路径遍历(../)和分隔符。请将返回的字符串视为最终结果,并按原样进行验证。
示例:
app.get("/", (event) => {
const params = getRouterParams(event); // { key: "value" }
});
示例:
// GET /files/%252e%252e/x
app.get("/files/**:rest", (event) => {
getRouterParams(event); // { rest: "%252e%252e/x" }
getRouterParams(event, { decode: true }); // { rest: "%2e%2e/x" } — 仍保持编码,请勿再次解码
});
getValidatedQuery(event, validate)
获取经过验证函数验证后的请求 URL 查询参数。
你可以使用一个简单的函数来验证查询对象,或者使用一个兼容标准架构的库,如 zod 来定义架构。
示例:
app.get("/", async (event) => {
const query = await getValidatedQuery(event, (data) => {
return "key" in data && typeof data.key === "string";
});
});
示例:
import { z } from "zod";
app.get("/", async (event) => {
const query = await getValidatedQuery(
event,
z.object({
key: z.string(),
}),
);
});
示例:
import * as v from "valibot";
app.get("/", async (event) => {
const params = await getValidatedQuery(
event,
v.object({
key: v.string(),
}),
{
onError: ({ issues }) => ({
statusText: "自定义验证错误",
message: v.summarize(issues),
}),
},
);
});
getValidatedRouterParams(event, validate)
获取匹配的路由参数并使用验证函数验证。
如果 decode 选项为 true,参数会按照 {@link getRouterParams} 中所述的方式解码一次——路径分隔符会保持编码状态,其他转义序列只解码一层,经过验证的值仍可能包含 %XX。请按原样进行验证;不要再次解码。
你可以使用一个简单的函数来验证参数对象,或者使用与标准模式兼容的库(如 zod)来定义模式。
示例:
app.get("/:key", async (event) => {
const params = await getValidatedRouterParams(event, (data) => {
return "key" in data && typeof data.key === "string";
});
});
示例:
import { z } from "zod";
app.get("/:key", async (event) => {
const params = await getValidatedRouterParams(
event,
z.object({
key: z.string(),
}),
);
});
示例:
import * as v from "valibot";
app.get("/:key", async (event) => {
const params = await getValidatedRouterParams(
event,
v.object({
key: v.pipe(v.string(), v.picklist(["route-1", "route-2", "route-3"])),
}),
{
decode: true,
onError: ({ issues }) => ({
statusText: "自定义验证错误",
message: v.summarize(issues),
}),
},
);
});
isMethod(event, expected, allowHead?)
检查传入请求的方法是否为预期类型。
如果 allowHead 为 true,且预期方法为 GET,则允许 HEAD 请求通过。
示例:
app.get("/", (event) => {
if (isMethod(event, "GET")) {
// 处理 GET 请求
} else if (isMethod(event, ["POST", "PUT"])) {
// 处理 POST 或 PUT 请求
}
});
requestWithBaseURL(req, base, options: { url?: URL })
创建一个轻量级请求代理,从 URL 路径名中移除基础路径。
options.url 是要从中移除 base 的已解析请求 URL,用于替代解析 req.url。如果存在 event,请始终传入 event.url:对于非规范路径,它包含父级用来匹配 base 的规范化形式,而 req.url 仍然保留线上的形式;使用从其中一个 URL 的偏移量去截取另一个 URL,正是挂载前缀发生不同步的原因。
requestWithURL(req, url)
创建一个轻量级请求代理,仅覆盖 URL。
避免克隆原始请求(无需 new Request() 分配)。
toRequest(input, options?)
将输入转换为网络 Request。
如果输入是相对 URL,它将根据头部规范化为完整路径。
如果输入已经是 Request 且未提供选项,则将原样返回。
getRequestFingerprint(event, opts)
获取传入请求的唯一指纹。