事情经过是这样的,最近在用 node.js 写个自用的 waf ,那么就需要一个表单解析库来分析请求中的参数,还要一个代理转发请求
之所以用 formidable/busboy
是因为看用法都是基于流去做,资源占用低,不需要自己在 request.on('data')
解析
选node-http-proxy是因为start数有7k多,看用法也简单
流量走向是这样的:nginx -> node.js -> 后端服务,测试了下大概有1~2ms的损耗,可以接受
于是开始完善细节,就碰到了标题中的问题了
测试代码如下
const http = require('http')
const formidable = require('formidable')
const httpProxy = require('http-proxy')
const proxy = httpProxy.createProxyServer({})
function request(rawRequest, rawResponse) {
let form = new formidable.IncomingForm()
form.parse(rawRequest, function (err, fields, files) {
console.log(fields) // 从表单中解析出的字段
console.log(files) // 从表单中解析出的文件
proxy.web(rawRequest, rawResponse, {
target: 'http://127.0.0.1:8333'
})
})
}
http.createServer().on('request', request).listen(8888, '127.0.0.1')
以上demo的位置是waf
const http = require('http')
const server = http.createServer()
let i = 0
async function request(rawRequest, rawResponse) {
console.log(i)
i++
console.log(rawRequest.method)
// get请求时
if (rawRequest.method === 'GET') {
rawResponse.writeHead(200)
rawResponse.write('ok')
rawResponse.end()
return
}
// 非get请求时
let body = []
rawRequest.on('data', (chunk) => {
console.log('on data')
body.push(chunk)
}).on('end', () => {
console.log('on end')
body = Buffer.concat(body).toString()
console.log('post data:', body)
rawResponse.writeHead(200)
rawResponse.write('ok')
rawResponse.end()
})
}
server.listen(8333, '127.0.0.1')
server.on('request', request)
以上demo的位置是后端server
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<form action="http://127.0.0.1:8888/" method="post">
<input type="text" name="test_name" value="test_value">
<input type="submit">
</form>
</body>
</html>
以上demo位置是表单html
直接打开 html
,提交数据,会发现 server.js
没有走 on data 和 on end ,猜测是 node-http-proxy
发送不完整
把 waf.js 的表单解析部分代码删除后(如下)
const http = require('http')
const formidable = require('formidable')
const httpProxy = require('http-proxy')
const proxy = httpProxy.createProxyServer({})
function request(rawRequest, rawResponse) {
// let form = new formidable.IncomingForm()
// form.parse(rawRequest, function (err, fields, files) {
// console.log(fields) // 从表单中解析出的字段
// console.log(files) // 从表单中解析出的文件
proxy.web(rawRequest, rawResponse, {
target: 'http://127.0.0.1:8333'
})
// })
}
http.createServer().on('request', request).listen(8888, '127.0.0.1')
server.js能正常输出post数据
由于对 node.js 没有太深的理解,调试进 formidable
的代码,发现了有 pause , resume 等函数,但是经过组合测试发现没有帮助
更换 busboy
来解析表单,也碰到了同样的问题
也曾参考了 https://imququ.com/post/web-proxy.html 这篇文章来实现反向代理替换 node-http-proxy
,结果后端还是无法走到 on data 和 on end
故前来求助,希望能解决 node-http-proxy
发送数据的问题
@htoooth 和连接里的问题是一样的,我还试过用lodash的深拷贝把request对象先拷贝出来,把拷贝出来的那份丢去表单解析库里,也不行 感谢你的连接,对我很有用,我还有一个备用的方案就是用request请求库去代替http-proxy,如果再不行就换备用方案了
stream已经被使用了,当然不行
阅读了多篇stream相关的文章,并试验了多天,问题得到解决了,解决方案是把请求的流数据复制了2份,demo如下
const http = require('http')
const server = http.createServer()
const Readable = require('stream').Readable
const formidable = require('formidable')
function requestHandle(rawRequest, rawResponse) {
if (rawRequest.method !== 'GET') {
let copyRequestForForm = new Readable() // 用来表单解析而复制的可读流
let copyRequestForRequest = new Readable() // 用来请求而复制的可读流
copyRequestForForm.headers = rawRequest.headers
copyRequestForForm._read = function () { }
copyRequestForRequest._read = function () { }
// 复制流
rawRequest.on('data', function (chunk) {
copyRequestForForm.push(chunk)
copyRequestForRequest.push(chunk)
})
rawRequest.on('end', function () {
copyRequestForForm.push(null)
copyRequestForRequest.push(null)
let form = new formidable.IncomingForm()
form.parse(copyRequestForForm, function (err, fields, files) {
// 表单解析完毕
console.log(fields)
console.log(files)
// 设置请求后端的选项
let options = {
hostname: '127.0.0.1',
port: 8333,
path: '/',
method: rawRequest.method,
headers: rawRequest.headers
}
// 向后端发起请求
let proxyRequest = http.request(options, function (proxyResponse) {
rawResponse.writeHead(proxyResponse.statusCode, proxyResponse.headers)
proxyResponse.pipe(rawResponse)
})
// 把复制来的流传给后端请求
copyRequestForRequest.pipe(proxyRequest)
})
})
}
}
server.listen(8888, '127.0.0.1')
server.on('request', requestHandle)