文件上传
文件上传
文件上传在
这里我列举几个注意点:
- 后台需要进行文件类型、大小、来源等验证
- 定义一个.htaccess 文件,只允许访问指定扩展名的文件。
- 将上传后的文件生成一个随机的文件名,并且加上此前生成的文件扩展名。
- 设置上传目录执行权限,避免不怀好意的人绕过如图片扩展名进行恶意攻击,拒绝脚本执行的可能性。
const input = document.querySelector('input[type="file"]');
const data = new FormData();
data.append("file", input.files[0]);
data.append("user", "hubot");
fetch("/avatars", {
  method: "post",
  body: data,
});
请求端
浏览器端
对于浏览器端的文件上传,可以归结出一个套路,所有东西核心思路就是构造出

File
首先我们先写下最简单的一个表单提交方式。
<form action="http://localhost:7787/files" method="POST">
  <input name="file" type="file" id="file" />
  <input type="submit" value="提交" />
</form>
这个表单是无法实际进行传输的,其实

发现是请求头和预期不符,也印证了
<form
  action="http://localhost:7787/files"
  enctype="multipart/form-data"
  method="POST"
>
  <input name="file" type="file" id="file" />
  <input type="submit" value="提交" />
</form>
FormData
<input type="file" id="file" />
<button id="submit">上传</button>
<script src="https://cdn.bootcss.com/axios/0.19.2/axios.min.js"></script>
<script>
  submit.onclick = () => {
    const file = document.getElementById("file").files[0];
    var form = new FormData();
    form.append("file", file);
    // type 1
    axios.post("http://localhost:7787/files", form).then((res) => {
      console.log(res.data);
    });
    // type 2
    fetch("http://localhost:7787/files", {
      method: "POST",
      body: form,
    })
      .then((res) => res.json())
      .tehn((res) => {
        console.log(res);
      });
    // type3
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http://localhost:7787/files", true);
    xhr.onload = function() {
      console.log(xhr.responseText);
    };
    xhr.send(form);
  };
</script>

Blob
- 直接使用blob 上传
const json = { hello: "world" };
const blob = new Blob([JSON.stringify(json, null, 2)], {
  type: "application/json",
});
const form = new FormData();
form.append("file", blob, "1.json");
axios.post("http://localhost:7787/files", form);
- 使用File 对象,再进行一次包装
const json = { hello: "world" };
const blob = new Blob([JSON.stringify(json, null, 2)], {
  type: "application/json",
});
const file = new File([blob], "1.json");
form.append("file", file);
axios.post("http://localhost:7787/files", form);
ArrayBuffer
const bufferArrary = [
  137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0,
  0, 0, 1, 1, 3, 0, 0, 0, 37, 219, 86, 202, 0, 0, 0, 6, 80, 76, 84, 69, 0, 0,
  255, 128, 128, 128, 76, 108, 191, 213, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 14,
  196, 0, 0, 14, 196, 1, 149, 43, 14, 27, 0, 0, 0, 10, 73, 68, 65, 84, 8, 153,
  99, 96, 0, 0, 0, 2, 0, 1, 244, 113, 100, 166, 0, 0, 0, 0, 73, 69, 78, 68, 174,
  66, 96, 130,
];
const array = Uint8Array.from(bufferArrary);
const blob = new Blob([array], { type: "image/png" });
const form = new FormData();
form.append("file", blob, "1.png");
axios.post("http://localhost:7787/files", form);
这里需要注意的是
Base64
const base64 =
  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAABlBMVEUAAP+AgIBMbL/VAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAACklEQVQImWNgAAAAAgAB9HFkpgAAAABJRU5ErkJggg==";
const byteCharacters = atob(base64);
const byteNumbers = new Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
  byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const array = Uint8Array.from(byteNumbers);
const blob = new Blob([array], { type: "image/png" });
const form = new FormData();
form.append("file", blob, "1.png");
axios.post("http://localhost:7787/files", form);
Node 端上传
const path = require("path");
const fs = require("fs");
const http = require("http");
// 定义一个分隔符,要确保唯一性
const boundaryKey = "-------------------------461591080941622511336662";
const request = http.request({
  method: "post",
  host: "localhost",
  port: "7787",
  path: "/files",
  headers: {
    "Content-Type": "multipart/form-data; boundary=" + boundaryKey, // 在请求头上加上分隔符
    Connection: "keep-alive",
  },
});
// 写入内容头部
request.write(
  `--${boundaryKey}\r\nContent-Disposition: form-data; name="file"; filename="1.png"\r\nContent-Type: image/jpeg\r\n\r\n`
);
// 写入内容
const fileStream = fs.createReadStream(path.join(__dirname, "../1.png"));
fileStream.pipe(request, { end: false });
fileStream.on("end", function () {
  // 写入尾部
  request.end("\r\n--" + boundaryKey + "--" + "\r\n");
});
request.on("response", function (res) {
  console.log(res.statusCode);
});
我们也可以直接使用
const path = require("path");
const FormData = require("form-data");
const fs = require("fs");
const http = require("http");
const form = new FormData();
form.append("file", fs.readFileSync(path.join(__dirname, "../1.png")), {
  filename: "1.png",
  contentType: "image/jpeg",
});
const request = http.request({
  method: "post",
  host: "localhost",
  port: "7787",
  path: "/files",
  headers: form.getHeaders(),
});
form.pipe(request);
request.on("response", function (res) {
  console.log(res.statusCode);
});
Node 端接收
const koaBody = require("koa-body");
app.use(koaBody({ multipart: true }));
我们来看看最常用的

const fs = require("fs");
const http = require("http");
const querystring = require("querystring");
const server = http.createServer((req, res) => {
  if (req.url === "/files" && req.method.toLowerCase() === "post") {
    parseFile(req, res);
  }
});
function parseFile(req, res) {
  req.setEncoding("binary");
  let body = "";
  let fileName = "";
  // 边界字符
  let boundary = req.headers["content-type"]
    .split("; ")[1]
    .replace("boundary=", "");
  req.on("data", function (chunk) {
    body += chunk;
  });
  req.on("end", function () {
    // 按照分解符切分
    const list = body.split(boundary);
    let contentType = "";
    let fileName = "";
    for (let i = 0; i < list.length; i++) {
      if (list[i].includes("Content-Disposition")) {
        const data = list[i].split("\r\n");
        for (let j = 0; j < data.length; j++) {
          // 从头部拆分出名字和类型
          if (data[j].includes("Content-Disposition")) {
            const info = data[j].split(":")[1].split(";");
            fileName = info[info.length - 1].split("=")[1].replace(/"/g, "");
            console.log(fileName);
          }
          if (data[j].includes("Content-Type")) {
            contentType = data[j];
            console.log(data[j].split(":")[1]);
          }
        }
      }
    }
    // 去除前面的请求头
    const start = body.toString().indexOf(contentType) + contentType.length + 4; // 有多\r\n\r\n
    const startBinary = body.toString().substring(start);
    const end = startBinary.indexOf("--" + boundary + "--") - 2; // 前面有多\r\n
    // 去除后面的分隔符
    const binary = startBinary.substring(0, end);
    const bufferData = Buffer.from(binary, "binary");
    fs.writeFile(fileName, bufferData, function (err) {
      res.end("sucess");
    });
  });
}
server.listen(7787);
