Node.js代理转发x-www-form-urlencoded POST请求时req.body为空问题
问题描述
我用bash命令 wget -q -T 25 --post-data "aa=bb&cc=dd" --no-check-certificate "$url" 将第三方API的数据从本地机器发给自己,但ISP屏蔽了第三方地址,所以搭建了一个Node.js代理服务器,接收数据后转发到第三方服务器。现在的问题是转发后req.body是空对象,搞不懂原因。
以下是简化后的代理API和模拟第三方API的Node.js代码:
const http = require('http') const compression = require('compression') const express = require('express') const httpServerPort = process.env.SERVER_PORT /// 设置你的端口 const app = express() app.use(express.urlencoded({ extended: true, limit: '50mb' })) app.use(compression()) const httpServer = http.createServer(app) httpServer.listen(httpServerPort, () => { console.log(`HTTP server started on port ${httpServerPort}`) }) app.post('/api/v1/agent_proxy', async (req, res) => { try { const postData = querystring.stringify(req.body) // console.log(postData) const options = { hostname: 'localhost', port: httpServerPort, path: '/api/v1/simu_api', method: 'POST', Headers: { "Content-Type": "application/x-www-form-urlencoded", "Content-Length": Buffer.byteLength(postData) } } const innerReq = http.request(options, (innerRes) => { innerRes.on('data', (d) => { console.log(`statusCode: ${res.statusCode}`) res.sendStatus(200) }); }); innerReq.on('error', (error) => { console.error(error) res.sendStatus(500) }); innerReq.write(postData) innerReq.end() } catch (err) { console.error(err) res.sendStatus(501) } }) app.post('/api/v1/simu_api', async (req, res) => { try { ////////// 这里能看到问题:req.body == {},但它应该是{aa: 'bb', cc: 'dd'} ///////// console.log(req.body) } catch (err) { console.error(err) res.sendStatus(501) } res.sendStatus(200) })
问题原因及修复方案
1. 缺少querystring模块引入
代码中使用了querystring.stringify()但未引入该模块,会导致运行报错,无法生成有效的postData。需要在代码顶部添加:
const querystring = require('querystring')
2. 请求头字段名错误
options中的Headers应为小写的headers——Node.js的http.request要求该字段名必须小写,大写的话不会被识别,导致转发请求缺少正确的Content-Type头,express.urlencoded中间件无法解析请求体,最终req.body为空。
3. 代理请求响应处理逻辑缺陷
当前代码仅监听innerRes的data事件,如果模拟API没有返回数据(仅返回状态码),回调不会触发,代理接口会一直处于挂起状态。需要改为监听end事件确保响应处理完成。
修复后的完整代码
const http = require('http') const compression = require('compression') const express = require('express') const querystring = require('querystring') // 新增模块引入 const httpServerPort = process.env.SERVER_PORT || 3000 // 增加默认端口,避免未设置环境变量时出错 const app = express() app.use(express.urlencoded({ extended: true, limit: '50mb' })) app.use(compression()) const httpServer = http.createServer(app) httpServer.listen(httpServerPort, () => { console.log(`HTTP server started on port ${httpServerPort}`) }) app.post('/api/v1/agent_proxy', async (req, res) => { try { const postData = querystring.stringify(req.body) console.log(postData) // 可打开日志确认数据是否正确生成 const options = { hostname: 'localhost', port: httpServerPort, path: '/api/v1/simu_api', method: 'POST', headers: { // 改为小写的headers "Content-Type": "application/x-www-form-urlencoded", "Content-Length": Buffer.byteLength(postData) } } const innerReq = http.request(options, (innerRes) => { // 监听end事件确保响应处理完成 innerRes.on('end', () => { console.log(`statusCode: ${innerRes.statusCode}`) // 这里应使用innerRes的状态码 res.sendStatus(200) }); // 处理响应数据,避免内存泄漏 innerRes.on('data', (d) => { // 可根据需求处理返回的数据 }); }); innerReq.on('error', (error) => { console.error(error) res.sendStatus(500) }); innerReq.write(postData) innerReq.end() } catch (err) { console.error(err) res.sendStatus(501) } }) app.post('/api/v1/simu_api', async (req, res) => { try { console.log(req.body) // 现在应能正确输出{ aa: 'bb', cc: 'dd' } } catch (err) { console.error(err) res.sendStatus(501) } res.sendStatus(200) })
验证步骤
- 安装依赖:
npm install express compression - 设置端口环境变量或使用默认的3000端口
- 启动服务器:
node 你的文件名.js - 执行bash命令:
wget -q -T 25 --post-data "aa=bb&cc=dd" --no-check-certificate "http://localhost:3000/api/v1/agent_proxy" - 查看服务器日志,
simu_api接口中的req.body应能正确输出预期对象。
内容的提问来源于stack exchange,提问作者Qua285
相关产品推荐
相关产品推荐

