處理文件上傳數據,也是先後端交互中重要的功能,它的處理方式與數據不一樣。html
接下來,經過一個例子查看服務端接收到的文件上傳數據。git
首先,在post_file.html中,新建一個用與上傳文件的表單:github
form的屬性enctype="multipart/form-data"表明表單上傳的是文件。後端
enctype的默認值爲enctype="application/x-www-form-urlencoded"表示上傳的是數據類型,此時服務端接收到的數據爲「username=lee&password=123456&file=upload.txt」。數組
代碼示例:/lesson16/post_file.htmlbash
<form action="http://localhost:8080/upload" method="POST" enctype="multipart/form-data">
用戶:<input type="text" name="username" value="lee"><br/>
密碼:<input type="password" name="password" value="123456"><br/>
<input type="file" name="file" id=""><br/>
<input type="submit" value="提交">
</form>
複製代碼
其次,在server.js中,查看接收到的表單提交數據:服務器
代碼示例:/lesson16/server.jsapp
const http = require('http')
const server = http.createServer((req, res) => {
let arr = []
req.on('data', (buffer) => {
arr.push(buffer)
})
req.on('end', () => {
let buffer = Buffer.concat(arr)
console.log(buffer.toString())
})
})
server.listen(8080)
複製代碼
最後,在表單中上傳/lesson16/upload.txt文件,並查看打印出的結果:less
------WebKitFormBoundaryL5AGcit70yhKB92Y
Content-Disposition: form-data; name="username"
lee
------WebKitFormBoundaryL5AGcit70yhKB92Y
Content-Disposition: form-data; name="password"
123456
Content-Disposition: form-data; name="file"; filename="upload.txt"
Content-Type: text/plain
upload
------WebKitFormBoundaryL5AGcit70yhKB92Y--
複製代碼
經過分析上面這個例子中,服務端接收到的數據,能夠獲得如下信息:post
const boundary = '--' + req.headers['content-type'].split('; ')[1].split('=')[1]
。由此能夠看出,文件上傳數據雖然有些亂,但仍是有規律的,那麼處理思路就是按照規律,將數據切割以後,取出其中有用的部分。
先回顧一下上面的數據,並將回車符標記出來:
------WebKitFormBoundaryL5AGcit70yhKB92Y\r\n
Content-Disposition: form-data; name="username"\r\n
\r\n
lee\r\n
------WebKitFormBoundaryL5AGcit70yhKB92Y\r\n
Content-Disposition: form-data; name="password"\r\n
\r\n
123456\r\n
Content-Disposition: form-data; name="file"; filename="upload.txt"\r\n
Content-Type: text/plain\r\n
\r\n
upload\r\n
------WebKitFormBoundaryL5AGcit70yhKB92Y--
複製代碼
能夠看出,每段數據的結構實際上是這樣的:
------WebKitFormBoundaryL5AGcit70yhKB92Y\r\nContent-Disposition: form-data; name="username"\r\n\r\nlee\r\n
複製代碼
將每段上傳數據簡化以下:
<分隔符>\r\n字段頭\r\n\r\n內容\r\n
複製代碼
也就是說,整個表單的數據,就是按照這樣的數據格式組裝而成。
須要注意的是,在表單數據的結尾再也不是\r\n,而是「--」。
[
‘’,
"\r\n字段信息\r\n\r\n內容\r\n",
"\r\n字段信息\r\n\r\n內容\r\n",
"\r\n字段信息\r\n\r\n內容\r\n",
'--'
]
複製代碼
[
"\r\n字段信息\r\n\r\n內容\r\n",
"\r\n字段信息\r\n\r\n內容\r\n",
"\r\n字段信息\r\n\r\n內容\r\n",
]
複製代碼
[
"字段信息\r\n\r\n內容",
"字段信息\r\n\r\n內容",
"字段信息\r\n\r\n內容",
]
複製代碼
[
"字段信息", "內容",
"字段信息", "內容",
"字段信息", "內容",
]
複製代碼
因爲文件都是二進制數據,不能直接將其轉換爲字符串後再進行處理,不然數據會出錯,所以要經過Buffer模塊進行數據處理操做。
Buffer模塊提供了indexOf方法獲取Buffer數據中,其參數所在位置的index值。
Buffer模塊提供了slice方法,可經過index值切分Buffer數據。
先測試一下這兩個方法:
示例代碼:/lesson16/buffer.js
let buffer = Buffer.from('lee\r\nchen\r\ntest')
const index = buffer.indexOf('\r\n')
console.log(index)
console.log(buffer.slice(0, index).toString())
複製代碼
能夠看到打印結果分別爲3和"lee",也就是說,咱們先找到了"\r\n"所在的index爲3,以後從Buffer數據的index爲0的位置,切割到index爲3的位置,獲得了正確的結果。
由此,能夠封裝一個專門用於切割Buffer數據的方法:
示例代碼:/lesson16/bufferSplit.js
module.exports = function bufferSplit(buffer, separator) {
let result = [];
let index = 0;
while ((index = buffer.indexOf(separator)) != -1) {
result.push(buffer.slice(0, index));
buffer = buffer.slice(index + separator.length);
}
result.push(buffer);
return result;
}
複製代碼
有了bufferSplit方法,就能夠正式開始處理數據了。
根據上面的思路,就能夠實現一個完整的文件上傳流程。
代碼示例:/lesson16/server.js
const http = require('http')
const fs = require('fs')
const bufferSplit = require('./bufferSplit')
const server = http.createServer((req, res) => {
const boundary = `--${req.headers['content-type'].split('; ')[1].split('=')[1]}` // 獲取分隔符
let arr = []
req.on('data', (buffer) => {
arr.push(buffer)
})
req.on('end', () => {
const buffer = Buffer.concat(arr)
console.log(buffer.toString())
// 1. 用<分隔符>切分數據
let result = bufferSplit(buffer, boundary)
console.log(result.map(item => item.toString()))
// 2. 刪除數組頭尾數據
result.pop()
result.shift()
console.log(result.map(item => item.toString()))
// 3. 將每一項數據頭尾的的\r\n刪除
result = result.map(item => item.slice(2, item.length - 2))
console.log(result.map(item => item.toString()))
// 4. 將每一項數據中間的\r\n\r\n刪除,獲得最終結果
result.forEach(item => {
console.log(bufferSplit(item, '\r\n\r\n').map(item => item.toString()))
let [info, data] = bufferSplit(item, '\r\n\r\n') // 數據中含有文件信息,保持爲Buffer類型
info = info.toString() // info爲字段信息,這是字符串類型數據,直接轉換成字符串,若爲文件信息,則數據中含有一個回車符\r\n,能夠據此判斷數據爲文件仍是爲普通數據。
if (info.indexOf('\r\n') >= 0) { // 若爲文件信息,則將Buffer轉爲文件保存
// 獲取字段名
let infoResult = info.split('\r\n')[0].split('; ')
let name = infoResult[1].split('=')[1]
name = name.substring(1, name.length - 1)
// 獲取文件名
let filename = infoResult[2].split('=')[1]
filename = filename.substring(1, filename.length - 1)
console.log(name)
console.log(filename)
// 將文件存儲到服務器
fs.writeFile(`./upload/${filename}`, data, err => {
if (err) {
console.log(err)
} else {
console.log('文件上傳成功')
}
})
} else { // 若爲數據,則直接獲取字段名稱和值
let name = info.split('; ')[1].split('=')[1]
name = name.substring(1, name.length - 1)
const value = data.toString()
console.log(name, value)
}
})
})
})
server.listen(8080)
複製代碼