使用 HTTP 上传 G 级的文件之 Node.js 版本(2)

首先我们需要用Node.js的"require()"函数来导入在后台上传G级文件的模块。注意我也导入了"path"以及"crypto" 模块。"path"模块提供了生成上传文件块的文件名的方法。"crypto" 模块提供了生成上传文件的MD5校验和的方法。

// The required modules       
var  express = require('express');     
var  formidable = require('formidable');     
var  fs = require('fs-extra');     
var  path = require('path'); 
var  crypto = require('crypto');

下一行代码就是见证奇迹的时刻。

<span>var</span><span> app <span>=</span> express<span>()</span><span>;</span></span> 

这行代码是用来创建express应用的。express应用是一个封装了Node.js底层功能的中间件。如果你还记得那个由Blank Node.js Web应用模板创建的"Hello World" 程序,你会发现我导入了"http"模块,然后调用了"http.CreateServer()"方法创建了 "Hello World" web应用。我们刚刚创建的express应用内建了所有的功能。

现在我们已经创建了一个express应用,我们让它呈现之前创建的"Default.html",然后让应用等待连接。

// Serve up the Default.html page 
app.use(express.static(__dirname, { index: 'Default.html' }));     
 
// Startup the express.js application 
app.listen(process.env.PORT || 1337);     
 
// Path to save the files 
var  uploadpath = 'C:/Uploads/CelerFT/';

express应用有app.VERB()方法,它提供了路由的功能。我们将使用app.post()方法来处理"UploadChunk" 请求。在app.post()方法里我们做的第一件事是检查我们是否在处理POST请求。接下去检查Content-Type是否是mutipart/form-data,然后检查上传的文件块大小不能大于51MB。

// Use the post method for express.js to respond to posts to the uploadchunk urls and 
// save each file chunk as a separate file 
app.post('*/api/CelerFTFileUpload/UploadChunk*', function(request,response) {     
 
    if (request.method === 'POST') {     
        // Check Content-Type   
        if (!(request.is('multipart/form-data'))){     
            response.status(415).send('Unsupported media type');     
            return;     
        }     
 
        // Check that we have not exceeded the maximum chunk upload size 
        var maxuploadsize =51 * 1024 * 1024;     
 
        if (request.headers['content-length']> maxuploadsize){     
            response.status(413).send('Maximum upload chunk size exceeded');     
            return;     
        }

一旦我们成功通过了所有的检查,我们将把上传的文件块作为一个单独分开的文件并将它按顺序数字命名。下面最重要的代码是调用fs.ensureDirSync()方法,它使用来检查临时目录是否存在。如果目录不存在则创建一个。注意我们使用的是该方法的同步版本。

// Get the extension from the file name 
var extension =path.extname(request.param('filename'));     
 
// Get the base file name 
var baseFilename =path.basename(request.param('filename'), extension);     
 
// Create the temporary file name for the chunk 
var tempfilename =baseFilename + '.'+     
request.param('chunkNumber').toString().padLeft('0', 16) + extension + ".tmp";     
 
 
// Create the temporary directory to store the file chunk 
// The temporary directory will be based on the file name 
var tempdir =uploadpath + request.param('directoryname')+ '/' + baseFilename;     
 
// The path to save the file chunk 
var localfilepath =tempdir + '/'+ tempfilename;     
 
if (fs.ensureDirSync(tempdir)) {     
    console.log('Created directory ' +tempdir); 
}

正如我之前提出的,我们可以通过两种方式上传文件到后端服务器。第一种方式是在web浏览器中使用FormData,然后把文件块作为二进制数据发送,另一种方式是把文件块转换成base64编码的字符串,然后创建一个手工的multipart/form-data encoded请求,然后发送到后端服务器。 

所以我们需要检查一下是否在上传的是一个手工multipart/form-data encoded请求,通过检查"CelerFT-Encoded"头部信息,如果这个头部存在,我们创建一个buffer并使用request的ondata时间把数据拷贝到buffer中。

在request的onend事件中通过将buffer呈现为字符串并按CRLF分开,从而 multipart/form-data encoded请求中提取base64字符串。base64编码的文件块可以在数组的第四个索引中找到。

通过创建一个新的buffer来将base64编码的数据重现转换为二进制。随后调用fs.outputFileSync()方法将buffer写入文件中。

// Check if we have uploaded a hand crafted multipart/form-data request 
// If we have done so then the data is sent as a base64 string 
// and we need to extract the base64 string and save it 
if (request.headers['celerft-encoded']=== 'base64') {   
 
    var fileSlice = newBuffer(+request.headers['content-length']);     
    var bufferOffset = 0;     
 
    // Get the data from the request 
    request.on('data', function (chunk) {     
        chunk.copy(fileSlice , bufferOffset);     
        bufferOffset += chunk.length;     
    }).on('end', function() {     
        // Convert the data from base64 string to binary 
        // base64 data in 4th index of the array 
        var base64data = fileSlice.toString().split('\r\n');     
        var fileData = newBuffer(base64data[4].toString(), 'base64');     
 
        fs.outputFileSync(localfilepath,fileData);     
        console.log('Saved file to ' +localfilepath);     
 
        // Send back a sucessful response with the file name 
        response.status(200).send(localfilepath);     
        response.end();     
    }); 
}

二进制文件块的上传是通过formidable模块来处理的。我们使用formidable.IncomingForm()方法得到multipart/form-data encoded请求。formidable模块将把上传的文件块保存为一个单独的文件并保存到临时目录。我们需要做的是在formidable的onend事件中将上传的文件块保存为里一个名字。

else {     
    // The data is uploaded as binary data.     
    // We will use formidable to extract the data and save it     
    var form = new formidable.IncomingForm();     
    form.keepExtensions = true;     
    form.uploadDir = tempdir;   
 
    // Parse the form and save the file chunks to the     
    // default location     
    form.parse(request, function (err, fields, files) {     
        if (err){     
            response.status(500).send(err);     
            return;     
        }     
 
    //console.log({ fields: fields, files: files });     
    });     
 
    // Use the filebegin event to save the file with the naming convention     
    /*form.on('fileBegin', function (name, file) { 
    file.path = localfilepath; 
});*/       
 
form.on('error', function (err) {     
        if (err){     
            response.status(500).send(err);     
            return;     
        }     
    });     
 
    // After the files have been saved to the temporary name     
    // move them to the to teh correct file name     
    form.on('end', function (fields,files) {     
        // Temporary location of our uploaded file           
        var temp_path = this.openedFiles[0].path;     
 
        fs.move(temp_path , localfilepath,function (err){     
 
            if (err) {     
                response.status(500).send(err);     
                return;     
            }     
            else {     
                // Send back a sucessful response with the file name     
                response.status(200).send(localfilepath);     
                response.end();     
            }   
        });   
    });     
 
// Send back a sucessful response with the file name     
//response.status(200).send(localfilepath);     
//response.end();     

}

app.get()方法使用来处理"MergeAll"请求的。这个方法实现了之前描述过的功能。

// Request to merge all of the file chunks into one file 
app.get('*/api/CelerFTFileUpload/MergeAll*', function(request,response) {     
 
    if (request.method === 'GET') {     
 
        // Get the extension from the file name 
        var extension =path.extname(request.param('filename'));     
 
        // Get the base file name 
        var baseFilename =path.basename(request.param('filename'), extension);     
 
        var localFilePath =uploadpath + request.param('directoryname')+ '/' + baseFilename;     
 
        // Check if all of the file chunks have be uploaded 
        // Note we only wnat the files with a *.tmp extension 
        var files =getfilesWithExtensionName(localFilePath, 'tmp')     
        /*if (err) { 
            response.status(500).send(err); 
            return; 
        }*/ 
 
        if (files.length !=request.param('numberOfChunks')){   
            response.status(400).send('Number of file chunks less than total count');     
            return;     
        }     
 
        var filename =localFilePath + '/'+ baseFilename +extension;     
        var outputFile =fs.createWriteStream(filename);     
 
        // Done writing the file 
        // Move it to top level directory 
        // and create MD5 hash 
        outputFile.on('finish', function (){     
            console.log('file has been written');     
            // New name for the file 
            var newfilename = uploadpath +request.param('directoryname')+ '/' + baseFilename 
            + extension;     
 
            // Check if file exists at top level if it does delete it 
            //if (fs.ensureFileSync(newfilename)) { 
            fs.removeSync(newfilename);     
            //}
 
            // Move the file 
            fs.move(filename, newfilename ,function (err) {     
                if (err) {     
                    response.status(500).send(err);     
                    return;     
                }     
                else {     
                    // Delete the temporary directory 
                    fs.removeSync(localFilePath);     
                    varhash = crypto.createHash('md5'),     
                        hashstream = fs.createReadStream(newfilename);   
 
                    hashstream.on('data', function (data) {     
                        hash.update(data)     
                    });     
 
                    hashstream.on('end', function (){   
                        var md5results =hash.digest('hex');     
                        // Send back a sucessful response with the file name 
                        response.status(200).send('Sucessfully merged file ' + filename + ", "     
                        + md5results.toUpperCase());     
                        response.end();     
                    });     
                }     
            });     
        });     
 
        // Loop through the file chunks and write them to the file 
        // files[index] retunrs the name of the file. 
        // we need to add put in the full path to the file 
        for (var index infiles) {   
            console.log(files[index]);     
            var data = fs.readFileSync(localFilePath +'/' +files[index]);     
            outputFile.write(data);     
            fs.removeSync(localFilePath + '/' + files[index]);     
        }     
        outputFile.end();     
    } 
 
})  ;

注意Node.js并没有提供String.padLeft()方法,这是通过扩展String实现的。

// String padding left code taken from 
// Javascript.aspx 
String.prototype.padLeft = function (paddingChar, length) {     
    var s = new String(this);     
    if ((this.length< length)&& (paddingChar.toString().length > 0)) {     
        for (var i = 0; i < (length - this.length) ; i++) {     
            s = paddingChar.toString().charAt(0).concat(s);     
        }     
    }   
    return s; 
}  ;

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/8424bf189e6b6de48461f07d119858ae.html