NodeJS – 使用Core NodeJS和最初的Node解决scheme,通过进度条上传文件

Ryan Dahl曾经说过他发明了NodeJS来解决file upload进度条问题( https://youtu.be/SAc0vQCC6UQ )。 使用Node于2009年推出的技术时,所以在Express和更高级的客户端JavaScript库自动告诉你进度更新之前,NodeJS究竟是如何解决这个问题的呢?

现在试着只使用Core NodeJS,我理解了请求stream,我可以看看头文件,获得总文件大小,然后获取每个数据块的大小,告诉我完成百分比。 但是,我不明白如何将这些进度更新stream回到浏览器,因为浏览器似乎没有更新,直到request.end()。

我想再一次围绕NodeJS最初如何解决这个进度更新问题的问题进行讨论。 WebSocket还没有,所以你不能打开一个WebSocket连接到客户端,并将进度更新stream传回浏览器。 有没有使用另一个客户端JavaScript技术?

这是我迄今的尝试。 进度更新stream式传输到服务器端控制台,但浏览器仅在响应stream收到response.end()后才更新。

var http = require('http'); var fs = require('fs'); var server = http.createServer(function(request, response){ response.writeHead(200); if(request.method === 'GET'){ fs.createReadStream('filechooser.html').pipe(response); } else if(request.method === 'POST'){ var outputFile = fs.createWriteStream('output'); var total = request.headers['content-length']; var progress = 0; request.on('data', function(chunk){ progress += chunk.length; var perc = parseInt((progress/total)*100); console.log('percent complete: '+perc+'%\n'); response.write('percent complete: '+perc+'%\n'); }); request.pipe(outputFile); request.on('end', function(){ response.end('\nArchived File\n\n'); }); } }); server.listen(8080, function(){ console.log('Server is listening on 8080'); }); 

FileChooser.html的:

 <!DOCTYPE html> <html> <body> <form id="uploadForm" enctype="multipart/form-data" action="/" method="post"> <input type="file" id="upload" name="upload" /> <input type="submit" value="Submit"> </form> </body> </html> 

这是一个更新的尝试。 浏览器现在显示进度更新,但我敢肯定,这不是Ryan Dahl最初提出的生产scheme的实际解决scheme。 他用了很长时间的投票吗? 这个解决scheme是什么样的?

 var http = require('http'); var fs = require('fs'); var server = http.createServer(function(request, response){ response.setHeader('Content-Type', 'text/html; charset=UTF-8'); response.writeHead(200); if(request.method === 'GET'){ fs.createReadStream('filechooser.html').pipe(response); } else if(request.method === 'POST'){ var outputFile = fs.createWriteStream('UPLOADED_FILE'); var total = request.headers['content-length']; var progress = 0; response.write('STARTING UPLOAD'); console.log('\nSTARTING UPLOAD\n'); request.on('data', function(chunk){ fakeNetworkLatency(function() { outputFile.write(chunk); progress += chunk.length; var perc = parseInt((progress/total)*100); console.log('percent complete: '+perc+'%\n'); response.write('<p>percent complete: '+perc+'%'); }); }); request.on('end', function(){ fakeNetworkLatency(function() { outputFile.end(); response.end('<p>FILE UPLOADED!'); console.log('FILE UPLOADED\n'); }); }); } }); server.listen(8080, function(){ console.log('Server is listening on 8080'); }); var delay = 100; //delay of 100 ms per chunk var count =0; var fakeNetworkLatency = function(callback){ setTimeout(function() { callback(); }, delay*count++); }; 

首先,你的代码确实在工作。 节点发送分块的响应,但浏览器只是在等待更多的麻烦之前显示它。

Node文档中的更多信息:

第一次调用response.write()时,它会将caching的头信息和第一个正文发送给客户端。 第二次调用response.write(),Node假定你要传输数据,并分别发送。 也就是说,响应被缓冲到第一个主体。

如果将content-type设置为html,如response.setHeader('Content-Type', 'text/html; charset=UTF-8'); ,它使铬渲染的内容,但只有做了伎俩,当我用一系列的设置超时调用response.write调用里面; 当我尝试使用你的代码时,它仍然没有更新dom,所以我挖得更深。

麻烦的是,当浏览器看起来合适时,它真的需要渲染内容,所以我设置了代码来发送ajax请求来检查状态:

首先,我更新了服务器,将其状态存储在一个全局variables中,并打开一个“checkstatus”端点来读取它:

 var http = require('http'); var fs = require('fs'); var status = 0; var server = http.createServer(function (request, response) { response.writeHead(200); if (request.method === 'GET') { if (request.url === '/checkstatus') { response.end(status.toString()); return; } fs.createReadStream('filechooser.html').pipe(response); } else if (request.method === 'POST') { status = 0; var outputFile = fs.createWriteStream('output'); var total = request.headers['content-length']; var progress = 0; request.on('data', function (chunk) { progress += chunk.length; var perc = parseInt((progress / total) * 100); console.log('percent complete: ' + perc + '%\n'); status = perc; }); request.pipe(outputFile); request.on('end', function () { response.end('\nArchived File\n\n'); }); } }); server.listen(8080, function () { console.log('Server is listening on 8080'); }); 

然后,我更新了filechooser.html以检查ajax请求的状态:

 <!DOCTYPE html> <html> <body> <form id="uploadForm" enctype="multipart/form-data" action="/" method="post"> <input type="file" id="upload" name="upload"/> <input type="submit" value="Submit"> </form> Percent Complete: <span id="status">0</span>% </body> <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script> <script> var $status = $('#status'); /** * When the form is submitted, begin checking status periodically. * Note that this is NOT long-polling--that's when the server waits to respond until something changed. * In a prod env, I recommend using a websockets library with a long-polling fall-back for older broswers--socket.io is a gentleman's choice) */ $('form').on('submit', function() { var longPoll = setInterval(function () { $.get('/checkstatus').then(function (status) { $status.text(status); //when it's done, stop annoying the server if (parseInt(status) === 100) { clearInterval(longPoll); } }); }, 500); }); </script> </html> 

请注意,尽pipe我没有结束响应,但服务器仍然能够处理传入的状态请求。

所以为了回答你的问题,达尔被一个flickr应用程序所吸引,他看到上传了一个文件,并长时间查询它的状态。 他觉得这个理由是,服务器能够处理这些Ajax请求,同时继续上传。 这是多任务处理。 看到他正好在这个video 14分钟的时候谈论它 – 甚至说,“所以这是它是如何工作的…”。 几分钟后,他提到了iframe技术,并将长轮询和简单的ajax请求区分开来。 他表示,他想编写一个针对这些types的行为进行优化的服务器。

无论如何,这在当时是不常见的。 大多数networking服务器软件一次只能处理一个请求。 如果他们去了一个数据库,调用一个web服务,与文件系统交互,或者类似的东西,这个过程就会坐下来等待它完成,而不是在等待的时候处理其他请求。

如果您想要同时处理多个请求,则必须启动另一个线程或使用负载平衡器添加更多服务器。

另一方面,Nodejs通过执行非阻塞IO来非常有效地使用主进程。 节点并不是第一个这样做的,但是在非阻塞IO领域,它的区别在于它的所有缺省方法都是asynchronous的,你必须调用“sync”方法来做错误的事情。 这种做法强迫用户做正确的事情。

另外,应该注意的是,selectjavascript的原因是因为它已经是一个在事件循环中运行的语言; 它被用来处理asynchronous代码。 您可以拥有匿名函数和闭包,这使得asynchronous操作更容易维护。

我也想提一下,使用承诺库也使得编写asynchronous代码更加清洁。 例如,检查bluebirdjs – 它有一个很好的“promisify”方法,它将把具有callback签名(function(error,params){})的对象原型上的函数转换为返回promise。