文件上传
2022-09-27 大约 5 分钟
"express": "^4.17.1",
"express-fileupload": "^1.1.9",
1
2
2
# 表单提交
- FormData是用来表示表单数据的一个对象
- 里面是key/value;
- 用Boundary分割;
示例代码如下:
// src/upload.js
const express = require('express');
const path = require("path");
const app = express();
app.get('/submit', (req, res) => {
res.sendFile(path.resolve(__dirname, './submit.html'));
});
app.listen(9999);
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
<!-- src/submit.html -->
<!DOCTYPE html>
<html>
<body>
<form action="/submit" method="POST" enctype="multipart/form-data">
<label for="fname">First name:</label><br>
<input type="text" id="fname" name="fname" value="张"><br>
<label for="lname">Last name:</label><br>
<input type="text" id="lname" name="lname" value="三"><br><br>
<label for="file">file:</label><br>
<input type="file" id="file" name="file" multiple="multiple"><br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
服务启动后打开浏览器并输入 http://localhost:9999/submit
即可看到表单提交页面。表单属性 enctype
可以指定请求头 Content-Type
的类型,具体看下面介绍。
# multipart/form-data
提交方式
此种提交方式才是正确的表达提交方式。
点击提交按钮后:
- 请求头:
POST /submit HTTP/1.1 Host: www.dev.com Content-Length: 26169 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://www.dev.com Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryun6LRDoHPHdxL5WR User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://www.dev.com/submit Accept-Encoding: gzip Accept-Language: zh-CN,zh;q=0.9 Connection: keep-alive ------WebKitFormBoundaryun6LRDoHPHdxL5WR Content-Disposition: form-data; name="fname" 寮� ------WebKitFormBoundaryun6LRDoHPHdxL5WR Content-Disposition: form-data; name="lname" 涓� ------WebKitFormBoundaryun6LRDoHPHdxL5WR Content-Disposition: form-data; name="file"; filename="logo-192.png" Content-Type: image/png 塒NG �3 IEND瓸`� ------WebKitFormBoundaryun6LRDoHPHdxL5WR--
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 - 响应头:
HTTP/1.1 404 Not Found X-Powered-By: Express Content-Security-Policy: default-src 'none' X-Content-Type-Options: nosniff Content-Type: text/html; charset=utf-8 Content-Length: 146 Date: Wed, 28 Sep 2022 14:48:42 GMT Connection: keep-alive Keep-Alive: timeout=5 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Error</title> </head> <body> <pre>Cannot POST /submit</pre> </body> </html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 表单提交后,body 体的内容会用
boundary
的值进行分割末尾
会带有--
加以标识。- 文件会以
流
的方式进行传递。
# application/x-www-form-urlencoded
提交方式
此种方式为默认的提交方式,在不手动设置
enctype
的情况下默认是application/x-www-form-urlencoded
。
点击提交按钮后:
- 请求头:
POST /submit HTTP/1.1 Host: www.dev.com Content-Length: 49 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: http://www.dev.com Content-Type: application/x-www-form-urlencoded User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/104.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Referer: http://www.dev.com/submit Accept-Encoding: gzip Accept-Language: zh-CN,zh;q=0.9 Connection: keep-alive fname=%E5%BC%A0&lname=%E4%B8%89&file=logo-192.png
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 - 响应头:
HTTP/1.1 404 Not Found X-Powered-By: Express Content-Security-Policy: default-src 'none' X-Content-Type-Options: nosniff Content-Type: text/html; charset=utf-8 Content-Length: 146 Date: Wed, 28 Sep 2022 14:47:20 GMT Connection: keep-alive Keep-Alive: timeout=5 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Error</title> </head> <body> <pre>Cannot POST /submit</pre> </body> </html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
body
体是以key=value
的方式进行传递的。- 当有文件传输时,只会传输文件名,不会有文件的内容。
相关信息
当使用 form
标签进行表单提交时,需要设置 enctype="multipart/form-data"
。
# 保存文件
前面的代码主要是用于将文件从客户端提交到服务器端,那么服务端是如何把接收到的文件保存起来的呢。
需要添加一个 post
请求用于表单提交,添加 upload.js
代码如下:
// 引入 `express-fileupload` 包
const fileUpload = require('express-fileupload');
// 新增一个 `/submit` 的post请求,用于接收文件。
app.post('/submit', fileUpload(), (req, res) => {
// 获取文件对象
const file = req.files.file;
// 上传多文件
if (Array.isArray(file)) {
file.forEach(fileItem => {
// 将获取的文件移动到当前目录的 upload 文件夹中
fileItem.mv(path.resolve(__dirname, 'upload', fileItem.name));
})
} else {
// 上传单文件
file.mv(path.resolve(__dirname, 'upload', file.name));
}
res.status(200).send('ok');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# HTML5中的Api方式
此方式不使用form表单提交,需要自己处理文件类型然后发送ajax请求。
# Web端代码
// upload.js 文件添加如下代码
app.get('/base64', (req, res) => {
res.sendFile(path.resolve(__dirname, "submitb64.html"))
})
1
2
3
4
5
2
3
4
5
<!-- src/submitb64.html -->
<!DOCTYPE html>
<html>
<body>
<h2>HTML Forms</h2>
<div>
<label for="file">file:</label><br>
<input type="file" id="file" name="file"><br><br>
<input onClick='submit()' type="submit" value="Submit">
</div>
<script>
let upload = {};
// 监听文件的变动
document.getElementById("file").addEventListener('change', e=>{
const files = e.target.files;
for(let file of files) {
const fr = new FileReader();
fr.readAsDataURL(file);
fr.onload = () => {
// 获取base64
upload.data = fr.result.substr(22);
upload.name = file.name;
}
}
});
function submit(){
fetch('/submitb64', {
method : "POST",
//body : `name=${upload.name}&data=${upload.data}`,
body : JSON.stringify(upload),
headers : {
//'Content-Type' : 'application/x-www-form-urlencoded'
'Content-Type' : 'application/json'
}
});
}
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 服务器端代码
// src/upload.js
const fs = require('fs');
const bodyParser = require('body-parser');
// 注意:如果不设置 limit 则会出现 413(请求实体太大)
app.post("/submitb64", bodyParser.json({ limit: '50mb' }), (req, res) => {
const buffer = Buffer.from(req.body.data, 'base64');
fs.writeFileSync(path.resolve(__dirname, "upload/x1.jpg"), buffer);
res.send("ok");
});
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# formData 方式
base64进行文件的上传,会增加body体的大小,所以需要减小传输的体积。不建议使用base64进行文件的传输可以替换为blob。
<!-- submit.html -->
<script>
let upload = {}
function submit(){
// 创建文件表单对象
const formData = new FormData();
// 添加键值对
formData.append('name', upload.name);
formData.append('file', upload.data);
// 不需要设置 contentType fetch会自动设置
fetch('/submitbinary', {
method : "POST",
body : formData,
headers : {
// 这里如果设置了Content-Type会有Bug
// 'Content-Type' : 'multipart/form-data'
}
})
}
document.getElementById("file").addEventListener('change', e => {
const files = e.target.files;
for(let file of files) {
upload.data = file
upload.name = file.name
}
});
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// src/upload.js
app.post("/submitbinary", fileUpload(), (req, res) => {
req.files.file.mv(path.resolve(__dirname, 'upload/b.jpg'));
res.send("ok");
});
1
2
3
4
5
2
3
4
5