如何在Node.js中处理POST数据?

756

如何从HTTP POST 方法发送的表单数据 (form[method="post"]) 和文件上传中提取数据,在Node.js中实现?

我已经阅读了文档,进行了谷歌搜索,但没有发现相关信息。

function (request, response) {
    //request.post????
}

有没有库或技巧可用?


2
很难相信在这个页面中没有人提到URLSearchParams,它是解析接收到的主体的标准方式,也是Node.js自身的建议,因为querystring模块被认为是遗留的且未维护 - Andrea Giammarchi
1
那如何有助于文件上传?@AndreaGiammarchi - Ollie Williams
30个回答

765
你可以使用 querystring 模块:
var qs = require('querystring');

function (request, response) {
    if (request.method == 'POST') {
        var body = '';

        request.on('data', function (data) {
            body += data;

            // Too much POST data, kill the connection!
            // 1e6 === 1 * Math.pow(10, 6) === 1 * 1000000 ~~~ 1MB
            if (body.length > 1e6)
                request.connection.destroy();
        });

        request.on('end', function () {
            var post = qs.parse(body);
            // use post['blah'], etc.
        });
    }
}

比如说,如果你有一个名称为ageinput字段,你可以使用变量post来访问它:

console.log(post.age);

9
嗯,那是个好主意。不过加上那个应该不难,所以为了保持简单,我会在例子中省略它。 - Casey Chu
92
Node.js网页服务器开发饱受中间件问题的困扰,这些中间件需要你花费数小时的学习时间来节省几分钟的编码时间。更别提它们所提供的资料极其有限。而且,你的应用程序最终依赖于其他人的标准,而不是你自己的标准。此外还可能存在各种性能问题。 - Juan Lanus
5
var POST = qs.parse(body); // use POST 只有像我这样的新手才需要注意:当输入文本字段的名称为“user”时,POST.user将显示该字段的数据。例如:console.log(POST.user); - Michael Moeller
7
你也可以使用readable回调函数来代替构建在请求体字符串中的数据。一旦触发,请求体通过request.read()方法可用。 - Thomas Fankhauser
5
如何处理这种情况下的文件字段?即 <input id="fileupload" type="file" name="files[]" multiple>。在这里我们如何使用 qs.parse(body) 检索文件数据。当我尝试使用 qs 时,它返回的内容为 {"-----------------------------189644229932\r\nContent-Disposition: form-data;.... -189644229932--\r\n":""}。如何在纯Node.js中处理提交表单文件数据? - Justin John
显示剩余20条评论

666
如果你使用Node.js的高性能、高等级Web开发框架Express,你可以这样做:

HTML:

<form method="post" action="/">
    <input type="text" name="user[name]">
    <input type="text" name="user[email]">
    <input type="submit" value="Submit">
</form>

API客户端:

fetch('/', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({
        user: {
            name: "John",
            email: "john@example.com"
        }
    })
});

Node.js:(自Express v4.16.0以来)

// Parse URL-encoded bodies (as sent by HTML forms)
app.use(express.urlencoded());

// Parse JSON bodies (as sent by API clients)
app.use(express.json());

// Access the parse results as request.body
app.post('/', function(request, response){
    console.log(request.body.user.name);
    console.log(request.body.user.email);
});

Node.js:(适用于Express <4.16.0)

const bodyParser = require("body-parser");

/** bodyParser.urlencoded(options)
 * Parses the text as URL encoded data (which is how browsers tend to send form data from regular forms set to POST)
 * and exposes the resulting object (containing the keys and values) on req.body
 */
app.use(bodyParser.urlencoded({
    extended: true
}));

/**bodyParser.json(options)
 * Parses the text as JSON and exposes the resulting object on req.body.
 */
app.use(bodyParser.json());

app.post("/", function (req, res) {
    console.log(req.body.user.name)
});

50
实际上,如果您想使用较低级别的入口点,则功能位于connect中的BodyParser模块中。 - Julian Birch
16
我感到困惑。name="user[email]" 和 request.body.email 是如何对应的? - sbose
46
天啊!我得同时阅读关于同一个框架的3份文档,快要疯了:/ http://nodejs.org/api/http.html ,http://www.senchalabs.org/connect/ 和 http://expressjs.com/guide.html - salmatron
18
在我加入了 app.use(express.bodyParser()); 之前,这个方法对我没有起作用。 - pettys
35
Express对于Node就像jQuery对于客户端JS一样。每次我在谷歌搜索Node的帮助时,都会得到这些无用的“使用Express!”的回答。难道解析post数据真的很困难,以至于需要安装整个Web框架吗? - Shawn Whinnery
显示剩余20条评论

227
很多答案并不是好的实践或者没有解释,这就是为什么我要写这篇文章。

基础

当http.createServer的回调被调用时,服务器实际上已经接收到了请求的所有头信息,但是数据可能还没有接收到,所以我们需要等待它。http请求对象(一个http.IncomingMessage实例)实际上是一个可读流(readable)(stream)。在可读流中,每当有一块数据到达时,就会发出data 事件(假设您已注册回调),当所有块都到达时,会发出end事件。以下是如何监听这些事件的示例:

http.createServer((request, response) => {
  console.log('Now we have a http message with headers but no data yet.');
  request.on('data', chunk => {
    console.log('A chunk of data has arrived: ', chunk);
  });
  request.on('end', () => {
    console.log('No more data');
  })
}).listen(8080)

将缓冲区转换为字符串

如果你尝试这样做,你会注意到这些块是缓冲区。如果你不处理二进制数据,需要使用字符串来工作,我建议使用request.setEncoding方法,它会导致流发出用给定编码解释的字符串,并正确地处理多字节字符。

缓存块

现在你可能不对每个块都感兴趣,所以在这种情况下,你可能想要像这样进行缓冲:

http.createServer((request, response) => {
  const chunks = [];
  request.on('data', chunk => chunks.push(chunk));
  request.on('end', () => {
    const data = Buffer.concat(chunks);
    console.log('Data: ', data);
  })
}).listen(8080)

这里使用了Buffer.concat,它会简单地连接所有的缓冲区并返回一个大缓冲区。你也可以使用concat-stream模块来完成同样的操作:

const http = require('http');
const concat = require('concat-stream');
http.createServer((request, response) => {
  concat(request, data => {
    console.log('Data: ', data);
  });
}).listen(8080)

解析内容

如果您正在尝试接受没有文件的HTML表单POST提交或处理默认内容类型的jQuery ajax调用,则内容类型为application/x-www-form-urlencoded,编码为utf-8。 您可以使用querystring模块来反序列化它并访问属性:

const http = require('http');
const concat = require('concat-stream');
const qs = require('querystring');
http.createServer((request, response) => {
  concat(request, buffer => {
    const data = qs.parse(buffer.toString());
    console.log('Data: ', data);
  });
}).listen(8080)

如果你的内容类型是 JSON,那么你可以使用 JSON.parse 替代 qs.parse
如果你正在处理文件或多部分内容类型,则应该使用像 formidable 这样的工具来减轻处理的痛苦。请查看我的 this other answer,其中我发布了有关多部分内容的有用链接和模块。
管道传输:
如果你不想解析内容而是将其传递到其他地方,例如将其作为数据发送到另一个 HTTP 请求或将其保存到文件中,我建议 piping it 而不是缓冲它,因为它的代码更少,能更好地处理返回压力,占用更少的内存,在某些情况下更快。
所以,如果你想将内容保存到文件中:
 http.createServer((request, response) => {
   request.pipe(fs.createWriteStream('./request'));
 }).listen(8080)

限制数据量

正如其他答案所指出的,要记住恶意客户端可能会向您发送大量数据以使您的应用程序崩溃或填满内存,因此为了保护您的应用程序,请确保丢弃超过一定限制的请求。如果您不使用库来处理传入的数据。我建议使用类似stream-meter的东西,如果达到指定的限制,它可以中止请求:

limitedStream = request.pipe(meter(1e7));
limitedStream.on('data', ...);
limitedStream.on('end', ...);

或者

request.pipe(meter(1e7)).pipe(createWriteStream(...));

或者

concat(request.pipe(meter(1e7)), ...);

NPM模块

虽然我已经描述了如何使用HTTP请求体来缓冲和解析内容,但是我建议使用以下这些模块之一而不是自己实现,因为它们可能会更好地处理边缘情况。对于express,我建议使用body-parser。对于koa,有一个类似的模块

如果您不使用框架,则body非常好。


1
谢谢,我使用了你的代码,但是出现了神秘的重复消息。可能是变量request被重复使用,导致request.on('end')被多次调用了吗?我该如何避免这种情况? - Yan King Yin
1
我没有看到你的代码无法确定原因。请注意,对于每个请求,“request.on('end',...)”将被调用。 - Farid Nouri Neshat
1
与不使用“end”处理程序,即不缓冲块的GET请求相比,这如何影响性能? - JSON
6
这里是对问题的最佳答案。 - montrealist
6
应标记为已接受答案。问题是如何在nodejs中处理表单,而不是在expressjs中处理。 - webduvet
显示剩余6条评论

163

确保如果有人试图将你的 RAM 淹没,要终止连接!

var qs = require('querystring');

function (request, response) {
    if (request.method == 'POST') {
        var body = '';
        request.on('data', function (data) {
            body += data;
            // 1e6 === 1 * Math.pow(10, 6) === 1 * 1000000 ~~~ 1MB
            if (body.length > 1e6) { 
                // FLOOD ATTACK OR FAULTY CLIENT, NUKE REQUEST
                request.connection.destroy();
            }
        });
        request.on('end', function () {

            var POST = qs.parse(body);
            // use POST

        });
    }
}

59
你可以返回HTTP 413错误代码(请求实体过大)。 - neoascetic
2
@SSHThis:不,它是1*10^6=1000000。 - thejh
1
@tq:在这种情况下,POST[name] (例如POST["foo"])。 - thejh
3
var POST = qs.parse(body); // 仅供新手使用:当输入文本字段的名称为"user"时,使用Post.user将显示该字段的数据。例如:console.log(Post.user); - Michael Moeller
3
有人能帮忙吗?如果我发布 {'Name':'Joe'},经过 qs.Parse(POST) 后我得到的是 { {'Name':'Joe'} : '' }。请帮我翻译成通俗易懂的中文,但不要改变原意。 - Matt Canty
1
仅供参考,其中一些想法已经在一个npm模块中被编写成代码了:https://npmjs.org/package/parse-post - Xavi

108

这是一个非常简单的不依赖框架的包装器,它基于这里发布的其他答案和文章:

var http = require('http');
var querystring = require('querystring');

function processPost(request, response, callback) {
    var queryData = "";
    if(typeof callback !== 'function') return null;

    if(request.method == 'POST') {
        request.on('data', function(data) {
            queryData += data;
            if(queryData.length > 1e6) {
                queryData = "";
                response.writeHead(413, {'Content-Type': 'text/plain'}).end();
                request.connection.destroy();
            }
        });

        request.on('end', function() {
            request.post = querystring.parse(queryData);
            callback();
        });

    } else {
        response.writeHead(405, {'Content-Type': 'text/plain'});
        response.end();
    }
}

使用示例:

http.createServer(function(request, response) {
    if(request.method == 'POST') {
        processPost(request, response, function() {
            console.log(request.post);
            // Use request.post here

            response.writeHead(200, "OK", {'Content-Type': 'text/plain'});
            response.end();
        });
    } else {
        response.writeHead(200, "OK", {'Content-Type': 'text/plain'});
        response.end();
    }

}).listen(8000);

1
这个检查是否应该移动到单独的中间件中,以便可以在所有post / put请求中检查过大的请求。 - Pavel Nikolov
1
@PavelNikolov 这主要是用于快速且简单的任务,否则最好像这里接受的答案建议的那样使用Express(它可能也能处理大请求)。但是请随意修改和“fork”它以适合您的需求。 - Mahn
那么 .read() 方法呢?http 模块不支持它吗?例如:response.read() - B T
嘿,只是好奇 - 为什么你把有效载荷放在响应对象(response.post)中而不是请求对象中? - Jotham
@Jotham 很好的问题...我不知道为什么我之前没有注意到这个,但是没有理由应该是response.post而不是更合乎逻辑的request.post。我已经更新了帖子。 - Mahn
我没有收到“data”事件,只有“readable”事件,并且在尝试请求读取时什么也没有。使用与上面类似的表单。 - Elisabeth

97

如果您将数据编码为JSON,然后发送到Node.js,它将更加清洁。

function (req, res) {
    if (req.method == 'POST') {
        var jsonString = '';

        req.on('data', function (data) {
            jsonString += data;
        });

        req.on('end', function () {
            console.log(JSON.parse(jsonString));
        });
    }
}

2
这是对我有效的解决方案。结果发现其他解决方案返回了一个看起来像JSON但没有被解析的字符串。与其使用qs.parse(),不如使用JSON.parse()将主体转换为可用的内容。例如:var post = JSON.parse(body);,然后通过post.fieldname访问数据。(故事的寓意是,如果你对所看到的内容感到困惑,请不要忘记使用typeof!) - wmassingham
16
请注意,您必须使用try-catch语句来处理JSON.parse函数,因为如果我想使您的应用程序崩溃,我只需发送一个具有原始文本的正文。 - ecarrizo
你应该使用 request.setEncoding 来使其正常工作,否则它可能无法正确处理非 ASCII 字符。 - Farid Nouri Neshat
那真的很有帮助。 - Saeedeh

40

对于任何想知道如何在不安装Web框架的情况下完成此琐事的人,我设法将其组合在一起。它几乎没有生产就绪,但似乎可以工作。

function handler(req, res) {
    var POST = {};
    if (req.method == 'POST') {
        req.on('data', function(data) {
            data = data.toString();
            data = data.split('&');
            for (var i = 0; i < data.length; i++) {
                var _data = data[i].split("=");
                POST[_data[0]] = _data[1];
            }
            console.log(POST);
        })
    }
}

终于找到一个完整可行的解决方案来解决这个奇怪的问题......之前的答案也帮了很大的忙,让我明白了为什么回调开始时请求中没有任何数据......非常感谢! - luis-br
4
  1. 这个答案假设数据是一个字符串。在一般情况下这是错误的假设。
  2. 这个答案假设数据只到达一次。否则,按 '=' 分割将会得到不可预测的结果。在一般情况下这是错误的假设。
- Konstantin
@Konstantin 实际上,这个答案假设数据是一个缓冲区。看看这个。 https://dev59.com/n2Uq5IYBdhLWcg3wXvWa还有这个。 https://millermedeiros.github.io/mdoc/examples/node_api/doc/streams.html#Event - Shawn Whinnery

21

您可以使用Node.js的请求体解析中间件body-parser

首先加载body-parser

$ npm install body-parser --save

一些示例代码

var express = require('express')
var bodyParser = require('body-parser')

var app = express()

app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())


app.use(function (req, res) {
  var post_data = req.body;
  console.log(post_data);
})

更多文档可以在这里找到。


1
我刚刚复制/粘贴了这个解决方案,但req.body是空的。也许旧版express可以工作,但现在不行了... - Philip Enc

12

10

如果您希望使用纯Node.js,则可以按照下面所示的方法提取POST数据:

// Dependencies
const StringDecoder = require('string_decoder').StringDecoder;
const http = require('http');

// Instantiate the HTTP server.
const httpServer = http.createServer((request, response) => {
  // Get the payload, if any.
  const decoder = new StringDecoder('utf-8');
  let payload = '';

  request.on('data', (data) => {
    payload += decoder.write(data);
  });

  request.on('end', () => {
    payload += decoder.end();

    // Parse payload to object.
    payload = JSON.parse(payload);

    // Do smoething with the payload....
  });
};

// Start the HTTP server.
const port = 3000;
httpServer.listen(port, () => {
  console.log(`The server is listening on port ${port}`);
});


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接