【问题标题】:Request stalls when proxying request unless I use http-proxy-middleware代理请求时请求停止,除非我使用 http-proxy-middleware
【发布时间】:2022-01-30 08:31:26
【问题描述】:

我有一个提供者如下:

@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,它需要查询我计划注入的其他一些提供程序。所以我想“留在里面”通过编写我自己的提供程序来构建 NestJS 的“生态系统”。

我不知道为什么我的请求停滞不前。

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

【问题讨论】:

    标签: typescript nestjs http-proxy


    【解决方案1】:

    原来问题在于 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 https://*.com/questions/70875743
                console.log("Parsing request body for path: " + req.path);
                this.jsonBodyParser(req, res, next);
            }
        }
    }
    
    

    【讨论】: