1. 程式人生 > >Node層模擬實現multipart表單的檔案上傳

Node層模擬實現multipart表單的檔案上傳

有時候就是有這樣的需求,Nodejs做webserver,從瀏覽器端上傳檔案到後端伺服器,Node層只是做一個數據中轉,如果在這個過程中,Node webserver需要對資料進行適當加工,然後再Post到後端,那麼就得在Node層模擬檔案上傳了。

首先,通過瀏覽器上傳檔案,PostData格式是長著個樣子的:

螢幕快照 2014-11-22 下午9.18.45.png

如圖,每一組資料其實就是用“-----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一下,也許你路過正好需要~~~