@lark-apaas/nestjs-http-forwarder
v0.1.3
Published
FullStack Nestjs server-side HTTP request forwarder (node egress, mihomo-aware)
Readme
@lark-apaas/nestjs-http-forwarder
NestJS 模块:通用内网转发能力。自动注册 controller,把请求的出口从浏览器变成 node。
⚠️ SDK 不感知代理 / 鉴权 / 隧道。实际客户内网访问的代理 / JWT / 隧道路由由部署环境(如沙箱内的 mihomo 透明代理 + iptables 拦截)处理。
⚠️ SDK 不内置鉴权 guard。消费方在 AppModule 层面给路由加自己的 guard(如全局 NeedLoginGuard)。
自动注册的接口
ALL /api/__platform__/http-forward?targetUrl=<内网 URL>- method:取自请求自身(GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS)
- targetUrl:query 参数
- headers:自动透传(剔除 host / content-length / hop-by-hop)
- body:自动透传
- 响应:上游 status / headers / body 直接投射到 HTTP response
路径硬编码 /api/__platform__/http-forward,不可配置(保证所有消费方调用方式一致)。
使用
1. 注册模块
import { Module } from '@nestjs/common';
import { HttpForwarderModule } from '@lark-apaas/nestjs-http-forwarder';
@Module({
imports: [
HttpForwarderModule.forRoot({
// 全部可选
requestTimeoutMs: 30_000,
maxResponseBytes: 10 * 1024 * 1024,
proxyAuth: { commandTimeoutMs: 5_000 }, // 调优代理鉴权刷新
}),
],
})
export class AppModule {}2. 前端调用(搭配 axiosForBackend)
import { axiosForBackend } from '@lark-apaas/client-toolkit';
// GET 内网接口
const res = await axiosForBackend.get('/api/__platform__/http-forward', {
params: { targetUrl: 'http://api.corp.com/v1/users' },
});
// POST 内网接口
await axiosForBackend.post('/api/__platform__/http-forward', { qty: 3 }, {
params: { targetUrl: 'http://api.corp.com/v1/orders' },
});
// 其他 method 同理
await axiosForBackend.delete('/api/__platform__/http-forward', {
params: { targetUrl: 'http://api.corp.com/v1/orders/42' },
});前端拿到的就是真实上游响应:response.status、response.headers、response.data 跟直连内网一样。
3. 自定义编排(高级用法)
如果默认 controller 行为不够灵活,可以直接注入 service 写自己的 controller:
import { Body, Controller, Post } from '@nestjs/common';
import {
HttpForwarderService,
type ForwardRequestDto,
} from '@lark-apaas/nestjs-http-forwarder';
@Controller('my/custom')
export class MyController {
constructor(private readonly forwarder: HttpForwarderService) {}
@Post()
async forward(@Body() body: ForwardRequestDto) {
return this.forwarder.forward(body);
}
}ForwardRequestDto / ForwardResponseDto
import type { Readable } from 'node:stream';
import type { IncomingHttpHeaders } from 'undici/types/header';
interface ForwardRequestDto {
method: string; // 由 undici 自身校验
targetUrl: string; // 必须 http:// 或 https://
headers?: Record<string, string>;
body?: string | null;
}
interface ForwardResponseDto {
status: number;
headers: IncomingHttpHeaders; // Record<string, string | string[] | undefined>
// 多值字段(set-cookie / link / via)保留数组
body: Readable; // 未消费的可读流;调用方 pipe / for-await 消费
}0.2.0 BREAKING:
headers形态从Record<string, string>改为IncomingHttpHeaders,body从string改为Readable。详情见docs/plans/2026-05-09-http-forwarder.mdPhase 3。
配置参考
| 字段 | 必填 | 默认 | 说明 |
|------|------|------|------|
| requestTimeoutMs | — | 30000 | 单次请求超时(AbortController) |
| maxResponseBytes | — | 10485760 (10 MB) | 上游响应体最大字节数;超阈值返回 502 RESPONSE_TOO_LARGE |
| proxyAuth | — | undefined | v0.3.0 起,出网代理鉴权机制始终启用(按 appId 拉 jwtToken + updateProxyCmd,exec 后定时刷新)。SDK 不感知具体代理实现。本字段仅用于覆盖调优默认值,不传则全用默认。详见 docs/api/nestjs-http-forwarder.md |
service 做的 6 件核心事
| # | 能力 | 说明 |
|---|------|------|
| 1 | 协议白名单 | targetUrl 仅允许 http:// / https://(防 SSRF) |
| 2 | host / content-length 接管 | host 重写为 targetUrl.host;content-length 让 undici 自算 |
| 3 | 超时控制 | AbortController + setTimeout 默认 30s;undici 自带 Headers/Body/Connect Timeout 也兜底 |
| 4 | 响应体大小上限 | 第一层 content-length 预检超阈值直接 502;第二层 Transform 流中累计字节,超阈值 destroy 流 |
| 5 | 错误码映射 | undici 抛错按 signal.aborted / 错误类(HeadersTimeout / InvalidArgument)/ err.code 等结构化信号映射 |
| 6 | 出网代理 | EnvHttpProxyAgent 读 HTTP_PROXY / HTTPS_PROXY / NO_PROXY env 注入 dispatcher |
method 白名单 / body 类型校验 / payload 非空校验等都没做——交给 undici 自身(undici 对配置错抛 InvalidArgumentError,由 SDK 映射为 400 INVALID_REQUEST)。
错误码
| HTTP | code | 含义 |
|------|------|------|
| 400 | INVALID_REQUEST | targetUrl 缺失/非法 URL / undici 抛 InvalidArgumentError(method/headers 配置错) |
| 400 | INVALID_TARGET_PROTOCOL | targetUrl 协议非 http/https |
| 502 | UPSTREAM_UNREACHABLE | 上游不可达 / 网络错(err.code ∈ ENOTFOUND / ECONNREFUSED / ETIMEDOUT / UND_ERR_*) |
| 502 | RESPONSE_TOO_LARGE | 上游响应体超 maxResponseBytes。预检命中时整次请求 502;流中累计兜底时 status 已发出,连接被切断(client 看到截断) |
| 504 | UPSTREAM_TIMEOUT | 单次请求超时(controller.signal.aborted 或 undici HeadersTimeoutError / BodyTimeoutError / ConnectTimeoutError) |
上游 4xx / 5xx 不抛错,原样投射到 HTTP response。
限制(v0.3.0)
- 请求体(
ForwardRequestDto.body)仍仅支持string/null(binary / FormData / 流上传待后续版本) - 响应体流式 pipe 到下游 response,不做 buffer,不支持改写 / 重试
- 默认 controller 路径固定
/api/__platform__/http-forward,路径自定义请走自定义 controller proxyAuth始终启用,每个进程严格只服务一个 appId(沙箱-per-app);不同 appId 的请求会被拒绝。消费方必须提供@lark-apaas/nestjs-common(RequestContextService+PLATFORM_HTTP_CLIENT)
