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

不管喜欢与否,javascript无处不在。 我们可以在客户端的前台应用中找到它,也可以在大量的框架、类库中找到它,而且可以在服务器端的后台应用中找到它。

创新互联服务项目包括路桥网站建设、路桥网站制作、路桥网页制作以及路桥网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,路桥网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到路桥省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!

近年来, Javascript越来越流行,这似乎是由于 Javascript 生态系统正在帮助提高生产率、减少入门所需的时间。 在我的***篇文章中,我介绍了使用 ASP.NET Web 后端 API 实现 G级文件上传,发表完这篇文章后,我决定试一下使用 Node.js 能否达到同样的效果。  这意味着我需要实现 UploadChunk和 MergeAll方法,在 Node.js中我发表的 ***一篇文章谈到了这一点。

开发环境

我们将使用 Visual Studio Express 2013 for Web 作为开发环境, 不过它还不能被用来做 Node.js 开发。为此我们需要安装 Node.js Tools for Visual Studio。  装好后 Visual Studio Express 2013 for Web 就会转变成一个 Node.js IDE 环境,提供创建这个应用所需要的所有东西.。而基于这里提供的指导,我们需要:

下载安装 Node.js  Windows 版,选择适用你系统平台的版本, Node.js (x86) 或者 Node.js (x64)。

下载并安装 Node.js 的 Visual Studio 工具。

安装完成后我们就会运行 Visual Studio Express 2013 for Web, 并使用 Node.js 的交互窗口来验证安装. Node.js 的交互窗口可以再 View->Other Windows->Node.js Interactive Window 下找到. Node.js 交互窗口运行后我们要输入一些命令检查是否一切OK.

Figure 1 Node.js Interactive Window

现在我们已经对安装进行了验证,我们现在就可以准备开始创建支持GB级文件上传的Node.js后台程序了. 开始我们先创建一个新的项目,并选择一个空的 Node.js Web应用程序模板.

Figure 2 New project using the Blank Node.js Web Application template

项目创建好以后,我们应该会看到一个叫做 server.js 的文件,还有解决方案浏览器里面的Node包管理器 (npm).

图3 解决方案管理器里面的 Node.js 应用程序

server.js 文件里面有需要使用Node.js来创建一个基础的hello world应用程序的代码.

Figure 4 The Hello World application

我现在继续把这段代码从 server.js 中删除,然后在Node.js中穿件G级别文件上传的后端代码。下面我需要用npm安装这个项目需要的一些依赖:

  •  Express - Node.js网页应用框架,用于构建单页面、多页面以及混合网络应用

  •  Formidable - 用于解析表单数据,特别是文件上传的Node.js模块

  •  fs-extra - 文件系统交互模块

图5 使用npm安装所需模块

模块安装完成后,我们可以从解决方案资源管理器中看到它们。

图6 解决方案资源管理器显示已安装模块

下一步我们需要在解决方案资源管理器新建一个 "Scripts" 文件夹并且添加  "workeruploadchunk.js" 和   "workerprocessfile.js" 到该文件夹。我们还需要下载 jQuery 2.x 和  SparkMD5 库并添加到"Scripts"文件夹。 ***还需要添加 "Default.html" 页面。这些都在我之前的 post 中介绍过。

#p#

创建Node.js后台

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

 
 
  1. // The required modules        
  2. var   express = require('express');      
  3. var   formidable = require('formidable');      
  4. var   fs = require('fs-extra');      
  5. var   path = require('path');  
  6. var   crypto = require('crypto');

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

 
 
  1. var app = express();

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

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

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

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

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

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

 
 
  1. // Get the extension from the file name  
  2. var extension =path.extname(request.param('filename'));      
  3.  
  4. // Get the base file name  
  5. var baseFilename =path.basename(request.param('filename'), extension);      
  6.  
  7. // Create the temporary file name for the chunk  
  8. var tempfilename =baseFilename + '.'+      
  9. request.param('chunkNumber').toString().padLeft('0', 16) + extension + ".tmp";      
  10.  
  11.  
  12. // Create the temporary directory to store the file chunk  
  13. // The temporary directory will be based on the file name  
  14. var tempdir =uploadpath + request.param('directoryname')+ '/' + baseFilename;      
  15.  
  16. // The path to save the file chunk  
  17. var localfilepath =tempdir + '/'+ tempfilename;      
  18.  
  19. if (fs.ensureDirSync(tempdir)) {      
  20.     console.log('Created directory ' +tempdir);  
  21. }

正如我之前提出的,我们可以通过两种方式上传文件到后端服务器。***种方式是在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写入文件中。

 
 
  1. // Check if we have uploaded a hand crafted multipart/form-data request  
  2. // If we have done so then the data is sent as a base64 string  
  3. // and we need to extract the base64 string and save it  
  4. if (request.headers['celerft-encoded']=== 'base64') {     
  5.  
  6.     var fileSlice = newBuffer(+request.headers['content-length']);      
  7.     var bufferOffset = 0;      
  8.  
  9.     // Get the data from the request  
  10.     request.on('data', function (chunk) {      
  11.         chunk.copy(fileSlice , bufferOffset);      
  12.         bufferOffset += chunk.length;      
  13.     }).on('end', function() {      
  14.         // Convert the data from base64 string to binary  
  15.         // base64 data in 4th index of the array  
  16.         var base64data = fileSlice.toString().split('\r\n');      
  17.         var fileData = newBuffer(base64data[4].toString(), 'base64');      
  18.  
  19.         fs.outputFileSync(localfilepath,fileData);      
  20.         console.log('Saved file to ' +localfilepath);      
  21.  
  22.         // Send back a sucessful response with the file name  
  23.         response.status(200).send(localfilepath);      
  24.         response.end();      
  25.     });  
  26. }

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

 
 
  1. else {      
  2.     // The data is uploaded as binary data.      
  3.     // We will use formidable to extract the data and save it      
  4.     var form = new formidable.IncomingForm();      
  5.     form.keepExtensions = true;      
  6.     form.uploadDir = tempdir;     
  7.  
  8.     // Parse the form and save the file chunks to the      
  9.     // default location      
  10.     form.parse(request, function (err, fields, files) {      
  11.         if (err){      
  12.             response.status(500).send(err);      
  13.             return;      
  14.         }      
  15.  
  16.     //console.log({ fields: fields, files: files });      
  17.     });      
  18.  
  19.     // Use the filebegin event to save the file with the naming convention      
  20.     /*form.on('fileBegin', function (name, file) {  
  21.     file.path = localfilepath;  
  22. });*/        
  23.  
  24. form.on('error', function (err) {      
  25.         if (err){      
  26.             response.status(500).send(err);      
  27.             return;      
  28.         }      
  29.     });      
  30.  
  31.     // After the files have been saved to the temporary name      
  32.     // move them to the to teh correct file name      
  33.     form.on('end', function (fields,files) {      
  34.         // Temporary location of our uploaded file             
  35.         var temp_path = this.openedFiles[0].path;      
  36.  
  37.         fs.move(temp_path , localfilepath,function (err){      
  38.  
  39.             if (err) {      
  40.                 response.status(500).send(err);      
  41.                 return;      
  42.             }      
  43.             else {      
  44.                 // Send back a sucessful response with the file name      
  45.                 response.status(200).send(localfilepath);      
  46.                 response.end();      
  47.             }     
  48.         });     
  49.     });      
  50.  
  51. // Send back a sucessful response with the file name      
  52. //response.status(200).send(localfilepath);      
  53. //response.end();      
  54. }  
  55. }

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

 
 
  1. // Request to merge all of the file chunks into one file  
  2. app.get('*/api/CelerFTFileUpload/MergeAll*', function(request,response) {      
  3.  
  4.     if (request.method === 'GET') {      
  5.  
  6.         // Get the extension from the file name  
  7.         var extension =path.extname(request.param('filename'));      
  8.  
  9.         // Get the base file name  
  10.         var baseFilename =path.basename(request.param('filename'), extension);      
  11.  
  12.         var localFilePath =uploadpath + request.param('directoryname')+ '/' + baseFilename;      
  13.  
  14.         // Check if all of the file chunks have be uploaded  
  15.         // Note we only wnat the files with a *.tmp extension  
  16.         var files =getfilesWithExtensionName(localFilePath, 'tmp')      
  17.         /*if (err) {  
  18.             response.status(500).send(err);  
  19.             return;  
  20.         }*/  
  21.  
  22.         if (files.length !=request.param('numberOfChunks')){     
  23.             response.status(400).send('Number of file chunks less than total count');      
  24.             return;      
  25.         }      
  26.  
  27.         var filename =localFilePath + '/'+ baseFilename +extension;      
  28.         var outputFile =fs.createWriteStream(filename);      
  29.  
  30.         // Done writing the file  
  31.         // Move it to top level directory  
  32.         // and create MD5 hash  
  33.         outputFile.on('finish', function (){      
  34.             console.log('file has been written');      
  35.             // New name for the file  
  36.             var newfilename = uploadpath +request.param('directoryname')+ '/' + baseFilename  
  37.             + extension;      
  38.  
  39.             // Check if file exists at top level if it does delete it  
  40.             //if (fs.ensureFileSync(newfilename)) {  
  41.             fs.removeSync(newfilename);      
  42.             //} 
  43.  
  44.             // Move the file  
  45.             fs.move(filename, newfilename ,function (err) {      
  46.                 if (err) {      
  47.                     response.status(500).send(err);      
  48.                     return;      
  49.                 }      
  50.                 else {      
  51.                     // Delete the temporary directory  
  52.                     fs.removeSync(localFilePath);      
  53.                     varhash = crypto.createHash('md5'),      
  54.                         hashstream = fs.createReadStream(newfilename);     
  55.  
  56.                     hashstream.on('data', function (data) {      
  57.                         hash.update(data)      
  58.                     });      
  59.  
  60.                     hashstream.on('end', function (){     
  61.                         var md5results =hash.digest('hex');      
  62.                         // Send back a sucessful response with the file name  
  63.                         response.status(200).send('Sucessfully merged file ' + filename + ", "      
  64.                         + md5results.toUpperCase());      
  65.                         response.end();      
  66.                     });      
  67.                 }      
  68.             });      
  69.         });      
  70.  
  71.         // Loop through the file chunks and write them to the file  
  72.         // files[index] retunrs the name of the file.  
  73.         // we need to add put in the full path to the file  
  74.         for (var index infiles) {     
  75.             console.log(files[index]);      
  76.             var data = fs.readFileSync(localFilePath +'/' +files[index]);      
  77.             outputFile.write(data);      
  78.             fs.removeSync(localFilePath + '/' + files[index]);      
  79.         }      
  80.         outputFile.end();      
  81.     }  
  82.  
  83. })   ;

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

 
 
  1. // String padding left code taken from  
  2. // http://www.lm-tech.it/Blog/post/2012/12/01/String-Padding-in-Javascript.aspx  
  3. String.prototype.padLeft = function (paddingChar, length) {      
  4.     var s = new String(this);      
  5.     if ((this.length< length)&& (paddingChar.toString().length > 0)) {      
  6.         for (var i = 0; i < (length - this.length) ; i++) {      
  7.             s = paddingChar.toString().charAt(0).concat(s);      
  8.         }      
  9.     }     
  10.     return s;  
  11. }   ;

#p#

一些其它事情

其中一件事是,发表上篇文章后我继续研究是为了通过域名碎片实现并行上传到CeleFT功能。域名碎片的原理是访问一个web站点时,让web浏览器建立更多的超过正常允许范围的并发连接。 域名碎片可以通过使用不同的域名(如web1.cdxwcx.com,web2.cdxwcx.com)或者不同的端口号(如8000, 8001)托管web站点的方式实现。

示例中,我们使用不同端口号托管web站点的方式。

我们使用 iisnode 把 Node.js集成到 IIS( Microsoft Internet Information Services)实现这一点。 下载兼容你操作系统的版本 iisnode (x86) 或者  iisnode (x64)。 下载 IIS URL重写包。

一旦安装完成(假定windows版Node.js已安装),到IIS管理器中创建6个新网站。将***个网站命名为CelerFTJS并且将侦听端口配置为8000。

图片7在IIS管理器中创建一个新网站

然后创建其他的网站。我为每一个网站都创建了一个应用池,并且给应用池“LocalSystem”级别的权限。所有网站的本地路径是C:\inetpub\wwwroot\CelerFTNodeJS。

图片8 文件夹层级

我在Release模式下编译了Node.js应用,然后我拷贝了server.js文件、Script文件夹以及node_modules文件夹到那个目录下。

要让包含 iisnode 的Node.js的应用工作,我们需要创建一个web.config文件,并在其中添加如下得内容。

 
 
  1.   
  2.       
  3.         
  4.       
  5.     
  6.  
  7.     
  8.           
  9.       
  10.     
  11.  
  12.     
  13.       
  14.         
  15.           
  16.           
  17.         
  18.  
  19.             
  20.         
  21.           
  22.         
  23.       
  24.   

web.config中各项的意思是让iisnode处理所有得*.js文件,由server.js 处理任何匹配"/*"的URL。

图片9 URL重写规则

如果你正确的做完了所有的工作,你就可以通过http://localhost:8000浏览网站,并进入CelerFT "Default.html"页面。

web.config文件被修改以支持如前面post中所解释的大文件的上传,这里我不会解释所有的项。不过下面的web.config项可以改善 iisnode中Node.js的性能。

 
 
  1.  node_env="production" debuggingEnabled="false" devErrorsEnabled="false" nodeProcessCountPerApplication="0"&

    网站题目:使用HTTP上传G级的文件之Node.js版本
    本文URL:http://www.gawzjz.com/qtweb/news13/163063.html

    网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

    广告

    声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联