Multer is a widely used middleware to handle file uploads and multipart/form-data
stuff received by your Node.js server application. There are tons of documentation and tutorials to teach you how to use Multer to upload files to your application. It’s not that simple to figure out how to upload files and form data at the same time though!
Multer是一种广泛使用的中间件,用于处理Node.js服务器应用程序接收的文件上传和multipart/form-data
内容。 有大量的文档和教程可以教您如何使用Multer将文件上传到您的应用程序。 不过,弄清楚如何同时上传文件和表单数据并不是那么简单!
The problem
问题
As you probably know, form data can be submitted with various encodings, form-data
or multipart/form-data
being the oldest and most widely used. You can also use x-www-form-uuencoded
, raw
or json
but neither of these can easily transfer a file.
您可能知道,表单数据可以使用各种编码提交,其中form-data
或multipart/form-data
是最古老且使用最广泛的。 您还可以使用x-www-form-uuencoded
, raw
或json
但是这两种方法都无法轻松传输文件。
But Express and its body-parser
extension can’t parse multipart/form-data
. ThePOST
request will invoke the appropriate route, but req.body
will be undefined
. Only Multer can save us. (Well, not really, but this article is about Multer.)
但是Express及其body-parser
扩展无法解析multipart/form-data
。 POST
请求将调用适当的路由,但是req.body
将是undefined
。 只有穆特能拯救我们。 (嗯,不是真的,但是这篇文章是关于穆特的。)
The solution
解决方案
Let’s say we’re trying to send some traditional form data and a file. It has to be multipart/form-data
. For the sake of this simple demo let’s send only one form field and a test file from Postman. Let it be just one form field called whatever
with the string value of 'nothing'
and a file called file
.
假设我们正在尝试发送一些传统的表单数据和文件。 它必须是multipart/form-data
。 为了进行这个简单的演示,我们仅发送来自Postman的一个表单字段和一个测试文件。 让这只是一个所谓的表单字段whatever
用的字符串值'nothing'
和一个名为file
。
What is happening on server side? It’s all in App.ts
for me.
服务器端发生了什么? 全部在App.ts
为了我。
import * as express from 'express';
import * as bodyParser from 'body-parser';
import * as multer from 'multer';
That’s your usual import block. Nothing serious going on here: we import Express and some middleware. Then we inject them in the constructor
of App.js
:
那是您通常的导入块。 这里没什么大不了的:我们导入Express和一些中间件。 然后,将它们注入App.js
的constructor
中:
public express;constructor(
private upload = new multer({ dest: 'c:\\upload' }),
) {
this.start();
}
The upload path is entirely arbitrary here, but you got the point.
此处的上传路径完全是任意的,但您已经明白了。
Now we have a Multer instance, let’s configure our Express instance.
现在我们有了Multer实例,让我们配置Express实例。
private start() { this.express = express(); // Allow body parsing
this.express.use(bodyParser.json());
this.express.use(bodyParser.urlencoded({ extended: true })); // Allow form-data parsing
this.express.use(this.upload.any());
}
That’s pretty much it. Now the app can receive and parse json
, urlencoded
and alsoform-data
. Let’s set up the router after the configuration lines to see how it works:
就是这样。 现在,该应用程序可以接收和解析json
, urlencoded
以及form-data
。 让我们在配置行之后设置路由器以查看其工作原理:
const router = express.Router();router.all('/', (req, res) => { res.redirect('/upload'); });router.post('/upload', (req, res) => {
console.log(req.body);
res.status(200).send('Hello there!');
});this.express.use('/', router);
Now when you click Send in Postman, you’ll get Hello there!
as a HTTP response. At the same time you’ll see this on your console:
现在,当您单击“在邮递员中发送”时,您将在Hello there!
得到Hello there!
作为HTTP响应。 同时,您会在控制台上看到以下内容:
[Object: null prototype] { whatever: 'nothing' }
And if you look into your upload directory, which was c:\upload
here (yes, I’m working on Windows) you’ll see your uploaded file under a temporary name. Read req.file
for details.
而且,如果您查看上传目录(这里为c:\upload
(是的,我在Windows上工作),您将看到一个临时名称的上传文件。 有关详细信息,请阅读req.file
。
What is happening here?
这是怎么回事
Multer has multiple methods of operation. Most tutorials are about receiving files, and they all suggest you to do this (remember, this.upload
was the name of our injected Multer instance):
Multer有多种操作方法。 大多数教程都是关于接收文件的,它们都建议您执行此操作(请记住, this.upload
是我们注入的Multer实例的名称):
router.post('/upload',
this.upload.single('file'),
(req, res) => {
console.log(req.body);
res.status(200).send('Hello there!');
});
In other words, their suggestion is to use Multer only in the route where a file upload is expected, and use the .single()
method to configure it.
换句话说,他们的建议是仅在希望上传文件的路径中使用Multer,并使用.single()
方法进行配置。
Which is inherently wrong. If you do this, your route will only receive the file (and only if the field name is file
in the above example) and never the form fields. You’ll keep getting console errors if you try to send anything else. With this solution you’ll have to submit all form data and file separately, with different encodings.
这本质上是错误的。 如果这样做,您的路线将仅接收文件(并且仅在上例中字段名称为file
的情况下),而不会收到表单字段。 如果尝试发送其他任何内容,您将不断收到控制台错误。 使用此解决方案,您将必须使用不同的编码分别提交所有表单数据和文件。
But if you use the .any()
method, Multer will accept everything, including single or multiple file uploads. Actually there’s a whole bunch of these methods, and the difference is the number of files they handle.
但是,如果您使用.any()
方法,则Multer将接受所有内容,包括单个或多个文件上传。 实际上,这些方法有很多,不同之处在于它们处理的文件数。
multer.none()
no files (0)multer.none()
无文件(0)multer.single()
one file (1)multer.single()
一个文件(1)multer.array()
at least one file (1+)multer.array()
至少一个文件(1+)multer.any()
any number of files (0+)multer.any()
任意数量的文件(0+)multer.fields()
at least one file (1+)multer.fields()
至少一个文件(1+)
The difference between array()
and fields()
is how the file list will be processed for the req.files
array.
array()
和fields()
之间的区别在于将如何处理req.files
数组的文件列表。
With array()
your req.files
will be a simple array with all the files you uploaded.
使用array()
您的req.files
将是包含所有上载文件的简单数组。
With fields()
you’ll get a string-indexed array with the individual filenames as keys.
使用fields()
您将获得一个字符串索引的数组,并以各个文件名作为键。
Neither array()
nor fields()
can process text fields, so I’d suggest staying with all()
unless you’re really only interested in uploaded files.
array()
和fields()
都不能处理文本字段,所以我建议不要使用all()
除非您真的只对上传的文件感兴趣。
Wait a minute! Isn’t this insecure?
等一下! 这不安全吗?
Oh look, you spotted the elephant in the room! Indeed, using multer.any()
for every route is a bit risky because it allows all of your POST
routes to receive file uploads. Even if no other harm is done, these files will sit in the temporary folder and consume server space, and it’s also easy to DDoS your server with multiple concurrent uploads.
哦,看,你发现房间里的大象了! 的确,对每个路由使用multer.any()
有点冒险,因为它允许所有POST
路由接收文件上传。 即使没有其他危害,这些文件也将位于临时文件夹中并占用服务器空间,并且通过多个并发上传来对服务器进行DDoS也很容易。
So the actual solution is not to use a global Multer configuration, unless either none of your routes expect files, or all of them does. Instead you should use multer.any()
for routes that are expected to receive both file uploads and form data. All other routes will do fine with multer.none()
. Here’s what I mean:
因此,实际的解决方案是不使用全局Multer配置,除非您的路由都没有期望文件,或者所有路由都没有。 相反,您应该对预期同时接收文件上传和表单数据的路由使用multer.any()
。 所有其他路由都可以通过multer.none()
。 这就是我的意思:
// This route receives files and form data
router.post('/upload',
this.upload.any(),
(req, res) => { ... });// This route receives only form data
router.post('/not_upload',
this.upload.none(),
(req, res) => { ... });
Of course it’s still recommended to process the upload, verify it and discard temporary files as soon as you’re done.
当然,仍然建议您在处理完毕后立即处理上传文件,对其进行验证并丢弃临时文件。