【已解决】碰到了奇怪的问题,当 post 请求经过 formidable/busboy 解析表单后, node-http-proxy 便发送不了post请求的body数据了?
发布于 2 个月前 作者 shuimugan 618 次浏览 来自 问答

事情经过是这样的,最近在用 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 发送数据的问题

4 回复

@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)
回到顶部