新聞中心
不管喜歡與否,javascript無(wú)處不在。 我們可以在客戶端的前臺(tái)應(yīng)用中找到它,也可以在大量的框架、類庫(kù)中找到它,而且可以在服務(wù)器端的后臺(tái)應(yīng)用中找到它。

創(chuàng)新互聯(lián)服務(wù)項(xiàng)目包括路橋網(wǎng)站建設(shè)、路橋網(wǎng)站制作、路橋網(wǎng)頁(yè)制作以及路橋網(wǎng)絡(luò)營(yíng)銷策劃等。多年來(lái),我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術(shù)優(yōu)勢(shì)、行業(yè)經(jīng)驗(yàn)、深度合作伙伴關(guān)系等,向廣大中小型企業(yè)、政府機(jī)構(gòu)等提供互聯(lián)網(wǎng)行業(yè)的解決方案,路橋網(wǎng)站推廣取得了明顯的社會(huì)效益與經(jīng)濟(jì)效益。目前,我們服務(wù)的客戶以成都為中心已經(jīng)輻射到路橋省份的部分城市,未來(lái)相信會(huì)繼續(xù)擴(kuò)大服務(wù)區(qū)域并繼續(xù)獲得客戶的支持與信任!
近年來(lái), Javascript越來(lái)越流行,這似乎是由于 Javascript 生態(tài)系統(tǒng)正在幫助提高生產(chǎn)率、減少入門(mén)所需的時(shí)間。 在我的***篇文章中,我介紹了使用 ASP.NET Web 后端 API 實(shí)現(xiàn) G級(jí)文件上傳,發(fā)表完這篇文章后,我決定試一下使用 Node.js 能否達(dá)到同樣的效果。 這意味著我需要實(shí)現(xiàn) UploadChunk和 MergeAll方法,在 Node.js中我發(fā)表的 ***一篇文章談到了這一點(diǎn)。
開(kāi)發(fā)環(huán)境
我們將使用 Visual Studio Express 2013 for Web 作為開(kāi)發(fā)環(huán)境, 不過(guò)它還不能被用來(lái)做 Node.js 開(kāi)發(fā)。為此我們需要安裝 Node.js Tools for Visual Studio。 裝好后 Visual Studio Express 2013 for Web 就會(huì)轉(zhuǎn)變成一個(gè) Node.js IDE 環(huán)境,提供創(chuàng)建這個(gè)應(yīng)用所需要的所有東西.。而基于這里提供的指導(dǎo),我們需要:
下載安裝 Node.js Windows 版,選擇適用你系統(tǒng)平臺(tái)的版本, Node.js (x86) 或者 Node.js (x64)。
下載并安裝 Node.js 的 Visual Studio 工具。
安裝完成后我們就會(huì)運(yùn)行 Visual Studio Express 2013 for Web, 并使用 Node.js 的交互窗口來(lái)驗(yàn)證安裝. Node.js 的交互窗口可以再 View->Other Windows->Node.js Interactive Window 下找到. Node.js 交互窗口運(yùn)行后我們要輸入一些命令檢查是否一切OK.
Figure 1 Node.js Interactive Window
現(xiàn)在我們已經(jīng)對(duì)安裝進(jìn)行了驗(yàn)證,我們現(xiàn)在就可以準(zhǔn)備開(kāi)始創(chuàng)建支持GB級(jí)文件上傳的Node.js后臺(tái)程序了. 開(kāi)始我們先創(chuàng)建一個(gè)新的項(xiàng)目,并選擇一個(gè)空的 Node.js Web應(yīng)用程序模板.
Figure 2 New project using the Blank Node.js Web Application template
項(xiàng)目創(chuàng)建好以后,我們應(yīng)該會(huì)看到一個(gè)叫做 server.js 的文件,還有解決方案瀏覽器里面的Node包管理器 (npm).
圖3 解決方案管理器里面的 Node.js 應(yīng)用程序
server.js 文件里面有需要使用Node.js來(lái)創(chuàng)建一個(gè)基礎(chǔ)的hello world應(yīng)用程序的代碼.
Figure 4 The Hello World application
我現(xiàn)在繼續(xù)把這段代碼從 server.js 中刪除,然后在Node.js中穿件G級(jí)別文件上傳的后端代碼。下面我需要用npm安裝這個(gè)項(xiàng)目需要的一些依賴:
-
Express - Node.js網(wǎng)頁(yè)應(yīng)用框架,用于構(gòu)建單頁(yè)面、多頁(yè)面以及混合網(wǎng)絡(luò)應(yīng)用
-
Formidable - 用于解析表單數(shù)據(jù),特別是文件上傳的Node.js模塊
-
fs-extra - 文件系統(tǒng)交互模塊
圖5 使用npm安裝所需模塊
模塊安裝完成后,我們可以從解決方案資源管理器中看到它們。
圖6 解決方案資源管理器顯示已安裝模塊
下一步我們需要在解決方案資源管理器新建一個(gè) "Scripts" 文件夾并且添加 "workeruploadchunk.js" 和 "workerprocessfile.js" 到該文件夾。我們還需要下載 jQuery 2.x 和 SparkMD5 庫(kù)并添加到"Scripts"文件夾。 ***還需要添加 "Default.html" 頁(yè)面。這些都在我之前的 post 中介紹過(guò)。
#p#
創(chuàng)建Node.js后臺(tái)
首先我們需要用Node.js的"require()"函數(shù)來(lái)導(dǎo)入在后臺(tái)上傳G級(jí)文件的模塊。注意我也導(dǎo)入了"path"以及"crypto" 模塊。"path"模塊提供了生成上傳文件塊的文件名的方法。"crypto" 模塊提供了生成上傳文件的MD5校驗(yàn)和的方法。
- // The required modules
- var express = require('express');
- var formidable = require('formidable');
- var fs = require('fs-extra');
- var path = require('path');
- var crypto = require('crypto');
下一行代碼就是見(jiàn)證奇跡的時(shí)刻。
- var app = express();
這行代碼是用來(lái)創(chuàng)建express應(yīng)用的。express應(yīng)用是一個(gè)封裝了Node.js底層功能的中間件。如果你還記得那個(gè)由Blank Node.js Web應(yīng)用模板創(chuàng)建的"Hello World" 程序,你會(huì)發(fā)現(xiàn)我導(dǎo)入了"http"模塊,然后調(diào)用了"http.CreateServer()"方法創(chuàng)建了 "Hello World" web應(yīng)用。我們剛剛創(chuàng)建的express應(yīng)用內(nèi)建了所有的功能。
現(xiàn)在我們已經(jīng)創(chuàng)建了一個(gè)express應(yīng)用,我們讓它呈現(xiàn)之前創(chuàng)建的"Default.html",然后讓?xiě)?yīng)用等待連接。
- // 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應(yīng)用有app.VERB()方法,它提供了路由的功能。我們將使用app.post()方法來(lái)處理"UploadChunk" 請(qǐng)求。在app.post()方法里我們做的***件事是檢查我們是否在處理POST請(qǐng)求。接下去檢查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;
- }
一旦我們成功通過(guò)了所有的檢查,我們將把上傳的文件塊作為一個(gè)單獨(dú)分開(kāi)的文件并將它按順序數(shù)字命名。下面最重要的代碼是調(diào)用fs.ensureDirSync()方法,它使用來(lái)檢查臨時(shí)目錄是否存在。如果目錄不存在則創(chuàng)建一個(gè)。注意我們使用的是該方法的同步版本。
- // 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);
- }
正如我之前提出的,我們可以通過(guò)兩種方式上傳文件到后端服務(wù)器。***種方式是在web瀏覽器中使用FormData,然后把文件塊作為二進(jìn)制數(shù)據(jù)發(fā)送,另一種方式是把文件塊轉(zhuǎn)換成base64編碼的字符串,然后創(chuàng)建一個(gè)手工的multipart/form-data encoded請(qǐng)求,然后發(fā)送到后端服務(wù)器。
所以我們需要檢查一下是否在上傳的是一個(gè)手工multipart/form-data encoded請(qǐng)求,通過(guò)檢查"CelerFT-Encoded"頭部信息,如果這個(gè)頭部存在,我們創(chuàng)建一個(gè)buffer并使用request的ondata時(shí)間把數(shù)據(jù)拷貝到buffer中。
在request的onend事件中通過(guò)將buffer呈現(xiàn)為字符串并按CRLF分開(kāi),從而從 multipart/form-data encoded請(qǐng)求中提取base64字符串。base64編碼的文件塊可以在數(shù)組的第四個(gè)索引中找到。
通過(guò)創(chuàng)建一個(gè)新的buffer來(lái)將base64編碼的數(shù)據(jù)重現(xiàn)轉(zhuǎn)換為二進(jìn)制。隨后調(diào)用fs.outputFileSync()方法將buffer寫(xiě)入文件中。
- // 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();
- });
- }
二進(jìn)制文件塊的上傳是通過(guò)formidable模塊來(lái)處理的。我們使用formidable.IncomingForm()方法得到multipart/form-data encoded請(qǐng)求。formidable模塊將把上傳的文件塊保存為一個(gè)單獨(dú)的文件并保存到臨時(shí)目錄。我們需要做的是在formidable的onend事件中將上傳的文件塊保存為里一個(gè)名字。
- 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()方法使用來(lái)處理"MergeAll"請(qǐng)求的。這個(gè)方法實(shí)現(xiàn)了之前描述過(guò)的功能。
- // 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并沒(méi)有提供String.padLeft()方法,這是通過(guò)擴(kuò)展String實(shí)現(xiàn)的。
- // String padding left code taken from
- // http://www.lm-tech.it/Blog/post/2012/12/01/String-Padding-in-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;
- } ;
#p#
一些其它事情
其中一件事是,發(fā)表上篇文章后我繼續(xù)研究是為了通過(guò)域名碎片實(shí)現(xiàn)并行上傳到CeleFT功能。域名碎片的原理是訪問(wèn)一個(gè)web站點(diǎn)時(shí),讓web瀏覽器建立更多的超過(guò)正常允許范圍的并發(fā)連接。 域名碎片可以通過(guò)使用不同的域名(如web1.cdxwcx.com,web2.cdxwcx.com)或者不同的端口號(hào)(如8000, 8001)托管web站點(diǎn)的方式實(shí)現(xiàn)。
示例中,我們使用不同端口號(hào)托管web站點(diǎn)的方式。
我們使用 iisnode 把 Node.js集成到 IIS( Microsoft Internet Information Services)實(shí)現(xiàn)這一點(diǎn)。 下載兼容你操作系統(tǒng)的版本 iisnode (x86) 或者 iisnode (x64)。 下載 IIS URL重寫(xiě)包。
一旦安裝完成(假定windows版Node.js已安裝),到IIS管理器中創(chuàng)建6個(gè)新網(wǎng)站。將***個(gè)網(wǎng)站命名為CelerFTJS并且將偵聽(tīng)端口配置為8000。
圖片7在IIS管理器中創(chuàng)建一個(gè)新網(wǎng)站
然后創(chuàng)建其他的網(wǎng)站。我為每一個(gè)網(wǎng)站都創(chuàng)建了一個(gè)應(yīng)用池,并且給應(yīng)用池“LocalSystem”級(jí)別的權(quán)限。所有網(wǎng)站的本地路徑是C:\inetpub\wwwroot\CelerFTNodeJS。
圖片8 文件夾層級(jí)
我在Release模式下編譯了Node.js應(yīng)用,然后我拷貝了server.js文件、Script文件夾以及node_modules文件夾到那個(gè)目錄下。
要讓包含 iisnode 的Node.js的應(yīng)用工作,我們需要?jiǎng)?chuàng)建一個(gè)web.config文件,并在其中添加如下得內(nèi)容。
web.config中各項(xiàng)的意思是讓iisnode處理所有得*.js文件,由server.js 處理任何匹配"/*"的URL。
圖片9 URL重寫(xiě)規(guī)則
如果你正確的做完了所有的工作,你就可以通過(guò)http://localhost:8000瀏覽網(wǎng)站,并進(jìn)入CelerFT "Default.html"頁(yè)面。
web.config文件被修改以支持如前面post中所解釋的大文件的上傳,這里我不會(huì)解釋所有的項(xiàng)。不過(guò)下面的web.config項(xiàng)可以改善 iisnode中Node.js的性能。
node_env="production" debuggingEnabled="false" devErrorsEnabled="false" nodeProcessCountPerApplication="0"&
本文名稱:使用HTTP上傳G級(jí)的文件之Node.js版本
文章出自:http://m.fisionsoft.com.cn/article/dhpjdid.html


咨詢
建站咨詢
