@lark-apaas/nestjs-http-forwarder 0.1.1-alpha.0

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.
@@ -0,0 +1,192 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { Request, Response } from 'express';
3
+
4
+ /**
5
+ * service.forward() 入参。
6
+ */
7
+ interface ForwardRequestDto {
8
+ /** HTTP method,由 fetch 自身校验合法性 */
9
+ method: string;
10
+ /** Target URL on the customer internal network. Must be http:// or https:// */
11
+ targetUrl: string;
12
+ /** Headers to forward (host will be overridden to targetUrl.host) */
13
+ headers?: Record<string, string>;
14
+ /** Body to forward */
15
+ body?: string | null;
16
+ }
17
+ /**
18
+ * service.forward() 出参(透传上游 status / headers / body)。
19
+ */
20
+ interface ForwardResponseDto {
21
+ status: number;
22
+ headers: Record<string, string>;
23
+ body: string;
24
+ }
25
+ /**
26
+ * HttpForwarderModule.forRoot() 选项
27
+ */
28
+ interface HttpForwarderModuleOptions {
29
+ /** 单次请求超时(ms),默认 30000 */
30
+ requestTimeoutMs?: number;
31
+ /** 上游响应体最大字节数,默认 10MB;超阈值返回 502 RESPONSE_TOO_LARGE */
32
+ maxResponseBytes?: number;
33
+ }
34
+
35
+ /**
36
+ * HttpForwarderModule
37
+ *
38
+ * 自动注册:
39
+ * - `HttpForwarderController` —— 通用内网转发 endpoint `ALL /anycross/forward?targetUrl=...`
40
+ * 支持所有 HTTP method,headers / body 自动透传,上游响应直接投射到客户端。
41
+ * - `HttpForwarderService`(可注入到自定义 controller,编排特殊场景)
42
+ *
43
+ * **不内置鉴权**——消费方在 AppModule 层面给路由加 guard(如全局 NeedLoginGuard)。
44
+ *
45
+ * 使用示例:
46
+ * ```ts
47
+ * @Module({
48
+ * imports: [
49
+ * HttpForwarderModule.forRoot({
50
+ * requestTimeoutMs: 30_000,
51
+ * maxResponseBytes: 10 * 1024 * 1024,
52
+ * }),
53
+ * ],
54
+ * })
55
+ * export class AppModule {}
56
+ * ```
57
+ *
58
+ * 前端调用:
59
+ * ```ts
60
+ * axiosForBackend.get('/anycross/forward', { params: { targetUrl: 'http://api.corp.com/v1/users' } });
61
+ * axiosForBackend.post('/anycross/forward', body, { params: { targetUrl: 'http://api.corp.com/v1/orders' } });
62
+ * ```
63
+ *
64
+ * **注**:实际代理 / 鉴权 / 隧道由部署环境处理(如沙箱内的 mihomo 透明代理 +
65
+ * iptables 拦截)。SDK 不感知 anycross / proxy_group_id / jwtToken。
66
+ */
67
+ declare class HttpForwarderModule {
68
+ static forRoot(options?: HttpForwarderModuleOptions): DynamicModule;
69
+ }
70
+
71
+ /**
72
+ * forRoot 入参经过默认值合并后的形态。SDK 内部统一使用此类型。
73
+ */
74
+ interface HttpForwarderOptionsResolved extends Required<HttpForwarderModuleOptions> {
75
+ }
76
+ /**
77
+ * 校验并补默认值。配置错误(非法字段值)在模块装配时抛出,启动期 fail-fast。
78
+ */
79
+ declare function resolveOptions(options: HttpForwarderModuleOptions | undefined): HttpForwarderOptionsResolved;
80
+
81
+ /**
82
+ * 反向 HTTP 转发器:把入站请求"原样"转发到目标 URL。
83
+ *
84
+ * 用途:把请求的出口从浏览器变成 node。客户内网访问的代理 / 鉴权 / 隧道由
85
+ * 部署环境(如沙箱内的 mihomo 透明代理 + iptables 拦截)负责,SDK 不感知。
86
+ *
87
+ * 只做 5 件核心事:
88
+ * 1. 协议白名单(仅 http/https,防 SSRF)
89
+ * 2. host 重写 + content-length 剥离(语义正确性,单点处理)
90
+ * 3. 单次请求超时(AbortController,默认 30s)
91
+ * 4. 响应体大小上限(流式累计字节,防 OOM,默认 10MB)
92
+ * 5. 错误码映射(给调用方稳定的 error contract)
93
+ */
94
+ declare class HttpForwarderService {
95
+ private readonly options;
96
+ constructor(options: HttpForwarderOptionsResolved);
97
+ forward(payload: ForwardRequestDto): Promise<ForwardResponseDto>;
98
+ /**
99
+ * URL 解析 + 协议白名单。其他入参校验交给 fetch 自身。
100
+ */
101
+ private assertValidTargetUrl;
102
+ /**
103
+ * host / content-length 的**单点处理点**:
104
+ * - host:强制覆盖为 targetUrl.host(防上游误识别,无论调用方传什么)
105
+ * - content-length:剥离(fetch 会按真实 body 自算)
106
+ *
107
+ * 其他 hop-by-hop / accept-encoding 等由 controller 已剥离,service 信任入参。
108
+ */
109
+ private buildOutgoingHeaders;
110
+ /**
111
+ * 流式读取响应体并累计字节数,超 `maxResponseBytes` 即 abort + 抛 502。
112
+ */
113
+ private readBoundedBody;
114
+ /**
115
+ * 把 fetch 抛错 / 超时统一映射为 HttpException。
116
+ *
117
+ * 1. HttpException → 直接透传(含 INVALID_TARGET_PROTOCOL / RESPONSE_TOO_LARGE)
118
+ * 2. controller.signal.aborted → UPSTREAM_TIMEOUT 504
119
+ * 3. TypeError → INVALID_REQUEST 400(fetch 对配置错统一抛 TypeError)
120
+ * 4. 兜底 → UPSTREAM_UNREACHABLE 502
121
+ */
122
+ private mapNetworkError;
123
+ }
124
+
125
+ /**
126
+ * 通用内网转发 controller。
127
+ *
128
+ * 路径:`/anycross/forward`(硬编码,所有消费方一致)。
129
+ *
130
+ * 接受所有 HTTP method(GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS)。
131
+ * - method:直接取自入站请求自身
132
+ * - targetUrl:取自 query `?targetUrl=...`
133
+ * - headers:透传入站请求 headers(剔除 hop-by-hop / accept-encoding;
134
+ * host / content-length 由 service 单点处理)
135
+ * - body:透传入站请求 body(已经过 express body-parser,按情况序列化为字符串)
136
+ *
137
+ * 响应:上游 status / headers / body **直接投射到 HTTP response**,让前端 axios
138
+ * 看到的就是真实上游响应(而非 wrap 的 JSON)。
139
+ *
140
+ * **SDK 不内置鉴权**——消费方在 AppModule 层面给本路由加 guard(如全局 NeedLoginGuard)。
141
+ */
142
+ declare class HttpForwarderController {
143
+ private readonly svc;
144
+ constructor(svc: HttpForwarderService);
145
+ forward(req: Request, res: Response): Promise<void>;
146
+ /**
147
+ * 从入站请求 headers 选出可透传给上游的字段。剔除 hop-by-hop / accept-encoding。
148
+ * 保留 cookie / authorization / x-* 等业务头。
149
+ * host / content-length 不在此处剥离——由 service.buildOutgoingHeaders 单点处理。
150
+ */
151
+ static pickIncomingHeaders(incoming: Request['headers']): Record<string, string>;
152
+ /**
153
+ * 把 express 已解析的 body 序列化回字符串供 service 透传。
154
+ */
155
+ static serializeIncomingBody(req: Request): string | null;
156
+ }
157
+
158
+ /** HttpForwarder module options injection token */
159
+ declare const HTTP_FORWARDER_MODULE_OPTIONS: unique symbol;
160
+ /** Controller route prefix (硬编码,不可配置以保持调用方式统一) */
161
+ declare const CONTROLLER_ROUTE = "anycross/forward";
162
+ /** Default request timeout in milliseconds */
163
+ declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
164
+ /** Default maximum response body size in bytes (10 MB) */
165
+ declare const DEFAULT_MAX_RESPONSE_BYTES: number;
166
+ /** Allowed target URL protocols */
167
+ declare const ALLOWED_PROTOCOLS: readonly ["http:", "https:"];
168
+ /**
169
+ * 入站 headers 中不能透传给上游的 hop-by-hop / transport / encoding 字段。
170
+ * RFC 7230 §6.1 hop-by-hop + accept-encoding(由 fetch / undici 接管)。
171
+ *
172
+ * 注:host / content-length 由 service.buildOutgoingHeaders 在写入 fetch 入参时
173
+ * 单点处理(host 重写为 targetUrl.host;content-length 让 fetch 自算),此处不重复。
174
+ */
175
+ declare const HEADERS_NOT_FORWARDED_TO_UPSTREAM: Set<string>;
176
+ /**
177
+ * 上游响应 headers 中不应回写到下游 response 的字段。
178
+ * - content-encoding:fetch 已解压,原 encoding 不再适用
179
+ * - content-length:node http 会根据真实 body 重算
180
+ * - transfer-encoding / connection / keep-alive:hop-by-hop
181
+ */
182
+ declare const HEADERS_NOT_RETURNED_TO_CLIENT: Set<string>;
183
+ /** Error codes returned to callers */
184
+ declare const ERROR_CODES: {
185
+ readonly INVALID_REQUEST: "INVALID_REQUEST";
186
+ readonly INVALID_TARGET_PROTOCOL: "INVALID_TARGET_PROTOCOL";
187
+ readonly UPSTREAM_UNREACHABLE: "UPSTREAM_UNREACHABLE";
188
+ readonly UPSTREAM_TIMEOUT: "UPSTREAM_TIMEOUT";
189
+ readonly RESPONSE_TOO_LARGE: "RESPONSE_TOO_LARGE";
190
+ };
191
+
192
+ export { ALLOWED_PROTOCOLS, CONTROLLER_ROUTE, DEFAULT_MAX_RESPONSE_BYTES, DEFAULT_REQUEST_TIMEOUT_MS, ERROR_CODES, type ForwardRequestDto, type ForwardResponseDto, HEADERS_NOT_FORWARDED_TO_UPSTREAM, HEADERS_NOT_RETURNED_TO_CLIENT, HTTP_FORWARDER_MODULE_OPTIONS, HttpForwarderController, HttpForwarderModule, type HttpForwarderModuleOptions, type HttpForwarderOptionsResolved, HttpForwarderService, resolveOptions };
@@ -0,0 +1,192 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { Request, Response } from 'express';
3
+
4
+ /**
5
+ * service.forward() 入参。
6
+ */
7
+ interface ForwardRequestDto {
8
+ /** HTTP method,由 fetch 自身校验合法性 */
9
+ method: string;
10
+ /** Target URL on the customer internal network. Must be http:// or https:// */
11
+ targetUrl: string;
12
+ /** Headers to forward (host will be overridden to targetUrl.host) */
13
+ headers?: Record<string, string>;
14
+ /** Body to forward */
15
+ body?: string | null;
16
+ }
17
+ /**
18
+ * service.forward() 出参(透传上游 status / headers / body)。
19
+ */
20
+ interface ForwardResponseDto {
21
+ status: number;
22
+ headers: Record<string, string>;
23
+ body: string;
24
+ }
25
+ /**
26
+ * HttpForwarderModule.forRoot() 选项
27
+ */
28
+ interface HttpForwarderModuleOptions {
29
+ /** 单次请求超时(ms),默认 30000 */
30
+ requestTimeoutMs?: number;
31
+ /** 上游响应体最大字节数,默认 10MB;超阈值返回 502 RESPONSE_TOO_LARGE */
32
+ maxResponseBytes?: number;
33
+ }
34
+
35
+ /**
36
+ * HttpForwarderModule
37
+ *
38
+ * 自动注册:
39
+ * - `HttpForwarderController` —— 通用内网转发 endpoint `ALL /anycross/forward?targetUrl=...`
40
+ * 支持所有 HTTP method,headers / body 自动透传,上游响应直接投射到客户端。
41
+ * - `HttpForwarderService`(可注入到自定义 controller,编排特殊场景)
42
+ *
43
+ * **不内置鉴权**——消费方在 AppModule 层面给路由加 guard(如全局 NeedLoginGuard)。
44
+ *
45
+ * 使用示例:
46
+ * ```ts
47
+ * @Module({
48
+ * imports: [
49
+ * HttpForwarderModule.forRoot({
50
+ * requestTimeoutMs: 30_000,
51
+ * maxResponseBytes: 10 * 1024 * 1024,
52
+ * }),
53
+ * ],
54
+ * })
55
+ * export class AppModule {}
56
+ * ```
57
+ *
58
+ * 前端调用:
59
+ * ```ts
60
+ * axiosForBackend.get('/anycross/forward', { params: { targetUrl: 'http://api.corp.com/v1/users' } });
61
+ * axiosForBackend.post('/anycross/forward', body, { params: { targetUrl: 'http://api.corp.com/v1/orders' } });
62
+ * ```
63
+ *
64
+ * **注**:实际代理 / 鉴权 / 隧道由部署环境处理(如沙箱内的 mihomo 透明代理 +
65
+ * iptables 拦截)。SDK 不感知 anycross / proxy_group_id / jwtToken。
66
+ */
67
+ declare class HttpForwarderModule {
68
+ static forRoot(options?: HttpForwarderModuleOptions): DynamicModule;
69
+ }
70
+
71
+ /**
72
+ * forRoot 入参经过默认值合并后的形态。SDK 内部统一使用此类型。
73
+ */
74
+ interface HttpForwarderOptionsResolved extends Required<HttpForwarderModuleOptions> {
75
+ }
76
+ /**
77
+ * 校验并补默认值。配置错误(非法字段值)在模块装配时抛出,启动期 fail-fast。
78
+ */
79
+ declare function resolveOptions(options: HttpForwarderModuleOptions | undefined): HttpForwarderOptionsResolved;
80
+
81
+ /**
82
+ * 反向 HTTP 转发器:把入站请求"原样"转发到目标 URL。
83
+ *
84
+ * 用途:把请求的出口从浏览器变成 node。客户内网访问的代理 / 鉴权 / 隧道由
85
+ * 部署环境(如沙箱内的 mihomo 透明代理 + iptables 拦截)负责,SDK 不感知。
86
+ *
87
+ * 只做 5 件核心事:
88
+ * 1. 协议白名单(仅 http/https,防 SSRF)
89
+ * 2. host 重写 + content-length 剥离(语义正确性,单点处理)
90
+ * 3. 单次请求超时(AbortController,默认 30s)
91
+ * 4. 响应体大小上限(流式累计字节,防 OOM,默认 10MB)
92
+ * 5. 错误码映射(给调用方稳定的 error contract)
93
+ */
94
+ declare class HttpForwarderService {
95
+ private readonly options;
96
+ constructor(options: HttpForwarderOptionsResolved);
97
+ forward(payload: ForwardRequestDto): Promise<ForwardResponseDto>;
98
+ /**
99
+ * URL 解析 + 协议白名单。其他入参校验交给 fetch 自身。
100
+ */
101
+ private assertValidTargetUrl;
102
+ /**
103
+ * host / content-length 的**单点处理点**:
104
+ * - host:强制覆盖为 targetUrl.host(防上游误识别,无论调用方传什么)
105
+ * - content-length:剥离(fetch 会按真实 body 自算)
106
+ *
107
+ * 其他 hop-by-hop / accept-encoding 等由 controller 已剥离,service 信任入参。
108
+ */
109
+ private buildOutgoingHeaders;
110
+ /**
111
+ * 流式读取响应体并累计字节数,超 `maxResponseBytes` 即 abort + 抛 502。
112
+ */
113
+ private readBoundedBody;
114
+ /**
115
+ * 把 fetch 抛错 / 超时统一映射为 HttpException。
116
+ *
117
+ * 1. HttpException → 直接透传(含 INVALID_TARGET_PROTOCOL / RESPONSE_TOO_LARGE)
118
+ * 2. controller.signal.aborted → UPSTREAM_TIMEOUT 504
119
+ * 3. TypeError → INVALID_REQUEST 400(fetch 对配置错统一抛 TypeError)
120
+ * 4. 兜底 → UPSTREAM_UNREACHABLE 502
121
+ */
122
+ private mapNetworkError;
123
+ }
124
+
125
+ /**
126
+ * 通用内网转发 controller。
127
+ *
128
+ * 路径:`/anycross/forward`(硬编码,所有消费方一致)。
129
+ *
130
+ * 接受所有 HTTP method(GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS)。
131
+ * - method:直接取自入站请求自身
132
+ * - targetUrl:取自 query `?targetUrl=...`
133
+ * - headers:透传入站请求 headers(剔除 hop-by-hop / accept-encoding;
134
+ * host / content-length 由 service 单点处理)
135
+ * - body:透传入站请求 body(已经过 express body-parser,按情况序列化为字符串)
136
+ *
137
+ * 响应:上游 status / headers / body **直接投射到 HTTP response**,让前端 axios
138
+ * 看到的就是真实上游响应(而非 wrap 的 JSON)。
139
+ *
140
+ * **SDK 不内置鉴权**——消费方在 AppModule 层面给本路由加 guard(如全局 NeedLoginGuard)。
141
+ */
142
+ declare class HttpForwarderController {
143
+ private readonly svc;
144
+ constructor(svc: HttpForwarderService);
145
+ forward(req: Request, res: Response): Promise<void>;
146
+ /**
147
+ * 从入站请求 headers 选出可透传给上游的字段。剔除 hop-by-hop / accept-encoding。
148
+ * 保留 cookie / authorization / x-* 等业务头。
149
+ * host / content-length 不在此处剥离——由 service.buildOutgoingHeaders 单点处理。
150
+ */
151
+ static pickIncomingHeaders(incoming: Request['headers']): Record<string, string>;
152
+ /**
153
+ * 把 express 已解析的 body 序列化回字符串供 service 透传。
154
+ */
155
+ static serializeIncomingBody(req: Request): string | null;
156
+ }
157
+
158
+ /** HttpForwarder module options injection token */
159
+ declare const HTTP_FORWARDER_MODULE_OPTIONS: unique symbol;
160
+ /** Controller route prefix (硬编码,不可配置以保持调用方式统一) */
161
+ declare const CONTROLLER_ROUTE = "anycross/forward";
162
+ /** Default request timeout in milliseconds */
163
+ declare const DEFAULT_REQUEST_TIMEOUT_MS = 30000;
164
+ /** Default maximum response body size in bytes (10 MB) */
165
+ declare const DEFAULT_MAX_RESPONSE_BYTES: number;
166
+ /** Allowed target URL protocols */
167
+ declare const ALLOWED_PROTOCOLS: readonly ["http:", "https:"];
168
+ /**
169
+ * 入站 headers 中不能透传给上游的 hop-by-hop / transport / encoding 字段。
170
+ * RFC 7230 §6.1 hop-by-hop + accept-encoding(由 fetch / undici 接管)。
171
+ *
172
+ * 注:host / content-length 由 service.buildOutgoingHeaders 在写入 fetch 入参时
173
+ * 单点处理(host 重写为 targetUrl.host;content-length 让 fetch 自算),此处不重复。
174
+ */
175
+ declare const HEADERS_NOT_FORWARDED_TO_UPSTREAM: Set<string>;
176
+ /**
177
+ * 上游响应 headers 中不应回写到下游 response 的字段。
178
+ * - content-encoding:fetch 已解压,原 encoding 不再适用
179
+ * - content-length:node http 会根据真实 body 重算
180
+ * - transfer-encoding / connection / keep-alive:hop-by-hop
181
+ */
182
+ declare const HEADERS_NOT_RETURNED_TO_CLIENT: Set<string>;
183
+ /** Error codes returned to callers */
184
+ declare const ERROR_CODES: {
185
+ readonly INVALID_REQUEST: "INVALID_REQUEST";
186
+ readonly INVALID_TARGET_PROTOCOL: "INVALID_TARGET_PROTOCOL";
187
+ readonly UPSTREAM_UNREACHABLE: "UPSTREAM_UNREACHABLE";
188
+ readonly UPSTREAM_TIMEOUT: "UPSTREAM_TIMEOUT";
189
+ readonly RESPONSE_TOO_LARGE: "RESPONSE_TOO_LARGE";
190
+ };
191
+
192
+ export { ALLOWED_PROTOCOLS, CONTROLLER_ROUTE, DEFAULT_MAX_RESPONSE_BYTES, DEFAULT_REQUEST_TIMEOUT_MS, ERROR_CODES, type ForwardRequestDto, type ForwardResponseDto, HEADERS_NOT_FORWARDED_TO_UPSTREAM, HEADERS_NOT_RETURNED_TO_CLIENT, HTTP_FORWARDER_MODULE_OPTIONS, HttpForwarderController, HttpForwarderModule, type HttpForwarderModuleOptions, type HttpForwarderOptionsResolved, HttpForwarderService, resolveOptions };