Node層模擬實現multipart表單的檔案上傳
阿新 • • 發佈:2019-01-02
有時候就是有這樣的需求,Nodejs做webserver,從瀏覽器端上傳檔案到後端伺服器,Node層只是做一個數據中轉,如果在這個過程中,Node webserver需要對資料進行適當加工,然後再Post到後端,那麼就得在Node層模擬檔案上傳了。
首先,通過瀏覽器上傳檔案,PostData格式是長著個樣子的:
如圖,每一組資料其實就是用“-----WebkitFormBoundary.....”分隔開的,最後再用這個分隔符結束,而且,這個分隔符完全是可自定義的。
每一段提交資料,則通過Content-Disposition
來描述,未指定Content-Type
,則預設text/plain,如果是上傳的二進位制檔案,指定其mime-type即可。
簡單封裝一個方法,實現Node層的檔案上傳:
/**
* 上傳檔案
* @param files 經過formidable處理過的檔案
* @param req httpRequest物件
* @param postData 額外提交的資料
*/
function uploadFile(files, req, postData) {
var boundaryKey = Math.random().toString(16);
var endData = '\r\n----' + boundaryKey + '--';
var filesLength = 0 , content;
// 初始資料,把post過來的資料都攜帶上去
content = (function (obj) {
var rslt = [];
Object.keys(obj).forEach(function (key) {
arr = ['\r\n----' + boundaryKey + '\r\n'];
arr.push('Content-Disposition: form-data; name="' + key + '"\r\n\r\n');
arr.push(obj[key]);
rslt.push(arr.join('' ));
});
return rslt.join('');
})(postData);
// 組裝資料
Object.keys(files).forEach(function (key) {
if (!files.hasOwnProperty(key)) {
delete files.key;
return;
}
content += '\r\n----' + boundaryKey + '\r\n' +
'Content-Type: application/octet-stream\r\n' +
'Content-Disposition: form-data; name="' + key + '"; ' +
'filename="' + files[key].name + '"; \r\n' +
'Content-Transfer-Encoding: binary\r\n\r\n';
files[key].contentBinary = new Buffer(content, 'utf-8');
filesLength += files[key].contentBinary.length + fs.statSync(files[key].path).size;
});
req.setHeader('Content-Type', 'multipart/form-data; boundary=--' + boundaryKey);
req.setHeader('Content-Length', filesLength + Buffer.byteLength(endData));
// 執行上傳
var allFiles = Object.keys(files);
var fileNum = allFiles.length;
var uploadedCount = 0;
allFiles.forEach(function (key) {
req.write(files[key].contentBinary);
var fileStream = fs.createReadStream(files[key].path, {bufferSize: 4 * 1024});
fileStream.on('end', function () {
// 上傳成功一個檔案之後,把臨時檔案刪了
fs.unlink(files[key].path);
uploadedCount++;
if (uploadedCount == fileNum) {
// 如果已經是最後一個檔案,那就正常結束
req.end(endData);
}
});
fileStream.pipe(req, {end: false});
});
}
思路就這樣,程式碼也不復雜,可能額外需要注意的是,在http.request
的response處理中,response.headers
可能是gzip
的,這個時候buffer不能直接toString,需要通過zlib解碼再轉換為string,大概思路:
var result = [];
response.on('data', function (chunk) {
result.push(chunk);
});
// 處理response
var _dealResponse = function (data) {
var buffer = data;
try {
data = data.toString('utf8');
data = data ? (JSON.parse(data) || data) : false;
} catch (err) {
// 介面返回資料格式異常,解析失敗
console.log(err);
}
self.res.writeHead(response.statusCode, 'OK', {
'content-type': 'text/plain; charset=utf-8',
'content-length': buffer.length
});
self.res.write(buffer);
self.res.end();
};
response.on('end', function () {
result = Buffer.concat(result);
// gzip 的資料,需要zlib解碼
if (response.headers['content-encoding'] == 'gzip') {
zlib.gunzip(result, function (err, dezipped) {
var data = err ? new Buffer('{}') : dezipped;
_dealResponse(data);
});
} else {
_dealResponse(result);
}
});
Mark一下,也許你路過正好需要~~~