文件上传

2022-09-27 计算机网络 大约 5 分钟

"express": "^4.17.1",
"express-fileupload": "^1.1.9",
1
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
<!-- 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

服务启动后打开浏览器并输入 http://localhost:9999/submit 即可看到表单提交页面。表单属性 enctype 可以指定请求头 Content-Type 的类型,具体看下面介绍。

# multipart/form-data 提交方式

此种提交方式才是正确的表达提交方式。

点击提交按钮后:

  1. 请求头:
    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
  2. 响应头:
     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
    
     &lt;!DOCTYPE html>
     &lt;html lang="en">
     &lt;head>
     &lt;meta charset="utf-8">
     &lt;title>Error&lt;/title>
     &lt;/head>
     &lt;body>
     &lt;pre>Cannot POST /submit&lt;/pre>
     &lt;/body>
     &lt;/html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  1. 表单提交后,body 体的内容会用 boundary 的值进行分割 末尾 会带有 -- 加以标识。
  2. 文件会以 的方式进行传递。

# application/x-www-form-urlencoded 提交方式

此种方式为默认的提交方式,在不手动设置 enctype 的情况下默认是 application/x-www-form-urlencoded

点击提交按钮后:

  1. 请求头:
    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&amp;lname=%E4%B8%89&amp;file=logo-192.png
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
  2. 响应头:
    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
    
    &lt;!DOCTYPE html>
    &lt;html lang="en">
    &lt;head>
    &lt;meta charset="utf-8">
    &lt;title>Error&lt;/title>
    &lt;/head>
    &lt;body>
    &lt;pre>Cannot POST /submit&lt;/pre>
    &lt;/body>
    &lt;/html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  1. body 体是以 key=value 的方式进行传递的。
  2. 当有文件传输时,只会传输文件名,不会有文件的内容。

相关信息

当使用 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

# HTML5中的Api方式

此方式不使用form表单提交,需要自己处理文件类型然后发送ajax请求。

# Web端代码

// upload.js 文件添加如下代码

app.get('/base64', (req, res) => {
  res.sendFile(path.resolve(__dirname, "submitb64.html"))
})
1
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

# 服务器端代码

// 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

# 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
// 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
上次编辑于: 2023年7月4日 09:36