本文主要講述基於node如何實現文件上傳和下載,分成原生node實現版、中間件實現版。
1、文件上傳爲什麼需要使用multipart/form-data編碼類型?
The encoding type application/x-www-form-urlencoded is inefficient for sending large quantities of binary data or text containing non-ASCII characters. Thus, a new media type,multipart/form-data, is proposed as a way of efficiently sending the values associated with a filled-out form from client to server.
文檔大概意思是說application/x-www-form-urlencoded不適合用於傳輸大型二進制數據和包含非ASCII字符的文本,因此提出了一個新的MIME類型multipart/form-data,有效地將與填寫的表單相關聯的值從客戶端發送到服務器。想了解很多MIME類型,點擊查看
2、文件對象介紹
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<form method="POST" id="uploadForm" enctype="multipart/form-data">
<input type="file" id="file" name="file" />
</form>
<button id="submit">submit</button>
</body>
</html>
<script>
$("#submit").click(function() {
console.log($("#file")[0].files[0])
});
</script>
文件(File)接口提供有關文件的信息,並允許網頁中的 JavaScript 訪問其內容。
File 對象是特殊類型的 Blob,且可以用在任意的 Blob 類型的 context 中。比如說, FileReader, URL.createObjectURL(), createImageBitmap(), 及 XMLHttpRequest.send() 都能處理 Blob 和 File。
另外還有一個blob對象,也附上一張chrome瀏覽器的截圖。blob,二進制大文檔存儲
兩個對象裏面都有size和type,按照官方的文檔,file集成了blob,而且可以使用slice方法.
slice方法可以在當前的blob數據中,取出一段數據,作爲新的blob。常用的就是文件斷點上傳。
3、node中的buffer、Stream、fs模塊介紹
JavaScript語言沒有用於讀取或處理二進制數據流的機制。
但在處理像TCP流或文件流時,必須使用到二進制數據。因此在 Node.js中,定義了一個 Buffer 類,該類用來創建一個專門存放二進制數據的緩存區。
在 Node.js 中,Buffer 類是隨 Node 內核一起發佈的核心庫。Buffer 庫爲 Node.js 帶來了一種存儲原始數據的方法,可以讓 Node.js 處理二進制數據,每當需要在 Node.js 中處理I/O操作中移動的數據時,就有可能使用 Buffer 庫。原始數據存儲在 Buffer 類的實例中。一個 Buffer 類似於一個整數數組,但它對應於 V8 堆內存之外的一塊原始內存(buffer 是 C++ 層面分配的,所得內存不在 V8 內)。
const Buffer = require('buffer').Buffer
var buf1 = Buffer.from('tést');
var buf2 = Buffer.from('tést', 'latin1');
var buf3 = Buffer.from([1, 2, 3]);
console.log(buf1, buf2, buf3);
輸出的結果爲:
<Buffer 74 c3 a9 73 74> <Buffer 74 e9 73 74> <Buffer 01 02 03>
不是說node引入buffer是來處理二進制數據流嗎?怎麼轉換成buffer對象打印出來卻不是二進制,而是十六進制呢?
在計算機內使用二進制表示數據,一個存儲空間叫做一個 bit ,只能存儲 0 或是 1。 通常,計算機把 8 個bit作爲一個存儲的單位,稱爲一個 Byte。
於是一個 Byte 可以出現 256 種不同的情況。一個 Buffer 是一段內存,比如大小爲 2(Byte)的buffer,一共有 16 bit ,比如是
00000001 00100011
,可是這樣顯示太不方便。所以顯示這段內存的數據的時候,用其對應的 16 進制就比較方便了,是01 23
,之所以用 16 進制是因爲轉換比較方便。內存僅僅存儲的是二進制的數據,但是如何解釋就是我們人類自己的事情了。。。。比如
A
在 內存中佔用兩個Byte,對應的內存狀態是0000000 01000001
,而uint16
(JS不存在這個類型) 類型的65
對應的存儲內存的狀態也是這個。如果輸出 Buffer 那麼nodejs 輸出的是內存實際存儲的值(因爲你沒有給出如何解釋這段內存中的數據),可是二進制顯示起來不方便看,所以轉換爲 16 進制方便人類閱讀。
如果轉換爲數組,那麼意思就是把這個 buffer 的每一個字節解釋爲一個數字(其實是10進制數字,這是人類最方便的),所以是 0~255 的 10 進制數字。
總之,這樣轉化的目的是方便顯示和查看。
Stream是一個抽象接口,Node中有很多對象實現了這個接口。例如,對http服務器發起請求的request對象和服務端響應對象response就是Stream,還有stdout(標準輸出)。
你可以把流理解成一種傳輸的能力。通過流,可以以平緩的方式,無副作用的將數據傳輸到目的地。Stream表示的是一種傳輸能力,Buffer是傳輸內容的載體 (可以這樣理解,Stream:外賣小哥哥, Buffer:你的外賣)。
在node中流無處不在:
流爲什麼這麼好用還這麼重要呢?
現在有個需求,我們要向客戶端傳輸一個大文件。每次接收一個請求,就要把這個大文件讀入內存,然後再傳輸給客戶端。
const fs = require('fs');
const server = require('http').createServer();
server.on('request', (req, res) => {
fs.readFile('./big.file', (err, data) => {
if (err) throw err;
res.end(data);
});
});
server.listen(8000);
通過這種方式可能會產生以下三種後果:
- 內存耗盡
- 拖慢其他進程
- 增加垃圾回收器的負載
所以這種方式在傳輸大文件的情況下,不是一個好的方案。併發量一大,幾百個請求過來很容易就將內存耗盡。
如果採用流呢?
const fs = require('fs');
const server = require('http').createServer();
server.on('request', (req, res) => {
const src = fs.createReadStream('./big.file');
src.pipe(res);
});
server.listen(8000);
採用這種方式,不會佔用太多內存,可以將文件一點一點讀到內存中,再一點一點返回給用戶,讀一部分,寫一部分。(利用了 HTTP 協議的 Transfer-Encoding: chunked 分段傳輸特性),用戶體驗得到優化,同時對內存的開銷明顯下降。如果想在傳輸的過程中,想對文件進行處理,比如壓縮、加密等等,也很好擴展。
未完待續。。。
參考文獻:
buffer概述:
http://www.runoob.com/nodejs/nodejs-buffer.html
https://nodejs.org/api/buffer.html#buffer_buffer
buffer數據顯示:
https://segmentfault.com/q/1010000009002065
stream運行機制:
https://www.php.cn/js-tutorial-412138.html
stream的理解:
https://www.jianshu.com/p/4eb9077a8956