表單在上傳文件數據時,會設置 enctype="multipart/form-data",那么請求報文會設置 "Content-Type: multipart/form-data",但是我們在發送 Ajax 請求時,就不會去設置他,報文會自動生成一個 content-type 。
那我手動去設置 content-type 會發生什么呢?
首先,我寫一個 ajax 請求,
<form id="form">
<input type="file" name="files" multiple>
<label for="username">用戶名:</label>
<input type="text" name="username" id="username">
<label for="password">密碼:</label>
<input type="password" name="password" id="password">
<input type="button" value="提交" id="btn">
</form>
<script>
var form = document.getElementById('form');
var btn = document.getElementById('btn');
btn.onclick = function () {
var formData = new FormData(form);
var xhr = new XMLHttpRequest();
xhr.open('post', 'http://localhost:8080/Ajax');
//xhr.setRequestHeader('Content-Type', 'multipart/form-data');
xhr.send(formData);
xhr.onreadystatechange = function () {
if (xhr.readyState == 4 && xhr.status == 200) {
console.log(xhr.responseText);
}
}
}
</script>
然后,我們在服務端接收和處理請求,這里我用 formidable 來解析 form-data 數據
const http = require('http');
const path = require('path');
const formidable = require('formidable');
const server = http.createServer();
server.on('request', async (req, res) => {
if (req.url == '/Ajax') {
if (req.method == 'GET') {
let pathName = path.join(__dirname, 'ajax.html');
let data = await readFile(pathName, 'utf8');
res.end(data);
}
if (req.method == 'POST') {
const form = formidable({multiples:true});
form.parse(req, (err, fields, files) => {
if(err){
console.log(err);
res.statusCode = 500;
res.end('解析錯誤');
}else{
res.writeHead(200, { 'content-type': 'application/json' });
res.end(JSON.stringify({ fields, files }, null, 2));
}
});
return;
}
} else {
res.end('not found');
}
});
server.listen(8080);
console.log('服務器啟動成功');
請求成功后,服務端返回一個 json 字符串,結構是這樣的:
{
"fields": {
"username": "",
"password": ""
},
"files": {
"files": {
"size": 33341,
"path": "C:\\Users\\luzhi\\AppData\\Local\\Temp\\upload_81c6907665bd2dbbf2acf30a5eacb5c0",
"name": "The_Half_of_It_poster.jpeg",
"type": "image/jpeg",
"mtime": "2020-06-14T10:56:58.473Z"
}
}
}
這個 path 是文件存儲的臨時路徑,文件是以二進制的數據存儲。再來看一下請求頭和請求體的內容,
可以看到, content-type 中有一個boundary ,文件是以二進制編碼格式傳遞。這個 boundary 是什么呢?我們再 view source 看一下,
可以看見,這個 boundary 就是不同數據的一個標記字符串,formidable 解析數據時也是以這個來分塊解析的。
如果我們手動設置 content-type 會怎樣呢?
xhr.setRequestHeader('Content-Type', 'multipart/form-data');
content-type 沒有了 boundary,錯誤信息:
Error: bad content-type header, no multipart boundary
formidable 無法解析。但請求體依然會有這種特殊的分界的編碼字符串,
由此可知,這個 boundary 是瀏覽器自己生成的帶有特定格式的隨機字符串,用以分隔不同的數據體,並在content-type 中標明,供服務端接收解析數據。
知道了這個,那我們自己去解析 "multipart/form-data" 編碼格式的請求體,實現一個類似於 formidable 功能的函數。
解析 form-data 數據,實現 formidable 函數
關於 formParse 的代碼 https://github.com/Arduka/ajax-formParser