代理请求时请求停止,除非我使用 http-proxy-middleware

Request stalls when proxying request unless I use http-proxy-middleware

我有如下供应商:

@Injectable()
export class GameServerProxyService {

    private httpProxy: httpProxy;
    
    constructor(@Inject(GameServerDetailsService) private gameServiceDetailsService: GameServerDetailsService) {
        this.httpProxy = httpProxy.createProxyServer({});

        this.httpProxy.on("proxyReq", (err,req,res)=>{
            console.log("proxyReq");
        });

        this.httpProxy.on("proxyRes", (err,req,res)=>{
            console.log("proxyRes");
        });
        
        this.httpProxy.on("error", (err,req,res)=>{
            console.error(err);
        });
    }

    proxyRequest(request: Request, response: Response){

        console.log("proxyRequest");

        this.httpProxy.web(request, response, {
            target: "http://localhost:3100/",
            changeOrigin: true,
        }, (error) => {
            console.log(error);
        });
    }
}

以及一个控制器,该控制器的路由使用此提供程序来代理调用:

@Controller()
export class SsrApiGatewayController {

    constructor(
        @Inject(GameServerProxyService) private gameServerProxyService: GameServerProxyService
    ) { }
    
    @All("game-server/*")
    async proxyGameServerRequest(@Req() request: Request, @Res() response: Response) {
        console.log("Proxying Request..!")
        this.gameServerProxyService.proxyRequest(request, response);
    }
}

当我向这条路线发送请求时,我的请求停止了。我看到以下日志:

Proxy Request..!
proxyReq

所以代理请求永远不会到达响应阶段。我使用 Insomnia 发送请求。如果我取消停滞的请求,我在代理的服务器上会收到错误 (http://localhost:3100/):

[Nest] 6220  - 01/27/2022, 7:41:35 PM   ERROR [ExceptionsHandler] request aborted
BadRequestError: request aborted
    at IncomingMessage.onAborted (C:\******\node_modules\raw-body\index.js:231:10)
    at IncomingMessage.emit (events.js:314:20)
    at IncomingMessage.EventEmitter.emit (domain.js:483:12)
    at abortIncoming (_http_server.js:533:9)
    at socketOnClose (_http_server.js:526:3)
    at Socket.emit (events.js:326:22)
    at Socket.EventEmitter.emit (domain.js:483:12)
    at TCP.<anonymous> (net.js:675:12)

很明显,我的请求被代理转发到另一台服务器,但我没有收到响应。

如果我改用 http-proxy-middleware,效果很好。我这样注册中间件:


async function bootstrap() {
    const app = await NestFactory.create(SsrApiGatewayModule);

    app.useGlobalPipes(
        new ValidationPipe({
            transform: true,
            whitelist: true,
        }),
    );

    app.use("/game-server/*", 
       createProxyMiddleware({
           target: "http://localhost:3100",
           changeOrigin: true,
       })
    );

    var configService = app.get(ConfigService);
    var commonConfig = configService.get<CommonConfig>(CommonKey);
    await app.listen(commonConfig.port);
}

代理请求处理正常。

我想使用我自己的提供者,因为我将有一个复杂的代理路由器 target,它需要查询我计划注入的其他一些提供者。所以我想 'stay inside' 的 NestJS 'ecosystem' 通过编写我自己的提供商。

我不确定我的请求为什么会停滞。

我还尝试实现一个 NestJS 中间件(因此我可以保留将提供程序注入我的中间件的能力)并将 http-proxy-middleware 本身包装在我的中间件中,而不是我自己使用 http-proxy。这以同样的方式失败。

原来问题是 BodyParser 中间件正在解析“消耗”底层数据流的请求主体。然后当我的代理代码运行时,它会尝试用它代理 fall 和请求主体,但由于数据流已被消耗而无法这样做。代理服务器无限期地等待请求数据,但它永远不会到达。

我的解决方案是编写自己的中间件来包装正文解析器和代理中间件。我根据请求 url 决定使用哪个 - 如果 URL 以 /game-server/ 开头或以 /game-server 结尾,则使用代理,否则使用正文解析器。

为了完整起见,这里是代码:

引导:

const app = await NestFactory.create(SsrApiGatewayModule, {
    //We will manually invoke body-parser in the API Gateway Proxy Middleware. 
    bodyParser: false,
});

根模块

@Module({
    //...
})
export class SsrApiGatewayModule implements NestModule {
    configure(consumer: MiddlewareConsumer) {
        consumer
            .apply(ApiGatewayProxyMiddleware)
            .forRoutes({ path: `*`, method: RequestMethod.ALL });
    }
}

中间件:

@Injectable()
export class ApiGatewayProxyMiddleware implements NestMiddleware {

    private jsonBodyParser: RequestHandler;
    private httpProxyMiddleware: RequestHandler;
    private startWith: string;
    private endWidth: string;

    //You can inject stuff here.
    constructor() {
        this.jsonBodyParser = bodyParser.json();

        //You can inject the config module to configure target, etc
        this.httpProxyMiddleware = createProxyMiddleware({
            target: "http://localhost:3000",
            changeOrigin: true,
            pathRewrite: {'^/game-server' : ''},
        })

        this.startWith = `/game-server/`;
        this.endWidth = `/game-server`;
    }

    use(req: Request, res: Response, next: NextFunction) {

        let pathMatch = req.path.startsWith(this.startWith);
        if(!pathMatch) pathMatch = req.path.endsWith(this.endWidth);

        if(pathMatch) {
            console.log("Proxying request for path: " + req.path)
            this.httpProxyMiddleware(req,res,next);
        }
        else {
            //Only run body parser if we plan to handle the request here instead of proxying it 
            console.log("Parsing request body for path: " + req.path);
            this.jsonBodyParser(req, res, next);
        }
    }
}