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')+ 'https://www.jb51.net/' + baseFilename; // The path to save the file chunk var localfilepath =tempdir + 'https://www.jb51.net/'+ 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"请求的。这个方法实现了之前描述过的功能。