vafast 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/defineRoute.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +3 -3
- package/dist/node-server/index.mjs +2 -2
- package/dist/node-server/request.mjs +1 -1
- package/dist/node-server/serve.mjs +2 -2
- package/dist/{parsers-BAQtDA1q.d.mts → parsers-CEkO18PZ.d.mts} +25 -1
- package/dist/{parsers-BrG_mRLq.mjs → parsers-CsgGn0xr.mjs} +31 -1
- package/dist/parsers-CsgGn0xr.mjs.map +1 -0
- package/dist/{request-DEWtcK8t.mjs → request-D5oMj6sH.mjs} +17 -3
- package/dist/{request-DEWtcK8t.mjs.map → request-D5oMj6sH.mjs.map} +1 -1
- package/dist/{serve-DVlDG92Y.mjs → serve-Cj0bYhav.mjs} +2 -2
- package/dist/{serve-DVlDG92Y.mjs.map → serve-Cj0bYhav.mjs.map} +1 -1
- package/dist/serve.mjs +2 -2
- package/dist/utils/handle.mjs +1 -1
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/index.mjs +1 -1
- package/dist/utils/parsers.d.mts +1 -1
- package/dist/utils/parsers.mjs +1 -1
- package/package.json +1 -1
- package/dist/parsers-BrG_mRLq.mjs.map +0 -1
package/dist/defineRoute.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as parseCookies, l as parseHeaders, r as parseBody, u as parseQuery } from "./parsers-
|
|
1
|
+
import { a as parseCookies, l as parseHeaders, r as parseBody, u as parseQuery } from "./parsers-CsgGn0xr.mjs";
|
|
2
2
|
import { a as validateAllSchemas, i as precompileSchemas } from "./validators-CkfvNBbK.mjs";
|
|
3
3
|
import { i as json, u as VafastError } from "./response-Bs9GhJz-.mjs";
|
|
4
4
|
|
package/dist/index.d.mts
CHANGED
|
@@ -7,7 +7,7 @@ import { t as DependencyManager } from "./dependency-manager-mqzLAocb.mjs";
|
|
|
7
7
|
import { t as ComponentServer } from "./component-server-CKcXIvMg.mjs";
|
|
8
8
|
import { t as ServerFactory } from "./index-BPXVOE-X.mjs";
|
|
9
9
|
import { a as isVafastError, i as errorHandler, n as VafastError, r as composeMiddleware, t as VAFAST_ERROR_SYMBOL } from "./middleware-BTg4GbjC.mjs";
|
|
10
|
-
import { a as parseBody, c as parseCookiesFast, d as parseHeaders, f as parseQuery, i as getHeader, p as parseQueryFast, r as getCookie, s as parseCookies } from "./parsers-
|
|
10
|
+
import { a as parseBody, c as parseCookiesFast, d as parseHeaders, f as parseQuery, i as getHeader, p as parseQueryFast, r as getCookie, s as parseCookies } from "./parsers-CEkO18PZ.mjs";
|
|
11
11
|
import { c as text, i as json, n as err, o as redirect, r as html, s as stream, t as empty } from "./response-lI0YZoia.mjs";
|
|
12
12
|
import { t as goAwait } from "./go-await-Dz1CRSTT.mjs";
|
|
13
13
|
import { n as base64urlEncode, t as base64urlDecode } from "./base64url-CAmasWF0.mjs";
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as parseCookies, d as parseQueryFast, l as parseHeaders, n as getHeader, o as parseCookiesFast, r as parseBody, t as getCookie, u as parseQuery } from "./parsers-
|
|
1
|
+
import { a as parseCookies, d as parseQueryFast, l as parseHeaders, n as getHeader, o as parseCookiesFast, r as parseBody, t as getCookie, u as parseQuery } from "./parsers-CsgGn0xr.mjs";
|
|
2
2
|
import { a as validateAllSchemas, c as validateSchemaOrThrow, i as precompileSchemas, n as createValidator, o as validateFast, r as getValidatorCacheStats, s as validateSchema } from "./validators-CkfvNBbK.mjs";
|
|
3
3
|
import { c as text, d as composeMiddleware, f as errorHandler, i as json, l as VAFAST_ERROR_SYMBOL, n as err, o as redirect, p as isVafastError, r as html, s as stream, t as empty, u as VafastError } from "./response-Bs9GhJz-.mjs";
|
|
4
4
|
import { defineMiddleware, defineRoute, defineRoutes, sse, withContext } from "./defineRoute.mjs";
|
|
@@ -16,8 +16,8 @@ import { n as getApiSpec, t as generateAITools } from "./contract-vSyKiRwz.mjs";
|
|
|
16
16
|
import "./utils/index.mjs";
|
|
17
17
|
import { normalizePath } from "./router.mjs";
|
|
18
18
|
import "./types/index.mjs";
|
|
19
|
-
import "./request-
|
|
20
|
-
import { n as serve } from "./serve-
|
|
19
|
+
import "./request-D5oMj6sH.mjs";
|
|
20
|
+
import { n as serve } from "./serve-Cj0bYhav.mjs";
|
|
21
21
|
import "./serve.mjs";
|
|
22
22
|
import { FormatRegistry, Type } from "@sinclair/typebox";
|
|
23
23
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as createProxyRequest } from "../request-
|
|
1
|
+
import { t as createProxyRequest } from "../request-D5oMj6sH.mjs";
|
|
2
2
|
import { n as writeResponseSimple, t as writeResponse } from "../response-BlHLmKys.mjs";
|
|
3
|
-
import { n as serve, t as createAdaptorServer } from "../serve-
|
|
3
|
+
import { n as serve, t as createAdaptorServer } from "../serve-Cj0bYhav.mjs";
|
|
4
4
|
|
|
5
5
|
export { createAdaptorServer, createProxyRequest, serve, writeResponse, writeResponseSimple };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "../request-
|
|
2
|
-
import { n as serve, t as createAdaptorServer } from "../serve-
|
|
1
|
+
import "../request-D5oMj6sH.mjs";
|
|
2
|
+
import { n as serve, t as createAdaptorServer } from "../serve-Cj0bYhav.mjs";
|
|
3
3
|
|
|
4
4
|
export { createAdaptorServer, serve };
|
|
@@ -12,6 +12,13 @@ interface FormData {
|
|
|
12
12
|
/**
|
|
13
13
|
* 简化的请求体解析函数
|
|
14
14
|
* 优先简洁性,处理最常见的场景
|
|
15
|
+
*
|
|
16
|
+
* 注意:此函数假设只用于有 body 的请求(POST/PUT/PATCH 等)
|
|
17
|
+
* 对于 GET/HEAD 请求,调用方应在调用前检查请求方法
|
|
18
|
+
* 参考:defineRoute.ts 中已有正确的检查逻辑
|
|
19
|
+
*
|
|
20
|
+
* 如果传入 GET/HEAD 请求,会返回 null(防御性编程)
|
|
21
|
+
* 这样即使调用方忘记检查,也不会导致运行时错误
|
|
15
22
|
*/
|
|
16
23
|
declare function parseBody(req: Request): Promise<unknown>;
|
|
17
24
|
/**
|
|
@@ -22,11 +29,28 @@ declare function parseBodyAs<T>(req: Request): Promise<T>;
|
|
|
22
29
|
/**
|
|
23
30
|
* 解析请求体为表单数据
|
|
24
31
|
* 专门用于处理 multipart/form-data
|
|
32
|
+
*
|
|
33
|
+
* 支持的 HTTP 方法:
|
|
34
|
+
* - POST:创建新资源(最常用)
|
|
35
|
+
* - PUT:替换指定资源
|
|
36
|
+
* - PATCH:部分更新(较少用于文件上传)
|
|
37
|
+
*
|
|
38
|
+
* 注意:GET/HEAD 请求没有 body,调用此函数会抛出错误
|
|
25
39
|
*/
|
|
26
40
|
declare function parseFormData(req: Request): Promise<FormData>;
|
|
27
41
|
/**
|
|
28
42
|
* 解析请求体为文件
|
|
29
43
|
* 专门用于处理文件上传
|
|
44
|
+
*
|
|
45
|
+
* 支持的 HTTP 方法:
|
|
46
|
+
* - POST:上传新文件,服务器决定存储位置(最常用)
|
|
47
|
+
* - PUT:上传到指定位置,或替换已有文件
|
|
48
|
+
*
|
|
49
|
+
* 业界实践参考:
|
|
50
|
+
* - AWS S3、阿里云 OSS 等主流云存储都支持 POST 和 PUT
|
|
51
|
+
* - POST 用于表单上传(multipart),PUT 用于直接上传(binary)
|
|
52
|
+
*
|
|
53
|
+
* 注意:GET/HEAD 请求没有 body,调用此函数会抛出错误
|
|
30
54
|
*/
|
|
31
55
|
declare function parseFile(req: Request): Promise<FileInfo>;
|
|
32
56
|
/** 获取查询字符串,直接返回对象 */
|
|
@@ -55,4 +79,4 @@ declare function parseCookiesFast(req: Request): Record<string, string>;
|
|
|
55
79
|
declare function getCookie(req: Request, name: string): string | null;
|
|
56
80
|
//#endregion
|
|
57
81
|
export { parseBody as a, parseCookiesFast as c, parseHeaders as d, parseQuery as f, getHeader as i, parseFile as l, FormData as n, parseBodyAs as o, parseQueryFast as p, getCookie as r, parseCookies as s, FileInfo as t, parseFormData as u };
|
|
58
|
-
//# sourceMappingURL=parsers-
|
|
82
|
+
//# sourceMappingURL=parsers-CEkO18PZ.d.mts.map
|
|
@@ -5,8 +5,17 @@ import cookie from "cookie";
|
|
|
5
5
|
/**
|
|
6
6
|
* 简化的请求体解析函数
|
|
7
7
|
* 优先简洁性,处理最常见的场景
|
|
8
|
+
*
|
|
9
|
+
* 注意:此函数假设只用于有 body 的请求(POST/PUT/PATCH 等)
|
|
10
|
+
* 对于 GET/HEAD 请求,调用方应在调用前检查请求方法
|
|
11
|
+
* 参考:defineRoute.ts 中已有正确的检查逻辑
|
|
12
|
+
*
|
|
13
|
+
* 如果传入 GET/HEAD 请求,会返回 null(防御性编程)
|
|
14
|
+
* 这样即使调用方忘记检查,也不会导致运行时错误
|
|
8
15
|
*/
|
|
9
16
|
async function parseBody(req) {
|
|
17
|
+
const method = req.method.toUpperCase();
|
|
18
|
+
if (method === "GET" || method === "HEAD") return null;
|
|
10
19
|
const contentType = req.headers.get("content-type") || "";
|
|
11
20
|
if (contentType.includes("application/json")) return await req.json();
|
|
12
21
|
if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
@@ -47,16 +56,37 @@ async function parseBodyAs(req) {
|
|
|
47
56
|
/**
|
|
48
57
|
* 解析请求体为表单数据
|
|
49
58
|
* 专门用于处理 multipart/form-data
|
|
59
|
+
*
|
|
60
|
+
* 支持的 HTTP 方法:
|
|
61
|
+
* - POST:创建新资源(最常用)
|
|
62
|
+
* - PUT:替换指定资源
|
|
63
|
+
* - PATCH:部分更新(较少用于文件上传)
|
|
64
|
+
*
|
|
65
|
+
* 注意:GET/HEAD 请求没有 body,调用此函数会抛出错误
|
|
50
66
|
*/
|
|
51
67
|
async function parseFormData(req) {
|
|
68
|
+
const method = req.method.toUpperCase();
|
|
69
|
+
if (method === "GET" || method === "HEAD") throw new Error("GET/HEAD 请求不能包含表单数据");
|
|
52
70
|
if (!(req.headers.get("content-type") || "").includes("multipart/form-data")) throw new Error("请求不是 multipart/form-data 格式");
|
|
53
71
|
return await parseMultipartFormData(req);
|
|
54
72
|
}
|
|
55
73
|
/**
|
|
56
74
|
* 解析请求体为文件
|
|
57
75
|
* 专门用于处理文件上传
|
|
76
|
+
*
|
|
77
|
+
* 支持的 HTTP 方法:
|
|
78
|
+
* - POST:上传新文件,服务器决定存储位置(最常用)
|
|
79
|
+
* - PUT:上传到指定位置,或替换已有文件
|
|
80
|
+
*
|
|
81
|
+
* 业界实践参考:
|
|
82
|
+
* - AWS S3、阿里云 OSS 等主流云存储都支持 POST 和 PUT
|
|
83
|
+
* - POST 用于表单上传(multipart),PUT 用于直接上传(binary)
|
|
84
|
+
*
|
|
85
|
+
* 注意:GET/HEAD 请求没有 body,调用此函数会抛出错误
|
|
58
86
|
*/
|
|
59
87
|
async function parseFile(req) {
|
|
88
|
+
const method = req.method.toUpperCase();
|
|
89
|
+
if (method === "GET" || method === "HEAD") throw new Error("GET/HEAD 请求不能包含文件");
|
|
60
90
|
if (!(req.headers.get("content-type") || "").includes("multipart/form-data")) throw new Error("请求不是 multipart/form-data 格式");
|
|
61
91
|
const formData = await parseMultipartFormData(req);
|
|
62
92
|
const fileKeys = Object.keys(formData.files);
|
|
@@ -165,4 +195,4 @@ function getCookie(req, name) {
|
|
|
165
195
|
|
|
166
196
|
//#endregion
|
|
167
197
|
export { parseCookies as a, parseFormData as c, parseQueryFast as d, parseBodyAs as i, parseHeaders as l, getHeader as n, parseCookiesFast as o, parseBody as r, parseFile as s, getCookie as t, parseQuery as u };
|
|
168
|
-
//# sourceMappingURL=parsers-
|
|
198
|
+
//# sourceMappingURL=parsers-CsgGn0xr.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parsers-CsgGn0xr.mjs","names":[],"sources":["../src/utils/parsers.ts"],"sourcesContent":["// src/parsers.ts\nimport qs from \"qs\";\nimport cookie from \"cookie\";\n\n// 文件信息接口\nexport interface FileInfo {\n name: string;\n type: string;\n size: number;\n data: ArrayBuffer;\n}\n\n// 表单数据接口\nexport interface FormData {\n fields: Record<string, string>;\n files: Record<string, FileInfo>;\n}\n\n/**\n * 简化的请求体解析函数\n * 优先简洁性,处理最常见的场景\n * \n * 注意:此函数假设只用于有 body 的请求(POST/PUT/PATCH 等)\n * 对于 GET/HEAD 请求,调用方应在调用前检查请求方法\n * 参考:defineRoute.ts 中已有正确的检查逻辑\n * \n * 如果传入 GET/HEAD 请求,会返回 null(防御性编程)\n * 这样即使调用方忘记检查,也不会导致运行时错误\n */\nexport async function parseBody(req: Request): Promise<unknown> {\n // 防御性检查:GET/HEAD 请求没有 body\n // HTTP 规范:GET/HEAD 请求通常不带 body,即使带了 Content-Type 也不应解析\n // 参考 Fastify: \"for GET and HEAD requests, the payload is never parsed\"\n const method = req.method.toUpperCase();\n if (method === \"GET\" || method === \"HEAD\") {\n return null;\n }\n\n const contentType = req.headers.get(\"content-type\") || \"\";\n if (contentType.includes(\"application/json\")) {\n return await req.json();\n }\n if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n const text = await req.text();\n return Object.fromEntries(new URLSearchParams(text));\n }\n return await req.text(); // fallback\n}\n\n/**\n * 解析 multipart/form-data 格式\n * 支持文件上传和普通表单字段\n */\nasync function parseMultipartFormData(req: Request): Promise<FormData> {\n const formData = await req.formData();\n const result: FormData = {\n fields: {},\n files: {},\n };\n\n for (const [key, value] of formData.entries()) {\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"name\" in value &&\n \"type\" in value &&\n \"size\" in value\n ) {\n // 处理文件\n const file = value as any;\n const arrayBuffer = await file.arrayBuffer();\n result.files[key] = {\n name: file.name,\n type: file.type,\n size: file.size,\n data: arrayBuffer,\n };\n } else {\n // 处理普通字段\n result.fields[key] = value as string;\n }\n }\n\n return result;\n}\n\n/**\n * 解析请求体为特定类型\n * 提供类型安全的解析方法\n */\nexport async function parseBodyAs<T>(req: Request): Promise<T> {\n const body = await parseBody(req);\n return body as T;\n}\n\n/**\n * 解析请求体为表单数据\n * 专门用于处理 multipart/form-data\n * \n * 支持的 HTTP 方法:\n * - POST:创建新资源(最常用)\n * - PUT:替换指定资源\n * - PATCH:部分更新(较少用于文件上传)\n * \n * 注意:GET/HEAD 请求没有 body,调用此函数会抛出错误\n */\nexport async function parseFormData(req: Request): Promise<FormData> {\n // 防御性检查:GET/HEAD 请求没有 body\n // HTTP 规范:GET/HEAD 请求不应有 body\n // 文件上传通常使用 POST(新建)或 PUT(替换)\n const method = req.method.toUpperCase();\n if (method === \"GET\" || method === \"HEAD\") {\n throw new Error(\"GET/HEAD 请求不能包含表单数据\");\n }\n\n const contentType = req.headers.get(\"content-type\") || \"\";\n\n if (!contentType.includes(\"multipart/form-data\")) {\n throw new Error(\"请求不是 multipart/form-data 格式\");\n }\n\n return await parseMultipartFormData(req);\n}\n\n/**\n * 解析请求体为文件\n * 专门用于处理文件上传\n * \n * 支持的 HTTP 方法:\n * - POST:上传新文件,服务器决定存储位置(最常用)\n * - PUT:上传到指定位置,或替换已有文件\n * \n * 业界实践参考:\n * - AWS S3、阿里云 OSS 等主流云存储都支持 POST 和 PUT\n * - POST 用于表单上传(multipart),PUT 用于直接上传(binary)\n * \n * 注意:GET/HEAD 请求没有 body,调用此函数会抛出错误\n */\nexport async function parseFile(req: Request): Promise<FileInfo> {\n // 防御性检查:GET/HEAD 请求没有 body\n // HTTP 规范:GET/HEAD 请求不应有 body\n const method = req.method.toUpperCase();\n if (method === \"GET\" || method === \"HEAD\") {\n throw new Error(\"GET/HEAD 请求不能包含文件\");\n }\n\n const contentType = req.headers.get(\"content-type\") || \"\";\n\n if (!contentType.includes(\"multipart/form-data\")) {\n throw new Error(\"请求不是 multipart/form-data 格式\");\n }\n\n const formData = await parseMultipartFormData(req);\n const fileKeys = Object.keys(formData.files);\n\n if (fileKeys.length === 0) {\n throw new Error(\"请求中没有文件\");\n }\n\n if (fileKeys.length > 1) {\n throw new Error(\"请求中包含多个文件,请使用 parseFormData\");\n }\n\n return formData.files[fileKeys[0]];\n}\n\n/**\n * 快速提取 query string(避免创建 URL 对象)\n */\nfunction extractQueryString(url: string): string {\n const qIndex = url.indexOf(\"?\");\n if (qIndex === -1) return \"\";\n\n const hashIndex = url.indexOf(\"#\", qIndex);\n return hashIndex === -1\n ? url.substring(qIndex + 1)\n : url.substring(qIndex + 1, hashIndex);\n}\n\n/** 获取查询字符串,直接返回对象 */\nexport function parseQuery(req: Request): Record<string, unknown> {\n const queryString = extractQueryString(req.url);\n if (!queryString) return {};\n return qs.parse(queryString);\n}\n\n/**\n * 快速解析简单查询字符串(不支持嵌套,但更快)\n * 适用于简单的 key=value&key2=value2 场景\n */\nexport function parseQueryFast(req: Request): Record<string, string> {\n const queryString = extractQueryString(req.url);\n if (!queryString) return {};\n\n const result: Record<string, string> = Object.create(null);\n const pairs = queryString.split(\"&\");\n\n for (const pair of pairs) {\n const eqIndex = pair.indexOf(\"=\");\n if (eqIndex === -1) {\n result[decodeURIComponent(pair)] = \"\";\n } else {\n const key = decodeURIComponent(pair.substring(0, eqIndex));\n const value = decodeURIComponent(pair.substring(eqIndex + 1));\n result[key] = value;\n }\n }\n\n return result;\n}\n\n/** 解析请求头,返回对象 */\nexport function parseHeaders(req: Request): Record<string, string> {\n const headers: Record<string, string> = Object.create(null);\n req.headers.forEach((value, key) => {\n headers[key] = value;\n });\n return headers;\n}\n\n/**\n * 获取单个请求头(避免解析全部)\n */\nexport function getHeader(req: Request, name: string): string | null {\n return req.headers.get(name);\n}\n\n/** 使用cookie库解析Cookie,保证可靠性 */\nexport function parseCookies(req: Request): Record<string, string> {\n const cookieHeader = req.headers.get(\"cookie\");\n if (!cookieHeader) return {};\n\n try {\n const parsed = cookie.parse(cookieHeader);\n // 过滤掉undefined和null值\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(parsed)) {\n if (value !== undefined && value !== null) {\n result[key] = value;\n }\n }\n return result;\n } catch {\n return {};\n }\n}\n\n/**\n * 快速解析 Cookie(简化版,不使用外部库)\n * 适用于简单的 cookie 场景\n */\nexport function parseCookiesFast(req: Request): Record<string, string> {\n const cookieHeader = req.headers.get(\"cookie\");\n if (!cookieHeader) return {};\n\n const result: Record<string, string> = Object.create(null);\n const pairs = cookieHeader.split(\";\");\n\n for (const pair of pairs) {\n const trimmed = pair.trim();\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex > 0) {\n const key = trimmed.substring(0, eqIndex).trim();\n const value = trimmed.substring(eqIndex + 1).trim();\n // 移除引号\n result[key] =\n value.startsWith('\"') && value.endsWith('\"')\n ? value.slice(1, -1)\n : value;\n }\n }\n\n return result;\n}\n\n/**\n * 获取单个 Cookie 值(避免解析全部)\n */\nexport function getCookie(req: Request, name: string): string | null {\n const cookieHeader = req.headers.get(\"cookie\");\n if (!cookieHeader) return null;\n\n const prefix = `${name}=`;\n const pairs = cookieHeader.split(\";\");\n\n for (const pair of pairs) {\n const trimmed = pair.trim();\n if (trimmed.startsWith(prefix)) {\n const value = trimmed.substring(prefix.length).trim();\n return value.startsWith('\"') && value.endsWith('\"')\n ? value.slice(1, -1)\n : value;\n }\n }\n\n return null;\n}\n"],"mappings":"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"}
|
|
@@ -173,17 +173,31 @@ Object.defineProperty(requestPrototype, "_getRequest", {
|
|
|
173
173
|
enumerable: true
|
|
174
174
|
});
|
|
175
175
|
});
|
|
176
|
+
Object.defineProperty(requestPrototype, "clone", {
|
|
177
|
+
value: function() {
|
|
178
|
+
return this._getRequest().clone();
|
|
179
|
+
},
|
|
180
|
+
enumerable: true
|
|
181
|
+
});
|
|
176
182
|
[
|
|
177
183
|
"arrayBuffer",
|
|
178
184
|
"blob",
|
|
179
|
-
"clone",
|
|
180
185
|
"formData",
|
|
181
186
|
"json",
|
|
182
187
|
"text"
|
|
183
188
|
].forEach((key) => {
|
|
184
189
|
Object.defineProperty(requestPrototype, key, {
|
|
185
190
|
value: function() {
|
|
186
|
-
const
|
|
191
|
+
const self = this;
|
|
192
|
+
const method = self[incomingKey].method || "GET";
|
|
193
|
+
if (method === "GET" || method === "HEAD") {
|
|
194
|
+
if (key === "json") return Promise.resolve(null);
|
|
195
|
+
if (key === "text") return Promise.resolve("");
|
|
196
|
+
if (key === "arrayBuffer") return Promise.resolve(/* @__PURE__ */ new ArrayBuffer(0));
|
|
197
|
+
if (key === "blob") return Promise.resolve(new Blob([]));
|
|
198
|
+
if (key === "formData") return Promise.resolve(new FormData());
|
|
199
|
+
}
|
|
200
|
+
const req = self._getRequest();
|
|
187
201
|
return req[key].call(req);
|
|
188
202
|
},
|
|
189
203
|
enumerable: true
|
|
@@ -227,4 +241,4 @@ function createProxyRequest(incoming, defaultHost, options) {
|
|
|
227
241
|
|
|
228
242
|
//#endregion
|
|
229
243
|
export { createProxyRequest as t };
|
|
230
|
-
//# sourceMappingURL=request-
|
|
244
|
+
//# sourceMappingURL=request-D5oMj6sH.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-DEWtcK8t.mjs","names":[],"sources":["../src/node-server/request.ts"],"sourcesContent":["/**\n * 优化的 Request 代理\n * 延迟创建真实 Request,减少不必要的对象分配\n */\n\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream as NodeReadableStream } from \"node:stream/web\";\nimport type { IncomingMessage } from \"node:http\";\nimport type { Socket } from \"node:net\";\n\n// 内部 Symbol\nconst requestCache = Symbol(\"requestCache\");\nconst incomingKey = Symbol(\"incoming\");\nconst urlKey = Symbol(\"url\");\nconst headersKey = Symbol(\"headers\");\nconst ipKey = Symbol(\"ip\");\nconst ipsKey = Symbol(\"ips\");\n\n/** 信任代理配置类型 */\nexport type TrustProxyOption = boolean | string | string[];\n\n/**\n * IP 解析优先级列表\n * 参考业界标准和各大云厂商\n */\nconst IP_HEADERS = [\n \"x-forwarded-for\", // 标准代理头(优先级最高)\n \"x-real-ip\", // Nginx\n \"x-client-ip\", // Apache\n \"cf-connecting-ip\", // Cloudflare\n \"fastly-client-ip\", // Fastly\n \"x-cluster-client-ip\", // GCP\n \"true-client-ip\", // Akamai & Cloudflare\n \"fly-client-ip\", // Fly.io\n \"x-forwarded\", // RFC 7239\n \"forwarded-for\", // RFC 7239\n \"forwarded\", // RFC 7239\n \"appengine-user-ip\", // GCP AppEngine\n \"cf-pseudo-ipv4\", // Cloudflare IPv6 兼容\n];\n\n/**\n * 检查 IP 是否在 CIDR 范围内\n */\nfunction isIpInCidr(ip: string, cidr: string): boolean {\n // 简单实现:只支持精确匹配和 /8, /16, /24 掩码\n if (!cidr.includes(\"/\")) {\n return ip === cidr;\n }\n \n const [network, maskStr] = cidr.split(\"/\");\n const mask = parseInt(maskStr, 10);\n \n const ipParts = ip.split(\".\").map(Number);\n const networkParts = network.split(\".\").map(Number);\n \n if (ipParts.length !== 4 || networkParts.length !== 4) {\n return false;\n }\n \n const ipNum = (ipParts[0] << 24) | (ipParts[1] << 16) | (ipParts[2] << 8) | ipParts[3];\n const networkNum = (networkParts[0] << 24) | (networkParts[1] << 16) | (networkParts[2] << 8) | networkParts[3];\n const maskNum = ~((1 << (32 - mask)) - 1);\n \n return (ipNum & maskNum) === (networkNum & maskNum);\n}\n\n/**\n * 检查是否应该信任代理\n */\nfunction shouldTrustProxy(\n socketIp: string | undefined,\n trustProxy: TrustProxyOption,\n): boolean {\n if (trustProxy === true) return true;\n if (trustProxy === false || !trustProxy) return false;\n if (!socketIp) return false;\n \n const trustedList = Array.isArray(trustProxy) ? trustProxy : [trustProxy];\n return trustedList.some((trusted) => isIpInCidr(socketIp, trusted));\n}\n\n/**\n * 从请求头解析客户端 IP\n */\nfunction parseClientIp(\n incoming: IncomingMessage,\n trustProxy: TrustProxyOption,\n): { ip: string; ips: string[] } {\n const socket = incoming.socket as Socket;\n const socketIp = socket.remoteAddress || \"\";\n \n // 如果不信任代理,直接返回 socket IP\n if (!shouldTrustProxy(socketIp, trustProxy)) {\n return { ip: socketIp, ips: [socketIp] };\n }\n \n // 尝试从各种头中获取 IP\n for (const header of IP_HEADERS) {\n const value = incoming.headers[header];\n if (value) {\n const headerValue = Array.isArray(value) ? value[0] : value;\n // X-Forwarded-For 可能包含多个 IP,逗号分隔\n if (header === \"x-forwarded-for\") {\n const ips = headerValue.split(\",\").map((ip) => ip.trim()).filter(Boolean);\n if (ips.length > 0) {\n return { ip: ips[0], ips };\n }\n } else {\n return { ip: headerValue.trim(), ips: [headerValue.trim()] };\n }\n }\n }\n \n // 回退到 socket IP\n return { ip: socketIp, ips: [socketIp] };\n}\n\n/**\n * 从 rawHeaders 高效解析 Headers\n */\nfunction parseHeaders(rawHeaders: string[]): Headers {\n const headers = new Headers();\n for (let i = 0; i < rawHeaders.length; i += 2) {\n const key = rawHeaders[i];\n const value = rawHeaders[i + 1];\n // 跳过 HTTP/2 伪头 (以 : 开头)\n if (key.charCodeAt(0) !== 58) {\n headers.append(key, value);\n }\n }\n return headers;\n}\n\n/**\n * 将 Node.js ReadableStream 转换为 Web 标准 ReadableStream\n * Node.js 和 Web 标准的 ReadableStream 在运行时兼容,但 TypeScript 类型不同\n */\nfunction toWebStream(\n nodeStream: NodeReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n // Node.js ReadableStream 和 Web ReadableStream 在运行时是兼容的\n // 这里使用类型断言是安全的,因为 Node.js >= 18 的 stream/web 完全实现了 WHATWG Streams 标准\n return nodeStream as unknown as ReadableStream<Uint8Array>;\n}\n\n/** 代理 Request 内部接口 */\ninterface ProxyRequestInternal {\n [requestCache]?: Request;\n [incomingKey]: IncomingMessage;\n [urlKey]: string;\n [headersKey]?: Headers;\n [ipKey]?: string;\n [ipsKey]?: string[];\n _getRequest(): Request;\n}\n\n/**\n * 创建真实的 Request 对象\n */\nfunction createRealRequest(proxy: ProxyRequestInternal): Request {\n const incoming = proxy[incomingKey];\n const method = incoming.method || \"GET\";\n const init: RequestInit & { duplex?: string } = {\n method,\n headers: proxy[headersKey] || parseHeaders(incoming.rawHeaders),\n };\n\n // 只有非 GET/HEAD 请求才有 body\n if (method !== \"GET\" && method !== \"HEAD\") {\n // 使用 Node.js 原生流转换,避免收集 chunks\n const nodeWebStream = Readable.toWeb(\n incoming,\n ) as NodeReadableStream<Uint8Array>;\n init.body = toWebStream(nodeWebStream);\n init.duplex = \"half\";\n }\n\n return new Request(proxy[urlKey], init);\n}\n\n/**\n * Request 代理原型\n * 使用 Object.defineProperty 定义属性以支持 this 绑定\n */\nconst requestPrototype: object = {};\n\n// 定义 method 属性\nObject.defineProperty(requestPrototype, \"method\", {\n get() {\n const self = this as ProxyRequestInternal;\n return self[incomingKey].method || \"GET\";\n },\n enumerable: true,\n});\n\n// 定义 url 属性\nObject.defineProperty(requestPrototype, \"url\", {\n get() {\n const self = this as ProxyRequestInternal;\n return self[urlKey];\n },\n enumerable: true,\n});\n\n// 定义 headers 属性(延迟解析)\nObject.defineProperty(requestPrototype, \"headers\", {\n get() {\n const self = this as ProxyRequestInternal;\n if (!self[headersKey]) {\n self[headersKey] = parseHeaders(self[incomingKey].rawHeaders);\n }\n return self[headersKey];\n },\n enumerable: true,\n});\n\n// 定义 _getRequest 方法(获取或创建真实 Request)\nObject.defineProperty(requestPrototype, \"_getRequest\", {\n value: function () {\n const self = this as ProxyRequestInternal;\n if (!self[requestCache]) {\n self[requestCache] = createRealRequest(self);\n }\n return self[requestCache]!;\n },\n enumerable: false,\n});\n\n// 代理需要访问真实 Request 的属性\nconst proxyGetters = [\n \"body\",\n \"bodyUsed\",\n \"signal\",\n \"cache\",\n \"credentials\",\n \"destination\",\n \"integrity\",\n \"mode\",\n \"redirect\",\n \"referrer\",\n \"referrerPolicy\",\n \"keepalive\",\n];\n\nproxyGetters.forEach((key) => {\n Object.defineProperty(requestPrototype, key, {\n get() {\n const self = this as ProxyRequestInternal;\n return self._getRequest()[key as keyof Request];\n },\n enumerable: true,\n });\n});\n\n// 代理需要调用真实 Request 的方法\nconst proxyMethods = [\n \"arrayBuffer\",\n \"blob\",\n \"clone\",\n \"formData\",\n \"json\",\n \"text\",\n];\n\nproxyMethods.forEach((key) => {\n Object.defineProperty(requestPrototype, key, {\n value: function () {\n const self = this as ProxyRequestInternal;\n const req = self._getRequest();\n return (req[key as keyof Request] as () => Promise<unknown>).call(req);\n },\n enumerable: true,\n });\n});\n\n// 定义 ip 属性(客户端真实 IP)\nObject.defineProperty(requestPrototype, \"ip\", {\n get() {\n const self = this as ProxyRequestInternal;\n return self[ipKey] || \"\";\n },\n enumerable: true,\n});\n\n// 定义 ips 属性(代理链中的所有 IP)\nObject.defineProperty(requestPrototype, \"ips\", {\n get() {\n const self = this as ProxyRequestInternal;\n return self[ipsKey] || [];\n },\n enumerable: true,\n});\n\n// 设置原型链\nObject.setPrototypeOf(requestPrototype, Request.prototype);\n\n/** 创建代理 Request 的选项 */\nexport interface CreateProxyRequestOptions {\n /** 信任代理配置 */\n trustProxy?: TrustProxyOption;\n}\n\n/**\n * 创建代理 Request\n * @param incoming Node.js IncomingMessage\n * @param defaultHost 默认主机名\n * @param options 可选配置\n */\nexport function createProxyRequest(\n incoming: IncomingMessage,\n defaultHost: string,\n options?: CreateProxyRequestOptions,\n): Request {\n const req = Object.create(requestPrototype) as ProxyRequestInternal;\n req[incomingKey] = incoming;\n\n // 构建 URL\n const host = incoming.headers.host || defaultHost;\n const protocol = (incoming.socket as { encrypted?: boolean }).encrypted\n ? \"https\"\n : \"http\";\n req[urlKey] = `${protocol}://${host}${incoming.url || \"/\"}`;\n\n // 解析客户端 IP(如果启用了 trustProxy)\n if (options?.trustProxy) {\n const { ip, ips } = parseClientIp(incoming, options.trustProxy);\n req[ipKey] = ip;\n req[ipsKey] = ips;\n } else {\n // 默认使用 socket IP\n const socketIp = (incoming.socket as Socket).remoteAddress || \"\";\n req[ipKey] = socketIp;\n req[ipsKey] = [socketIp];\n }\n\n return req as unknown as Request;\n}\n"],"mappings":";;;;;;;AAWA,MAAM,eAAe,OAAO,eAAe;AAC3C,MAAM,cAAc,OAAO,WAAW;AACtC,MAAM,SAAS,OAAO,MAAM;AAC5B,MAAM,aAAa,OAAO,UAAU;AACpC,MAAM,QAAQ,OAAO,KAAK;AAC1B,MAAM,SAAS,OAAO,MAAM;;;;;AAS5B,MAAM,aAAa;CACjB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;;;;AAKD,SAAS,WAAW,IAAY,MAAuB;AAErD,KAAI,CAAC,KAAK,SAAS,IAAI,CACrB,QAAO,OAAO;CAGhB,MAAM,CAAC,SAAS,WAAW,KAAK,MAAM,IAAI;CAC1C,MAAM,OAAO,SAAS,SAAS,GAAG;CAElC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,IAAI,OAAO;CACzC,MAAM,eAAe,QAAQ,MAAM,IAAI,CAAC,IAAI,OAAO;AAEnD,KAAI,QAAQ,WAAW,KAAK,aAAa,WAAW,EAClD,QAAO;CAGT,MAAM,QAAS,QAAQ,MAAM,KAAO,QAAQ,MAAM,KAAO,QAAQ,MAAM,IAAK,QAAQ;CACpF,MAAM,aAAc,aAAa,MAAM,KAAO,aAAa,MAAM,KAAO,aAAa,MAAM,IAAK,aAAa;CAC7G,MAAM,UAAU,GAAG,KAAM,KAAK,QAAS;AAEvC,SAAQ,QAAQ,cAAc,aAAa;;;;;AAM7C,SAAS,iBACP,UACA,YACS;AACT,KAAI,eAAe,KAAM,QAAO;AAChC,KAAI,eAAe,SAAS,CAAC,WAAY,QAAO;AAChD,KAAI,CAAC,SAAU,QAAO;AAGtB,SADoB,MAAM,QAAQ,WAAW,GAAG,aAAa,CAAC,WAAW,EACtD,MAAM,YAAY,WAAW,UAAU,QAAQ,CAAC;;;;;AAMrE,SAAS,cACP,UACA,YAC+B;CAE/B,MAAM,WADS,SAAS,OACA,iBAAiB;AAGzC,KAAI,CAAC,iBAAiB,UAAU,WAAW,CACzC,QAAO;EAAE,IAAI;EAAU,KAAK,CAAC,SAAS;EAAE;AAI1C,MAAK,MAAM,UAAU,YAAY;EAC/B,MAAM,QAAQ,SAAS,QAAQ;AAC/B,MAAI,OAAO;GACT,MAAM,cAAc,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK;AAEtD,OAAI,WAAW,mBAAmB;IAChC,MAAM,MAAM,YAAY,MAAM,IAAI,CAAC,KAAK,OAAO,GAAG,MAAM,CAAC,CAAC,OAAO,QAAQ;AACzE,QAAI,IAAI,SAAS,EACf,QAAO;KAAE,IAAI,IAAI;KAAI;KAAK;SAG5B,QAAO;IAAE,IAAI,YAAY,MAAM;IAAE,KAAK,CAAC,YAAY,MAAM,CAAC;IAAE;;;AAMlE,QAAO;EAAE,IAAI;EAAU,KAAK,CAAC,SAAS;EAAE;;;;;AAM1C,SAAS,aAAa,YAA+B;CACnD,MAAM,UAAU,IAAI,SAAS;AAC7B,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK,GAAG;EAC7C,MAAM,MAAM,WAAW;EACvB,MAAM,QAAQ,WAAW,IAAI;AAE7B,MAAI,IAAI,WAAW,EAAE,KAAK,GACxB,SAAQ,OAAO,KAAK,MAAM;;AAG9B,QAAO;;;;;;AAOT,SAAS,YACP,YAC4B;AAG5B,QAAO;;;;;AAiBT,SAAS,kBAAkB,OAAsC;CAC/D,MAAM,WAAW,MAAM;CACvB,MAAM,SAAS,SAAS,UAAU;CAClC,MAAM,OAA0C;EAC9C;EACA,SAAS,MAAM,eAAe,aAAa,SAAS,WAAW;EAChE;AAGD,KAAI,WAAW,SAAS,WAAW,QAAQ;AAKzC,OAAK,OAAO,YAHU,SAAS,MAC7B,SACD,CACqC;AACtC,OAAK,SAAS;;AAGhB,QAAO,IAAI,QAAQ,MAAM,SAAS,KAAK;;;;;;AAOzC,MAAM,mBAA2B,EAAE;AAGnC,OAAO,eAAe,kBAAkB,UAAU;CAChD,MAAM;AAEJ,SADa,KACD,aAAa,UAAU;;CAErC,YAAY;CACb,CAAC;AAGF,OAAO,eAAe,kBAAkB,OAAO;CAC7C,MAAM;AAEJ,SADa,KACD;;CAEd,YAAY;CACb,CAAC;AAGF,OAAO,eAAe,kBAAkB,WAAW;CACjD,MAAM;EACJ,MAAM,OAAO;AACb,MAAI,CAAC,KAAK,YACR,MAAK,cAAc,aAAa,KAAK,aAAa,WAAW;AAE/D,SAAO,KAAK;;CAEd,YAAY;CACb,CAAC;AAGF,OAAO,eAAe,kBAAkB,eAAe;CACrD,OAAO,WAAY;EACjB,MAAM,OAAO;AACb,MAAI,CAAC,KAAK,cACR,MAAK,gBAAgB,kBAAkB,KAAK;AAE9C,SAAO,KAAK;;CAEd,YAAY;CACb,CAAC;AAGmB;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAEY,SAAS,QAAQ;AAC5B,QAAO,eAAe,kBAAkB,KAAK;EAC3C,MAAM;AAEJ,UADa,KACD,aAAa,CAAC;;EAE5B,YAAY;EACb,CAAC;EACF;AAGmB;CACnB;CACA;CACA;CACA;CACA;CACA;CACD,CAEY,SAAS,QAAQ;AAC5B,QAAO,eAAe,kBAAkB,KAAK;EAC3C,OAAO,WAAY;GAEjB,MAAM,MADO,KACI,aAAa;AAC9B,UAAQ,IAAI,KAAiD,KAAK,IAAI;;EAExE,YAAY;EACb,CAAC;EACF;AAGF,OAAO,eAAe,kBAAkB,MAAM;CAC5C,MAAM;AAEJ,SADa,KACD,UAAU;;CAExB,YAAY;CACb,CAAC;AAGF,OAAO,eAAe,kBAAkB,OAAO;CAC7C,MAAM;AAEJ,SADa,KACD,WAAW,EAAE;;CAE3B,YAAY;CACb,CAAC;AAGF,OAAO,eAAe,kBAAkB,QAAQ,UAAU;;;;;;;AAc1D,SAAgB,mBACd,UACA,aACA,SACS;CACT,MAAM,MAAM,OAAO,OAAO,iBAAiB;AAC3C,KAAI,eAAe;CAGnB,MAAM,OAAO,SAAS,QAAQ,QAAQ;AAItC,KAAI,UAAU,GAHI,SAAS,OAAmC,YAC1D,UACA,OACsB,KAAK,OAAO,SAAS,OAAO;AAGtD,KAAI,SAAS,YAAY;EACvB,MAAM,EAAE,IAAI,QAAQ,cAAc,UAAU,QAAQ,WAAW;AAC/D,MAAI,SAAS;AACb,MAAI,UAAU;QACT;EAEL,MAAM,WAAY,SAAS,OAAkB,iBAAiB;AAC9D,MAAI,SAAS;AACb,MAAI,UAAU,CAAC,SAAS;;AAG1B,QAAO"}
|
|
1
|
+
{"version":3,"file":"request-D5oMj6sH.mjs","names":[],"sources":["../src/node-server/request.ts"],"sourcesContent":["/**\n * 优化的 Request 代理\n * 延迟创建真实 Request,减少不必要的对象分配\n */\n\nimport { Readable } from \"node:stream\";\nimport type { ReadableStream as NodeReadableStream } from \"node:stream/web\";\nimport type { IncomingMessage } from \"node:http\";\nimport type { Socket } from \"node:net\";\n\n// 内部 Symbol\nconst requestCache = Symbol(\"requestCache\");\nconst incomingKey = Symbol(\"incoming\");\nconst urlKey = Symbol(\"url\");\nconst headersKey = Symbol(\"headers\");\nconst ipKey = Symbol(\"ip\");\nconst ipsKey = Symbol(\"ips\");\n\n/** 信任代理配置类型 */\nexport type TrustProxyOption = boolean | string | string[];\n\n/**\n * IP 解析优先级列表\n * 参考业界标准和各大云厂商\n */\nconst IP_HEADERS = [\n \"x-forwarded-for\", // 标准代理头(优先级最高)\n \"x-real-ip\", // Nginx\n \"x-client-ip\", // Apache\n \"cf-connecting-ip\", // Cloudflare\n \"fastly-client-ip\", // Fastly\n \"x-cluster-client-ip\", // GCP\n \"true-client-ip\", // Akamai & Cloudflare\n \"fly-client-ip\", // Fly.io\n \"x-forwarded\", // RFC 7239\n \"forwarded-for\", // RFC 7239\n \"forwarded\", // RFC 7239\n \"appengine-user-ip\", // GCP AppEngine\n \"cf-pseudo-ipv4\", // Cloudflare IPv6 兼容\n];\n\n/**\n * 检查 IP 是否在 CIDR 范围内\n */\nfunction isIpInCidr(ip: string, cidr: string): boolean {\n // 简单实现:只支持精确匹配和 /8, /16, /24 掩码\n if (!cidr.includes(\"/\")) {\n return ip === cidr;\n }\n \n const [network, maskStr] = cidr.split(\"/\");\n const mask = parseInt(maskStr, 10);\n \n const ipParts = ip.split(\".\").map(Number);\n const networkParts = network.split(\".\").map(Number);\n \n if (ipParts.length !== 4 || networkParts.length !== 4) {\n return false;\n }\n \n const ipNum = (ipParts[0] << 24) | (ipParts[1] << 16) | (ipParts[2] << 8) | ipParts[3];\n const networkNum = (networkParts[0] << 24) | (networkParts[1] << 16) | (networkParts[2] << 8) | networkParts[3];\n const maskNum = ~((1 << (32 - mask)) - 1);\n \n return (ipNum & maskNum) === (networkNum & maskNum);\n}\n\n/**\n * 检查是否应该信任代理\n */\nfunction shouldTrustProxy(\n socketIp: string | undefined,\n trustProxy: TrustProxyOption,\n): boolean {\n if (trustProxy === true) return true;\n if (trustProxy === false || !trustProxy) return false;\n if (!socketIp) return false;\n \n const trustedList = Array.isArray(trustProxy) ? trustProxy : [trustProxy];\n return trustedList.some((trusted) => isIpInCidr(socketIp, trusted));\n}\n\n/**\n * 从请求头解析客户端 IP\n */\nfunction parseClientIp(\n incoming: IncomingMessage,\n trustProxy: TrustProxyOption,\n): { ip: string; ips: string[] } {\n const socket = incoming.socket as Socket;\n const socketIp = socket.remoteAddress || \"\";\n \n // 如果不信任代理,直接返回 socket IP\n if (!shouldTrustProxy(socketIp, trustProxy)) {\n return { ip: socketIp, ips: [socketIp] };\n }\n \n // 尝试从各种头中获取 IP\n for (const header of IP_HEADERS) {\n const value = incoming.headers[header];\n if (value) {\n const headerValue = Array.isArray(value) ? value[0] : value;\n // X-Forwarded-For 可能包含多个 IP,逗号分隔\n if (header === \"x-forwarded-for\") {\n const ips = headerValue.split(\",\").map((ip) => ip.trim()).filter(Boolean);\n if (ips.length > 0) {\n return { ip: ips[0], ips };\n }\n } else {\n return { ip: headerValue.trim(), ips: [headerValue.trim()] };\n }\n }\n }\n \n // 回退到 socket IP\n return { ip: socketIp, ips: [socketIp] };\n}\n\n/**\n * 从 rawHeaders 高效解析 Headers\n */\nfunction parseHeaders(rawHeaders: string[]): Headers {\n const headers = new Headers();\n for (let i = 0; i < rawHeaders.length; i += 2) {\n const key = rawHeaders[i];\n const value = rawHeaders[i + 1];\n // 跳过 HTTP/2 伪头 (以 : 开头)\n if (key.charCodeAt(0) !== 58) {\n headers.append(key, value);\n }\n }\n return headers;\n}\n\n/**\n * 将 Node.js ReadableStream 转换为 Web 标准 ReadableStream\n * Node.js 和 Web 标准的 ReadableStream 在运行时兼容,但 TypeScript 类型不同\n */\nfunction toWebStream(\n nodeStream: NodeReadableStream<Uint8Array>,\n): ReadableStream<Uint8Array> {\n // Node.js ReadableStream 和 Web ReadableStream 在运行时是兼容的\n // 这里使用类型断言是安全的,因为 Node.js >= 18 的 stream/web 完全实现了 WHATWG Streams 标准\n return nodeStream as unknown as ReadableStream<Uint8Array>;\n}\n\n/** 代理 Request 内部接口 */\ninterface ProxyRequestInternal {\n [requestCache]?: Request;\n [incomingKey]: IncomingMessage;\n [urlKey]: string;\n [headersKey]?: Headers;\n [ipKey]?: string;\n [ipsKey]?: string[];\n _getRequest(): Request;\n}\n\n/**\n * 创建真实的 Request 对象\n */\nfunction createRealRequest(proxy: ProxyRequestInternal): Request {\n const incoming = proxy[incomingKey];\n const method = incoming.method || \"GET\";\n const init: RequestInit & { duplex?: string } = {\n method,\n headers: proxy[headersKey] || parseHeaders(incoming.rawHeaders),\n };\n\n // 只有非 GET/HEAD 请求才有 body\n if (method !== \"GET\" && method !== \"HEAD\") {\n // 使用 Node.js 原生流转换,避免收集 chunks\n const nodeWebStream = Readable.toWeb(\n incoming,\n ) as NodeReadableStream<Uint8Array>;\n init.body = toWebStream(nodeWebStream);\n init.duplex = \"half\";\n }\n\n return new Request(proxy[urlKey], init);\n}\n\n/**\n * Request 代理原型\n * 使用 Object.defineProperty 定义属性以支持 this 绑定\n */\nconst requestPrototype: object = {};\n\n// 定义 method 属性\nObject.defineProperty(requestPrototype, \"method\", {\n get() {\n const self = this as ProxyRequestInternal;\n return self[incomingKey].method || \"GET\";\n },\n enumerable: true,\n});\n\n// 定义 url 属性\nObject.defineProperty(requestPrototype, \"url\", {\n get() {\n const self = this as ProxyRequestInternal;\n return self[urlKey];\n },\n enumerable: true,\n});\n\n// 定义 headers 属性(延迟解析)\nObject.defineProperty(requestPrototype, \"headers\", {\n get() {\n const self = this as ProxyRequestInternal;\n if (!self[headersKey]) {\n self[headersKey] = parseHeaders(self[incomingKey].rawHeaders);\n }\n return self[headersKey];\n },\n enumerable: true,\n});\n\n// 定义 _getRequest 方法(获取或创建真实 Request)\nObject.defineProperty(requestPrototype, \"_getRequest\", {\n value: function () {\n const self = this as ProxyRequestInternal;\n if (!self[requestCache]) {\n self[requestCache] = createRealRequest(self);\n }\n return self[requestCache]!;\n },\n enumerable: false,\n});\n\n// 代理需要访问真实 Request 的属性\nconst proxyGetters = [\n \"body\",\n \"bodyUsed\",\n \"signal\",\n \"cache\",\n \"credentials\",\n \"destination\",\n \"integrity\",\n \"mode\",\n \"redirect\",\n \"referrer\",\n \"referrerPolicy\",\n \"keepalive\",\n];\n\nproxyGetters.forEach((key) => {\n Object.defineProperty(requestPrototype, key, {\n get() {\n const self = this as ProxyRequestInternal;\n return self._getRequest()[key as keyof Request];\n },\n enumerable: true,\n });\n});\n\n// 代理需要调用真实 Request 的方法\n// 注意:对于 body 相关方法(json/text/arrayBuffer/blob/formData),GET/HEAD 请求没有 body\n// 直接返回空值,避免用户误调用导致流读取异常\n// 这是框架级防御,参考业界标准:Fastify \"for GET and HEAD requests, the payload is never parsed\"\n\n// 定义 clone 方法(所有请求都可以克隆)\nObject.defineProperty(requestPrototype, \"clone\", {\n value: function () {\n const self = this as ProxyRequestInternal;\n const req = self._getRequest();\n return req.clone();\n },\n enumerable: true,\n});\n\n// 定义 body 相关方法(GET/HEAD 返回空值)\nconst bodyMethods = [\"arrayBuffer\", \"blob\", \"formData\", \"json\", \"text\"] as const;\n\nbodyMethods.forEach((key) => {\n Object.defineProperty(requestPrototype, key, {\n value: function () {\n const self = this as ProxyRequestInternal;\n const method = self[incomingKey].method || \"GET\";\n \n // GET/HEAD 请求没有 body,直接返回空值\n // 这样即使用户误调用 req.json() 也不会出错\n if (method === \"GET\" || method === \"HEAD\") {\n // json() 返回 null,text() 返回空字符串,其他返回对应的空值\n if (key === \"json\") return Promise.resolve(null);\n if (key === \"text\") return Promise.resolve(\"\");\n if (key === \"arrayBuffer\") return Promise.resolve(new ArrayBuffer(0));\n if (key === \"blob\") return Promise.resolve(new Blob([]));\n if (key === \"formData\") return Promise.resolve(new FormData());\n }\n \n // 其他请求正常处理\n const req = self._getRequest();\n return (req[key as keyof Request] as () => Promise<unknown>).call(req);\n },\n enumerable: true,\n });\n});\n\n// 定义 ip 属性(客户端真实 IP)\nObject.defineProperty(requestPrototype, \"ip\", {\n get() {\n const self = this as ProxyRequestInternal;\n return self[ipKey] || \"\";\n },\n enumerable: true,\n});\n\n// 定义 ips 属性(代理链中的所有 IP)\nObject.defineProperty(requestPrototype, \"ips\", {\n get() {\n const self = this as ProxyRequestInternal;\n return self[ipsKey] || [];\n },\n enumerable: true,\n});\n\n// 设置原型链\nObject.setPrototypeOf(requestPrototype, Request.prototype);\n\n/** 创建代理 Request 的选项 */\nexport interface CreateProxyRequestOptions {\n /** 信任代理配置 */\n trustProxy?: TrustProxyOption;\n}\n\n/**\n * 创建代理 Request\n * @param incoming Node.js IncomingMessage\n * @param defaultHost 默认主机名\n * @param options 可选配置\n */\nexport function createProxyRequest(\n incoming: IncomingMessage,\n defaultHost: string,\n options?: CreateProxyRequestOptions,\n): Request {\n const req = Object.create(requestPrototype) as ProxyRequestInternal;\n req[incomingKey] = incoming;\n\n // 构建 URL\n const host = incoming.headers.host || defaultHost;\n const protocol = (incoming.socket as { encrypted?: boolean }).encrypted\n ? \"https\"\n : \"http\";\n req[urlKey] = `${protocol}://${host}${incoming.url || \"/\"}`;\n\n // 解析客户端 IP(如果启用了 trustProxy)\n if (options?.trustProxy) {\n const { ip, ips } = parseClientIp(incoming, options.trustProxy);\n req[ipKey] = ip;\n req[ipsKey] = ips;\n } else {\n // 默认使用 socket IP\n const socketIp = (incoming.socket as Socket).remoteAddress || \"\";\n req[ipKey] = socketIp;\n req[ipsKey] = [socketIp];\n }\n\n return req as unknown as Request;\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as createProxyRequest } from "./request-
|
|
1
|
+
import { t as createProxyRequest } from "./request-D5oMj6sH.mjs";
|
|
2
2
|
import { t as writeResponse } from "./response-BlHLmKys.mjs";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
4
|
|
|
@@ -144,4 +144,4 @@ function createAdaptorServer(fetch, onError, timeout, bodyLimit, trustProxy) {
|
|
|
144
144
|
|
|
145
145
|
//#endregion
|
|
146
146
|
export { serve as n, createAdaptorServer as t };
|
|
147
|
-
//# sourceMappingURL=serve-
|
|
147
|
+
//# sourceMappingURL=serve-Cj0bYhav.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve-DVlDG92Y.mjs","names":["timeout"],"sources":["../src/node-server/serve.ts"],"sourcesContent":["/**\n * Node.js 服务器适配器\n * 提供类似 Bun.serve 的 API\n */\n\nimport {\n createServer,\n type Server as HttpServer,\n type IncomingMessage,\n type ServerResponse,\n} from \"node:http\";\nimport { createProxyRequest } from \"./request\";\nimport { writeResponse } from \"./response\";\n\n/** fetch 函数类型 */\nexport type FetchHandler = (request: Request) => Response | Promise<Response>;\n\n/** 优雅关闭配置 */\nexport interface GracefulShutdownOptions {\n /** 关闭超时时间(毫秒),默认 30000 */\n timeout?: number;\n /** 关闭前回调 */\n onShutdown?: () => void | Promise<void>;\n /** 关闭完成回调 */\n onShutdownComplete?: () => void;\n /** 监听的信号,默认 ['SIGINT', 'SIGTERM'] */\n signals?: NodeJS.Signals[];\n}\n\n/**\n * 请求超时配置\n *\n * 默认行为与 Fastify 和 Node.js 一致:\n * - requestTimeout: 0(无限制,需显式设置以防 DoS 攻击)\n * - headersTimeout: 使用 Node.js 默认值 60000ms\n * - keepAliveTimeout: 使用 Node.js 默认值 5000ms\n *\n * @see https://fastify.dev/docs/latest/Reference/Server/#requesttimeout\n */\nexport interface RequestTimeoutOptions {\n /**\n * 单个请求的最大处理时间(毫秒)\n * - 默认: 0(无限制)\n * - 建议: 如果没有反向代理,设置为 30000-120000 以防 DoS\n */\n requestTimeout?: number;\n /**\n * 接收完整请求头的超时时间(毫秒)\n * - 不设置则使用 Node.js 默认值(60000ms)\n */\n headersTimeout?: number;\n /**\n * Keep-Alive 连接空闲超时时间(毫秒)\n * - 不设置则使用 Node.js 默认值(5000ms)\n */\n keepAliveTimeout?: number;\n /**\n * 超时时返回的 JSON 响应\n * - 默认: { code: 504, message: \"Request timeout\" }\n */\n timeoutResponse?: { code: number; message: string };\n}\n\n/**\n * 信任代理配置\n * - true: 信任所有代理,从 X-Forwarded-For 等头获取真实 IP\n * - false: 不信任代理,使用 socket IP(默认)\n * - string: 信任特定 IP 或 CIDR(如 \"127.0.0.1\" 或 \"10.0.0.0/8\")\n * - string[]: 信任多个 IP 或 CIDR\n */\nexport type TrustProxyOption = boolean | string | string[];\n\n/** serve 配置选项 */\nexport interface ServeOptions {\n /** fetch 处理函数 */\n fetch: FetchHandler;\n /** 端口号,默认 3000 */\n port?: number;\n /** 主机名,默认 0.0.0.0 */\n hostname?: string;\n /** 错误处理函数 */\n onError?: (error: Error) => Response | Promise<Response>;\n /** 优雅关闭配置,设置为 true 使用默认配置 */\n gracefulShutdown?: boolean | GracefulShutdownOptions;\n /** 请求超时配置 */\n timeout?: RequestTimeoutOptions;\n /**\n * 请求体大小限制(字节)\n * - 默认: 1048576 (1MB)\n * - 设置为 0 表示不限制\n * - 超过限制返回 413 Payload Too Large\n */\n bodyLimit?: number;\n /**\n * 信任代理配置\n * - true: 信任所有代理,从 X-Forwarded-For 等头获取真实 IP\n * - false: 不信任代理,使用 socket IP(默认)\n * - string/string[]: 信任特定 IP 或 CIDR\n * \n * 启用后,request 对象会附加 ip 和 ips 属性\n */\n trustProxy?: TrustProxyOption;\n}\n\n/** serve 返回的服务器信息 */\nexport interface ServeResult {\n /** Node.js HTTP Server 实例 */\n server: HttpServer;\n /** 服务器端口 */\n port: number;\n /** 服务器主机名 */\n hostname: string;\n /** 关闭服务器 */\n stop: () => Promise<void>;\n /** 优雅关闭(等待现有请求完成) */\n shutdown: () => Promise<void>;\n}\n\n/** 默认请求体大小限制:1MB */\nconst DEFAULT_BODY_LIMIT = 1048576;\n\n/**\n * 创建请求处理函数\n */\nfunction createRequestHandler(\n fetch: FetchHandler,\n defaultHost: string,\n onError?: (error: Error) => Response | Promise<Response>,\n timeoutOptions?: RequestTimeoutOptions,\n bodyLimit?: number,\n trustProxy?: TrustProxyOption,\n) {\n const requestTimeout = timeoutOptions?.requestTimeout ?? 0;\n const timeoutResponse = timeoutOptions?.timeoutResponse ?? {\n code: 504,\n message: \"Request timeout\",\n };\n // bodyLimit: undefined 使用默认值,0 表示不限制\n const maxBodySize = bodyLimit === undefined ? DEFAULT_BODY_LIMIT : bodyLimit;\n\n return async (incoming: IncomingMessage, outgoing: ServerResponse) => {\n // 检查请求体大小限制\n if (maxBodySize > 0) {\n const contentLength = incoming.headers[\"content-length\"];\n if (contentLength && parseInt(contentLength, 10) > maxBodySize) {\n outgoing.statusCode = 413;\n outgoing.setHeader(\"Content-Type\", \"application/json\");\n outgoing.end(JSON.stringify({\n code: 413,\n message: \"Payload Too Large\",\n limit: maxBodySize,\n }));\n return;\n }\n }\n\n // 请求级别超时处理\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let isTimedOut = false;\n\n if (requestTimeout > 0) {\n timeoutId = setTimeout(() => {\n isTimedOut = true;\n if (!outgoing.headersSent) {\n outgoing.statusCode = 504;\n outgoing.setHeader(\"Content-Type\", \"application/json\");\n outgoing.end(JSON.stringify(timeoutResponse));\n }\n }, requestTimeout);\n }\n\n try {\n // 创建代理 Request(延迟创建真实 Request)\n const request = createProxyRequest(incoming, defaultHost, { trustProxy });\n\n // 调用 fetch handler\n const response = await fetch(request);\n\n // 清除超时定时器\n if (timeoutId) clearTimeout(timeoutId);\n\n // 如果已超时,不再写入响应\n if (isTimedOut) return;\n\n // 流式写入 Response\n await writeResponse(response, outgoing);\n } catch (error) {\n // 清除超时定时器\n if (timeoutId) clearTimeout(timeoutId);\n\n // 如果已超时,不再处理错误\n if (isTimedOut) return;\n\n // 错误处理\n const err = error instanceof Error ? error : new Error(String(error));\n\n if (onError) {\n try {\n const errorResponse = await onError(err);\n await writeResponse(errorResponse, outgoing);\n return;\n } catch {\n // onError 也失败了,返回 500\n }\n }\n\n // 默认错误响应\n if (!outgoing.headersSent) {\n outgoing.statusCode = 500;\n outgoing.setHeader(\"Content-Type\", \"text/plain\");\n outgoing.end(\"Internal Server Error\");\n }\n }\n };\n}\n\n/**\n * 启动 HTTP 服务器\n *\n * @example\n * ```ts\n * import { serve } from \"@vafast/node-server\";\n * import { Server } from \"vafast\";\n *\n * const app = new Server([\n * { method: \"GET\", path: \"/\", handler: () => \"Hello World\" },\n * ]);\n *\n * serve({ fetch: app.fetch, port: 3000 }, () => {\n * console.log(\"Server running on http://localhost:3000\");\n * });\n * ```\n */\nexport function serve(\n options: ServeOptions,\n callback?: () => void,\n): ServeResult {\n const { fetch, port = 3000, hostname = \"0.0.0.0\", onError, gracefulShutdown, timeout, bodyLimit, trustProxy } = options;\n\n const defaultHost = `${hostname === \"0.0.0.0\" ? \"localhost\" : hostname}:${port}`;\n const handler = createRequestHandler(fetch, defaultHost, onError, timeout, bodyLimit, trustProxy);\n\n const server = createServer(handler);\n\n // 设置服务器级别超时(Node.js 原生能力)\n if (timeout) {\n // headersTimeout: 接收完整请求头的超时时间\n if (timeout.headersTimeout !== undefined) {\n server.headersTimeout = timeout.headersTimeout;\n }\n // keepAliveTimeout: Keep-Alive 连接空闲超时时间\n if (timeout.keepAliveTimeout !== undefined) {\n server.keepAliveTimeout = timeout.keepAliveTimeout;\n }\n }\n\n // 追踪活跃连接\n const connections = new Set<import(\"node:net\").Socket>();\n\n server.on(\"connection\", (socket) => {\n connections.add(socket);\n socket.on(\"close\", () => connections.delete(socket));\n });\n\n // 优雅关闭函数\n let isShuttingDown = false;\n\n const shutdown = async (): Promise<void> => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n\n const shutdownOptions: GracefulShutdownOptions =\n typeof gracefulShutdown === \"object\" ? gracefulShutdown : {};\n\n const timeout = shutdownOptions.timeout ?? 30000;\n\n // 执行关闭前回调\n if (shutdownOptions.onShutdown) {\n await shutdownOptions.onShutdown();\n }\n\n return new Promise<void>((resolve) => {\n // 设置超时强制关闭\n const forceCloseTimer = setTimeout(() => {\n // 强制关闭所有连接\n for (const socket of connections) {\n socket.destroy();\n }\n connections.clear();\n resolve();\n }, timeout);\n\n // 停止接受新连接\n server.close(() => {\n clearTimeout(forceCloseTimer);\n shutdownOptions.onShutdownComplete?.();\n resolve();\n });\n\n // 关闭空闲连接\n for (const socket of connections) {\n // 如果连接空闲,立即关闭\n if (!socket.writableLength) {\n socket.end();\n }\n }\n });\n };\n\n // 注册信号处理\n if (gracefulShutdown) {\n const shutdownOptions: GracefulShutdownOptions =\n typeof gracefulShutdown === \"object\" ? gracefulShutdown : {};\n\n const signals = shutdownOptions.signals ?? [\"SIGINT\", \"SIGTERM\"];\n\n for (const signal of signals) {\n process.on(signal, () => {\n shutdown().then(() => process.exit(0));\n });\n }\n }\n\n // 启动服务器\n server.listen(port, hostname, callback);\n\n return {\n server,\n port,\n hostname,\n stop: () =>\n new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n }),\n shutdown,\n };\n}\n\n/**\n * 创建适配器服务器(不自动启动)\n * 用于需要更多控制的场景\n */\nexport function createAdaptorServer(\n fetch: FetchHandler,\n onError?: (error: Error) => Response | Promise<Response>,\n timeout?: RequestTimeoutOptions,\n bodyLimit?: number,\n trustProxy?: TrustProxyOption,\n): HttpServer {\n const handler = createRequestHandler(fetch, \"localhost\", onError, timeout, bodyLimit, trustProxy);\n return createServer(handler);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"serve-Cj0bYhav.mjs","names":["timeout"],"sources":["../src/node-server/serve.ts"],"sourcesContent":["/**\n * Node.js 服务器适配器\n * 提供类似 Bun.serve 的 API\n */\n\nimport {\n createServer,\n type Server as HttpServer,\n type IncomingMessage,\n type ServerResponse,\n} from \"node:http\";\nimport { createProxyRequest } from \"./request\";\nimport { writeResponse } from \"./response\";\n\n/** fetch 函数类型 */\nexport type FetchHandler = (request: Request) => Response | Promise<Response>;\n\n/** 优雅关闭配置 */\nexport interface GracefulShutdownOptions {\n /** 关闭超时时间(毫秒),默认 30000 */\n timeout?: number;\n /** 关闭前回调 */\n onShutdown?: () => void | Promise<void>;\n /** 关闭完成回调 */\n onShutdownComplete?: () => void;\n /** 监听的信号,默认 ['SIGINT', 'SIGTERM'] */\n signals?: NodeJS.Signals[];\n}\n\n/**\n * 请求超时配置\n *\n * 默认行为与 Fastify 和 Node.js 一致:\n * - requestTimeout: 0(无限制,需显式设置以防 DoS 攻击)\n * - headersTimeout: 使用 Node.js 默认值 60000ms\n * - keepAliveTimeout: 使用 Node.js 默认值 5000ms\n *\n * @see https://fastify.dev/docs/latest/Reference/Server/#requesttimeout\n */\nexport interface RequestTimeoutOptions {\n /**\n * 单个请求的最大处理时间(毫秒)\n * - 默认: 0(无限制)\n * - 建议: 如果没有反向代理,设置为 30000-120000 以防 DoS\n */\n requestTimeout?: number;\n /**\n * 接收完整请求头的超时时间(毫秒)\n * - 不设置则使用 Node.js 默认值(60000ms)\n */\n headersTimeout?: number;\n /**\n * Keep-Alive 连接空闲超时时间(毫秒)\n * - 不设置则使用 Node.js 默认值(5000ms)\n */\n keepAliveTimeout?: number;\n /**\n * 超时时返回的 JSON 响应\n * - 默认: { code: 504, message: \"Request timeout\" }\n */\n timeoutResponse?: { code: number; message: string };\n}\n\n/**\n * 信任代理配置\n * - true: 信任所有代理,从 X-Forwarded-For 等头获取真实 IP\n * - false: 不信任代理,使用 socket IP(默认)\n * - string: 信任特定 IP 或 CIDR(如 \"127.0.0.1\" 或 \"10.0.0.0/8\")\n * - string[]: 信任多个 IP 或 CIDR\n */\nexport type TrustProxyOption = boolean | string | string[];\n\n/** serve 配置选项 */\nexport interface ServeOptions {\n /** fetch 处理函数 */\n fetch: FetchHandler;\n /** 端口号,默认 3000 */\n port?: number;\n /** 主机名,默认 0.0.0.0 */\n hostname?: string;\n /** 错误处理函数 */\n onError?: (error: Error) => Response | Promise<Response>;\n /** 优雅关闭配置,设置为 true 使用默认配置 */\n gracefulShutdown?: boolean | GracefulShutdownOptions;\n /** 请求超时配置 */\n timeout?: RequestTimeoutOptions;\n /**\n * 请求体大小限制(字节)\n * - 默认: 1048576 (1MB)\n * - 设置为 0 表示不限制\n * - 超过限制返回 413 Payload Too Large\n */\n bodyLimit?: number;\n /**\n * 信任代理配置\n * - true: 信任所有代理,从 X-Forwarded-For 等头获取真实 IP\n * - false: 不信任代理,使用 socket IP(默认)\n * - string/string[]: 信任特定 IP 或 CIDR\n * \n * 启用后,request 对象会附加 ip 和 ips 属性\n */\n trustProxy?: TrustProxyOption;\n}\n\n/** serve 返回的服务器信息 */\nexport interface ServeResult {\n /** Node.js HTTP Server 实例 */\n server: HttpServer;\n /** 服务器端口 */\n port: number;\n /** 服务器主机名 */\n hostname: string;\n /** 关闭服务器 */\n stop: () => Promise<void>;\n /** 优雅关闭(等待现有请求完成) */\n shutdown: () => Promise<void>;\n}\n\n/** 默认请求体大小限制:1MB */\nconst DEFAULT_BODY_LIMIT = 1048576;\n\n/**\n * 创建请求处理函数\n */\nfunction createRequestHandler(\n fetch: FetchHandler,\n defaultHost: string,\n onError?: (error: Error) => Response | Promise<Response>,\n timeoutOptions?: RequestTimeoutOptions,\n bodyLimit?: number,\n trustProxy?: TrustProxyOption,\n) {\n const requestTimeout = timeoutOptions?.requestTimeout ?? 0;\n const timeoutResponse = timeoutOptions?.timeoutResponse ?? {\n code: 504,\n message: \"Request timeout\",\n };\n // bodyLimit: undefined 使用默认值,0 表示不限制\n const maxBodySize = bodyLimit === undefined ? DEFAULT_BODY_LIMIT : bodyLimit;\n\n return async (incoming: IncomingMessage, outgoing: ServerResponse) => {\n // 检查请求体大小限制\n if (maxBodySize > 0) {\n const contentLength = incoming.headers[\"content-length\"];\n if (contentLength && parseInt(contentLength, 10) > maxBodySize) {\n outgoing.statusCode = 413;\n outgoing.setHeader(\"Content-Type\", \"application/json\");\n outgoing.end(JSON.stringify({\n code: 413,\n message: \"Payload Too Large\",\n limit: maxBodySize,\n }));\n return;\n }\n }\n\n // 请求级别超时处理\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n let isTimedOut = false;\n\n if (requestTimeout > 0) {\n timeoutId = setTimeout(() => {\n isTimedOut = true;\n if (!outgoing.headersSent) {\n outgoing.statusCode = 504;\n outgoing.setHeader(\"Content-Type\", \"application/json\");\n outgoing.end(JSON.stringify(timeoutResponse));\n }\n }, requestTimeout);\n }\n\n try {\n // 创建代理 Request(延迟创建真实 Request)\n const request = createProxyRequest(incoming, defaultHost, { trustProxy });\n\n // 调用 fetch handler\n const response = await fetch(request);\n\n // 清除超时定时器\n if (timeoutId) clearTimeout(timeoutId);\n\n // 如果已超时,不再写入响应\n if (isTimedOut) return;\n\n // 流式写入 Response\n await writeResponse(response, outgoing);\n } catch (error) {\n // 清除超时定时器\n if (timeoutId) clearTimeout(timeoutId);\n\n // 如果已超时,不再处理错误\n if (isTimedOut) return;\n\n // 错误处理\n const err = error instanceof Error ? error : new Error(String(error));\n\n if (onError) {\n try {\n const errorResponse = await onError(err);\n await writeResponse(errorResponse, outgoing);\n return;\n } catch {\n // onError 也失败了,返回 500\n }\n }\n\n // 默认错误响应\n if (!outgoing.headersSent) {\n outgoing.statusCode = 500;\n outgoing.setHeader(\"Content-Type\", \"text/plain\");\n outgoing.end(\"Internal Server Error\");\n }\n }\n };\n}\n\n/**\n * 启动 HTTP 服务器\n *\n * @example\n * ```ts\n * import { serve } from \"@vafast/node-server\";\n * import { Server } from \"vafast\";\n *\n * const app = new Server([\n * { method: \"GET\", path: \"/\", handler: () => \"Hello World\" },\n * ]);\n *\n * serve({ fetch: app.fetch, port: 3000 }, () => {\n * console.log(\"Server running on http://localhost:3000\");\n * });\n * ```\n */\nexport function serve(\n options: ServeOptions,\n callback?: () => void,\n): ServeResult {\n const { fetch, port = 3000, hostname = \"0.0.0.0\", onError, gracefulShutdown, timeout, bodyLimit, trustProxy } = options;\n\n const defaultHost = `${hostname === \"0.0.0.0\" ? \"localhost\" : hostname}:${port}`;\n const handler = createRequestHandler(fetch, defaultHost, onError, timeout, bodyLimit, trustProxy);\n\n const server = createServer(handler);\n\n // 设置服务器级别超时(Node.js 原生能力)\n if (timeout) {\n // headersTimeout: 接收完整请求头的超时时间\n if (timeout.headersTimeout !== undefined) {\n server.headersTimeout = timeout.headersTimeout;\n }\n // keepAliveTimeout: Keep-Alive 连接空闲超时时间\n if (timeout.keepAliveTimeout !== undefined) {\n server.keepAliveTimeout = timeout.keepAliveTimeout;\n }\n }\n\n // 追踪活跃连接\n const connections = new Set<import(\"node:net\").Socket>();\n\n server.on(\"connection\", (socket) => {\n connections.add(socket);\n socket.on(\"close\", () => connections.delete(socket));\n });\n\n // 优雅关闭函数\n let isShuttingDown = false;\n\n const shutdown = async (): Promise<void> => {\n if (isShuttingDown) return;\n isShuttingDown = true;\n\n const shutdownOptions: GracefulShutdownOptions =\n typeof gracefulShutdown === \"object\" ? gracefulShutdown : {};\n\n const timeout = shutdownOptions.timeout ?? 30000;\n\n // 执行关闭前回调\n if (shutdownOptions.onShutdown) {\n await shutdownOptions.onShutdown();\n }\n\n return new Promise<void>((resolve) => {\n // 设置超时强制关闭\n const forceCloseTimer = setTimeout(() => {\n // 强制关闭所有连接\n for (const socket of connections) {\n socket.destroy();\n }\n connections.clear();\n resolve();\n }, timeout);\n\n // 停止接受新连接\n server.close(() => {\n clearTimeout(forceCloseTimer);\n shutdownOptions.onShutdownComplete?.();\n resolve();\n });\n\n // 关闭空闲连接\n for (const socket of connections) {\n // 如果连接空闲,立即关闭\n if (!socket.writableLength) {\n socket.end();\n }\n }\n });\n };\n\n // 注册信号处理\n if (gracefulShutdown) {\n const shutdownOptions: GracefulShutdownOptions =\n typeof gracefulShutdown === \"object\" ? gracefulShutdown : {};\n\n const signals = shutdownOptions.signals ?? [\"SIGINT\", \"SIGTERM\"];\n\n for (const signal of signals) {\n process.on(signal, () => {\n shutdown().then(() => process.exit(0));\n });\n }\n }\n\n // 启动服务器\n server.listen(port, hostname, callback);\n\n return {\n server,\n port,\n hostname,\n stop: () =>\n new Promise<void>((resolve, reject) => {\n server.close((err) => {\n if (err) reject(err);\n else resolve();\n });\n }),\n shutdown,\n };\n}\n\n/**\n * 创建适配器服务器(不自动启动)\n * 用于需要更多控制的场景\n */\nexport function createAdaptorServer(\n fetch: FetchHandler,\n onError?: (error: Error) => Response | Promise<Response>,\n timeout?: RequestTimeoutOptions,\n bodyLimit?: number,\n trustProxy?: TrustProxyOption,\n): HttpServer {\n const handler = createRequestHandler(fetch, \"localhost\", onError, timeout, bodyLimit, trustProxy);\n return createServer(handler);\n}\n"],"mappings":"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"}
|
package/dist/serve.mjs
CHANGED
package/dist/utils/handle.mjs
CHANGED
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../defineRoute-DKVyYELW.mjs";
|
|
2
2
|
import { t as DependencyManager } from "../dependency-manager-mqzLAocb.mjs";
|
|
3
|
-
import { a as parseBody, c as parseCookiesFast, d as parseHeaders, f as parseQuery, i as getHeader, p as parseQueryFast, r as getCookie, s as parseCookies } from "../parsers-
|
|
3
|
+
import { a as parseBody, c as parseCookiesFast, d as parseHeaders, f as parseQuery, i as getHeader, p as parseQueryFast, r as getCookie, s as parseCookies } from "../parsers-CEkO18PZ.mjs";
|
|
4
4
|
import { c as text, i as json, n as err, o as redirect, r as html, s as stream, t as empty } from "../response-lI0YZoia.mjs";
|
|
5
5
|
import { t as goAwait } from "../go-await-Dz1CRSTT.mjs";
|
|
6
6
|
import { n as base64urlEncode, t as base64urlDecode } from "../base64url-CAmasWF0.mjs";
|
package/dist/utils/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as parseCookies, d as parseQueryFast, l as parseHeaders, n as getHeader, o as parseCookiesFast, r as parseBody, t as getCookie, u as parseQuery } from "../parsers-
|
|
1
|
+
import { a as parseCookies, d as parseQueryFast, l as parseHeaders, n as getHeader, o as parseCookiesFast, r as parseBody, t as getCookie, u as parseQuery } from "../parsers-CsgGn0xr.mjs";
|
|
2
2
|
import { a as validateAllSchemas, c as validateSchemaOrThrow, i as precompileSchemas, n as createValidator, o as validateFast, r as getValidatorCacheStats, s as validateSchema } from "../validators-CkfvNBbK.mjs";
|
|
3
3
|
import { c as text, i as json, n as err, o as redirect, r as html, s as stream, t as empty } from "../response-Bs9GhJz-.mjs";
|
|
4
4
|
import { a as getRoute, i as getAllRoutes, n as createRouteRegistry, o as getRouteRegistry, r as filterRoutes, s as getRoutesByMethod, t as RouteRegistry } from "../route-registry-DsPslV2b.mjs";
|
package/dist/utils/parsers.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as parseBody, c as parseCookiesFast, d as parseHeaders, f as parseQuery, i as getHeader, l as parseFile, n as FormData, o as parseBodyAs, p as parseQueryFast, r as getCookie, s as parseCookies, t as FileInfo, u as parseFormData } from "../parsers-
|
|
1
|
+
import { a as parseBody, c as parseCookiesFast, d as parseHeaders, f as parseQuery, i as getHeader, l as parseFile, n as FormData, o as parseBodyAs, p as parseQueryFast, r as getCookie, s as parseCookies, t as FileInfo, u as parseFormData } from "../parsers-CEkO18PZ.mjs";
|
|
2
2
|
export { FileInfo, FormData, getCookie, getHeader, parseBody, parseBodyAs, parseCookies, parseCookiesFast, parseFile, parseFormData, parseHeaders, parseQuery, parseQueryFast };
|
package/dist/utils/parsers.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as parseCookies, c as parseFormData, d as parseQueryFast, i as parseBodyAs, l as parseHeaders, n as getHeader, o as parseCookiesFast, r as parseBody, s as parseFile, t as getCookie, u as parseQuery } from "../parsers-
|
|
1
|
+
import { a as parseCookies, c as parseFormData, d as parseQueryFast, i as parseBodyAs, l as parseHeaders, n as getHeader, o as parseCookiesFast, r as parseBody, s as parseFile, t as getCookie, u as parseQuery } from "../parsers-CsgGn0xr.mjs";
|
|
2
2
|
|
|
3
3
|
export { getCookie, getHeader, parseBody, parseBodyAs, parseCookies, parseCookiesFast, parseFile, parseFormData, parseHeaders, parseQuery, parseQueryFast };
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"parsers-BrG_mRLq.mjs","names":[],"sources":["../src/utils/parsers.ts"],"sourcesContent":["// src/parsers.ts\nimport qs from \"qs\";\nimport cookie from \"cookie\";\n\n// 文件信息接口\nexport interface FileInfo {\n name: string;\n type: string;\n size: number;\n data: ArrayBuffer;\n}\n\n// 表单数据接口\nexport interface FormData {\n fields: Record<string, string>;\n files: Record<string, FileInfo>;\n}\n\n/**\n * 简化的请求体解析函数\n * 优先简洁性,处理最常见的场景\n */\nexport async function parseBody(req: Request): Promise<unknown> {\n const contentType = req.headers.get(\"content-type\") || \"\";\n if (contentType.includes(\"application/json\")) {\n return await req.json();\n }\n if (contentType.includes(\"application/x-www-form-urlencoded\")) {\n const text = await req.text();\n return Object.fromEntries(new URLSearchParams(text));\n }\n return await req.text(); // fallback\n}\n\n/**\n * 解析 multipart/form-data 格式\n * 支持文件上传和普通表单字段\n */\nasync function parseMultipartFormData(req: Request): Promise<FormData> {\n const formData = await req.formData();\n const result: FormData = {\n fields: {},\n files: {},\n };\n\n for (const [key, value] of formData.entries()) {\n if (\n typeof value === \"object\" &&\n value !== null &&\n \"name\" in value &&\n \"type\" in value &&\n \"size\" in value\n ) {\n // 处理文件\n const file = value as any;\n const arrayBuffer = await file.arrayBuffer();\n result.files[key] = {\n name: file.name,\n type: file.type,\n size: file.size,\n data: arrayBuffer,\n };\n } else {\n // 处理普通字段\n result.fields[key] = value as string;\n }\n }\n\n return result;\n}\n\n/**\n * 解析请求体为特定类型\n * 提供类型安全的解析方法\n */\nexport async function parseBodyAs<T>(req: Request): Promise<T> {\n const body = await parseBody(req);\n return body as T;\n}\n\n/**\n * 解析请求体为表单数据\n * 专门用于处理 multipart/form-data\n */\nexport async function parseFormData(req: Request): Promise<FormData> {\n const contentType = req.headers.get(\"content-type\") || \"\";\n\n if (!contentType.includes(\"multipart/form-data\")) {\n throw new Error(\"请求不是 multipart/form-data 格式\");\n }\n\n return await parseMultipartFormData(req);\n}\n\n/**\n * 解析请求体为文件\n * 专门用于处理文件上传\n */\nexport async function parseFile(req: Request): Promise<FileInfo> {\n const contentType = req.headers.get(\"content-type\") || \"\";\n\n if (!contentType.includes(\"multipart/form-data\")) {\n throw new Error(\"请求不是 multipart/form-data 格式\");\n }\n\n const formData = await parseMultipartFormData(req);\n const fileKeys = Object.keys(formData.files);\n\n if (fileKeys.length === 0) {\n throw new Error(\"请求中没有文件\");\n }\n\n if (fileKeys.length > 1) {\n throw new Error(\"请求中包含多个文件,请使用 parseFormData\");\n }\n\n return formData.files[fileKeys[0]];\n}\n\n/**\n * 快速提取 query string(避免创建 URL 对象)\n */\nfunction extractQueryString(url: string): string {\n const qIndex = url.indexOf(\"?\");\n if (qIndex === -1) return \"\";\n\n const hashIndex = url.indexOf(\"#\", qIndex);\n return hashIndex === -1\n ? url.substring(qIndex + 1)\n : url.substring(qIndex + 1, hashIndex);\n}\n\n/** 获取查询字符串,直接返回对象 */\nexport function parseQuery(req: Request): Record<string, unknown> {\n const queryString = extractQueryString(req.url);\n if (!queryString) return {};\n return qs.parse(queryString);\n}\n\n/**\n * 快速解析简单查询字符串(不支持嵌套,但更快)\n * 适用于简单的 key=value&key2=value2 场景\n */\nexport function parseQueryFast(req: Request): Record<string, string> {\n const queryString = extractQueryString(req.url);\n if (!queryString) return {};\n\n const result: Record<string, string> = Object.create(null);\n const pairs = queryString.split(\"&\");\n\n for (const pair of pairs) {\n const eqIndex = pair.indexOf(\"=\");\n if (eqIndex === -1) {\n result[decodeURIComponent(pair)] = \"\";\n } else {\n const key = decodeURIComponent(pair.substring(0, eqIndex));\n const value = decodeURIComponent(pair.substring(eqIndex + 1));\n result[key] = value;\n }\n }\n\n return result;\n}\n\n/** 解析请求头,返回对象 */\nexport function parseHeaders(req: Request): Record<string, string> {\n const headers: Record<string, string> = Object.create(null);\n req.headers.forEach((value, key) => {\n headers[key] = value;\n });\n return headers;\n}\n\n/**\n * 获取单个请求头(避免解析全部)\n */\nexport function getHeader(req: Request, name: string): string | null {\n return req.headers.get(name);\n}\n\n/** 使用cookie库解析Cookie,保证可靠性 */\nexport function parseCookies(req: Request): Record<string, string> {\n const cookieHeader = req.headers.get(\"cookie\");\n if (!cookieHeader) return {};\n\n try {\n const parsed = cookie.parse(cookieHeader);\n // 过滤掉undefined和null值\n const result: Record<string, string> = {};\n for (const [key, value] of Object.entries(parsed)) {\n if (value !== undefined && value !== null) {\n result[key] = value;\n }\n }\n return result;\n } catch {\n return {};\n }\n}\n\n/**\n * 快速解析 Cookie(简化版,不使用外部库)\n * 适用于简单的 cookie 场景\n */\nexport function parseCookiesFast(req: Request): Record<string, string> {\n const cookieHeader = req.headers.get(\"cookie\");\n if (!cookieHeader) return {};\n\n const result: Record<string, string> = Object.create(null);\n const pairs = cookieHeader.split(\";\");\n\n for (const pair of pairs) {\n const trimmed = pair.trim();\n const eqIndex = trimmed.indexOf(\"=\");\n if (eqIndex > 0) {\n const key = trimmed.substring(0, eqIndex).trim();\n const value = trimmed.substring(eqIndex + 1).trim();\n // 移除引号\n result[key] =\n value.startsWith('\"') && value.endsWith('\"')\n ? value.slice(1, -1)\n : value;\n }\n }\n\n return result;\n}\n\n/**\n * 获取单个 Cookie 值(避免解析全部)\n */\nexport function getCookie(req: Request, name: string): string | null {\n const cookieHeader = req.headers.get(\"cookie\");\n if (!cookieHeader) return null;\n\n const prefix = `${name}=`;\n const pairs = cookieHeader.split(\";\");\n\n for (const pair of pairs) {\n const trimmed = pair.trim();\n if (trimmed.startsWith(prefix)) {\n const value = trimmed.substring(prefix.length).trim();\n return value.startsWith('\"') && value.endsWith('\"')\n ? value.slice(1, -1)\n : value;\n }\n }\n\n return null;\n}\n"],"mappings":"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"}
|