nodejs 處理文件上傳(express)

在實際開發功能中,經常會有涉及到文件上傳的需求。這裏簡單記錄一下文件上傳處理的sample。具體場景還需要進一步開發。


服務端:使用express框架處理文件上傳
Client:使用POSTMAN進行文件上傳/使用nodejs request 編寫http請求

1 使用binary方式上傳

將整個報文體作爲文件上傳。(POST/PUT 方法)
這種情況用於已知文件大小時傳輸。
express 服務端代碼:

const express = require('express');
const router = express.Router();
const fs = require('fs');

router.post('/upload', (req, res, next) => {
  console.log(req.headers);
  let buffers = [];
  req.on('data',(trunk) => {
    console.info(trunk.length);
    buffers.push(trunk);
  }).on('end',async () => {
      const buffer= Buffer.concat(buffers);
      fs.writeFileSync('test.mp4', buffer);
      return res.end();
    }).on('close', () => {
      res.status(400).json({"err_code":"40000"});
    }).on('error', () => {
      res.status(400).json({"err_code":"40000"});
    });
})

將接受到所有數據寫入文件中。
**
POSTMAN 上傳選擇bin時 HTTP headers:

accept:"*/*"
accept-encoding:"gzip, deflate, br"
cache-control:"no-cache"
connection:"keep-alive"
content-length:"7995707"
content-type:"video/quicktime"
host:"localhost:12000"
postman-token:"79ef8202-d390-4496-bbce-0b3cd59ad71b"
user-agent:"PostmanRuntime/7.22.0"

使用nodejs request模塊上傳

const fs = require('fs');
const request = require("request");
const video = fs.readFileSync('./test.mp4');

var options = { method: 'POST',
  url: 'http://localhost:12000/file/upload',
  body:video
};

request(options, function (error, response, body) {
  if (error) {
    console.error(error);
  }
  console.log(response.statusCode);
});

http headers:·

connection:"close"
content-length:"2897147"
host:"localhost:12000"

2 Chunked方式上傳

分塊傳輸編碼(Chunked transfer encoding)是超文本傳輸協議(HTTP)中的一種數據傳輸機制,允許HTTP由網頁服務器發送給客戶端應用( 通常是網頁瀏覽器)的數據可以分成多個部分。使用分塊傳輸編碼,數據分解成一系列數據塊,並以一個或多個塊發送,這樣服務器可以發送數據而不需要預先知道發送內容的總大小。
在不知道數據量有多大的情況下可以使用chunked來進行數據傳輸

1 HTTP的頭部有Transfer-Encoding: chunked
2 HTTP的body中組成: 編碼使用若干個chunk組成,由一個標明長度爲0的chunk結束。
每個chunk有兩部分組成,第一部分是該chunk的長度,第二部分就是指定長度的內容,每個部分用CRLF隔開。在最後一個長度爲0的chunk中的內容是稱爲footer的內容,是一些沒有寫的頭部內容。
chunk格式如下:
[chunk size][\r\n][chunk data][\r\n][chunk size][\r\n][chunk data][\r\n][chunk size = 0][\r\n][\r\n]

chunk size是以十六進制的ASCII碼錶示,比如:頭部是3134這兩個字節,表示的是1和4這兩個ascii字符,被http協議解釋爲十六進制數14,也就是十進制的20,後面緊跟[\r\n](0d 0a),再接着是連續的20個字節的chunk正文。chunk數據以0長度的chunk塊結束,也就是(30 0d 0a 0d 0a)。

Server 代碼同1.1 中代碼, express req.on(‘data’)會自行處理chunked 這種數據方式,觸發data直接就是解析chunked後的真正數據

Server收到的headers 如下:

connection:"close"
host:"localhost:12000"
transfer-encoding:"chunked"

nodejs request請求代碼:

const fs = require('fs');
const request = require("request");
const videoFile = fs.readFileSync('./test.mp4');
const video = Buffer.from(videoFile);
console.info(videoFile.length);
const video1 = video.slice(0, 1000000); 
const video2 = video.slice(1000000, 2000000); 
const video3 = video.slice(2000000); 

const http = require('follow-redirects').http;
var options = {
  'method': 'POST',
  'hostname': 'localhost',
  'port':'12000',
  'path':'/file/upload',
  'maxRedirects': 20
};
var req = http.request(options, function (res) {
  var chunks = [];
  res.on("data", function (chunk) {
    chunks.push(chunk);
  });

  res.on("end", function (chunk) {
    console.log("------------------");
    var body = Buffer.concat(chunks);
    console.log(body.toString());
  });

  res.on("error", function (error) {
    console.error(error);
  });
});
// var postData = video;
req.write(video1);
req.write(video2);
req.write(video3);

req.end();

這裏代碼沒有填寫Content-Length 所以這裏默認使用chunked 方式傳輸,如果這類填入content-Length
即上訴代碼中options修改爲:

var options = {
  'method': 'POST',
  'hostname': 'localhost',
  'port':'12000',
  'path':'/file/upload',
  'maxRedirects': 20,
  headers:{
    'Content-Length':videoFile.length
  }
};

則會禁用chunked方式傳輸。但實際上數據還是分段寫入,這種用法可以用去嵌入式設備。比如內存有限的情況下不能將一個文件讀出來再調用API進行發送,這裏就可以讀一點發一點,最後嗲用res.end().
這裏headers 如下:

connection:"close"
content-length:"2897147"
host:"localhost:12000"

3 x-www-urlencoded 對值進行base64編碼上傳文件

默認情況下是 application/x-www-urlencoded,當表單使用 POST 請求時,數據會被以 x-www-urlencoded 方式編碼到 Body 中來傳送。即會被編碼成價值對,如比如name=java&age
= 23。 x-www-urlencoded好像僅支持ASCII字符集。
原來使用百度API時,用於識別的圖片文件就被base64編碼成一個字段值進行傳輸。但這種數據傳輸僅適用於小文件,因爲base64對2進制數據編碼會造成文件大小增加。影響傳輸效率。
所以下列代碼 name=imageName&image=imageDataBase64 將圖片數據傳輸給server。

普通的 HTML Form POST請求,它會在頭信息裏使用 Content-Length 註明內容長度。請求頭信息每行一條,空行之後便是 Body,即“內容”(entity)。內容的格式是在頭信息中的 Content-Type 指定的,如上是 application/x-www-form-urlencoded,這意味着消息內容會經過 URL 格式編碼

express 處理代碼

router.post('/base64', (req, res, next) => {
  console.info(req.headers);
  console.info(req.body.image.length);
  const base64Data = req.body.image.replace(/\s/g,"+");
  const imageData = Buffer.from(base64Data, 'base64');//解碼圖片
  fs.writeFileSync('test.png', imageData);
  res.end();
})

Http headers

connection:"close"
content-length:"2006"
content-type:"application/x-www-form-urlencoded"
host:"localhost:12000"

nodejs request代碼

const fs = require('fs');
const request = require("request");
const imageFile = fs.readFileSync('./test.png');
let base64str = Buffer.from(imageFile, 'binary').toString('base64');
const body = {
  name: 'test.png',
  image: base64str
}
var options = { method: 'POST',
  url: 'http://localhost:12000/file/base64',
  form: body
};
request(options, (error, response, body) => {
  if (error) {
    console.error(error);
  }
  console.log(response.statusCode);
  console.log(body);
});

4 表單上傳

form-data:
http請求中的multipart/form-data,會將表單的數據處理爲一條消息,以標籤爲單元,用分隔符分開。
既可以上傳鍵值對,也可以上傳文件。當上傳的字段是文件,會使用content-type表明文件類型;content-disposition說明字段的一些信息。
由於有boundary隔離,所以multipart/form-data既可以上傳文件,也可以上傳鍵值對。

下面舉個例子:

POST /file/formdata HTTP/1.1
Host: localhost:12000
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW

----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="name"

haha
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="video"; filename="/E:/source/develop/fileuploadserver/uploadclient/test.mp4"
Content-Type: <Content-Type header here>

(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW
Content-Disposition: form-data; name="image"; filename="/E:/source/develop/fileuploadserver/uploadclient/test.png"
Content-Type: image/png

(data)
----WebKitFormBoundary7MA4YWxkTrZu0gW

可以看到body之間使用content-Type中 boundary 分隔符進行分隔。

接受form-data編碼的數據,其中有三個字段name video image, video image爲文件。
express處理代碼:這裏使用multer模塊

const express = require('express');
const router = express.Router();
const multer = require('multer'); // v1.0.5
const fs = require('fs');
const storage  = multer.memoryStorage();
var upload = multer({storage: storage})
router.post('/formdata', function(req, res, next) {
    console.log(req.headers);
    upload.fields([{name:'video', maxCount:1}, { name:'image', maxCount: 1}])(req, res, (err) => {//接受文件上傳
      console.log(req.files.video[0]);
      console.log(req.files.image[0]);
      fs.writeFileSync('test.MOV', req.files.video[0].buffer);
      fs.writeFileSync('test.png', req.files.image[0].length);
      console.log(req.body.name);
      res.end();
    });
})

HTTP headers 請求headers

connection:"close"
content-length:"2898918"
content-type:"multipart/form-data; boundary=--------------------------230104090310630847651902"
host:"localhost:12000"

nodejs request 請求

const fs = require('fs');
const request = require("request");
const formData = {
  name: 'haha',
  video: fs.createReadStream('./test.mp4'),
  image: fs.createReadStream('./test.png'),
};
var options = { method: 'POST',
  url: 'http://localhost:12000/file/formdata',
  formData,
};
// console.info(options);
request(options, (error, response, body) => {
  if (error) {
    console.error(error);
  }
  console.log(response.statusCode);
  console.log(body);
});

5 斷點續傳(分塊併發上傳)/大文件處理

todo

6 示例代碼

鏈接: https://pan.baidu.com/s/1fM8VsI4u8iUTT-wt6Xf89A 提取碼: y1sk

在experss 和client 分別 npm install 即可。然後閱讀client代碼及路由代碼即可非常簡單。

參考:
https://blog.csdn.net/wyn126/article/details/96451357

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章