Ryan Dahl说他发明了NodeJS来解决文件上传进度条问题(https://youtu.be/SAc0vQCC6UQ)。使用2009年推出Node时可用的技术,因此在Express和更高级的客户端javascript库自动告诉您进度更新之前,NodeJS究竟是如何解决这个问题的呢?
现在尝试使用Core NodeJS,我理解请求流,我可以查看标题,获取总文件大小,然后获取每个数据块的大小,告诉我百分比完成。但后来我不明白如何将这些进度更新流回浏览器,因为浏览器似乎没有更新,直到request.end()。
我再次想要了解NodeJS最初如何解决此进度更新问题。 WebSockets尚未出现,因此您无法打开与客户端的WebSocket连接,并将进度更新流回浏览器。是否有另一种客户端javascript技术被使用?
到目前为止,这是我的尝试。进度更新将流式传输到服务器端控制台,但只有在响应流收到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最初为生产方案提出的实际解决方案。他是否使用长轮询?那个解决方案会是什么样的?
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++);
};
答案 0 :(得分:8)
首先,您的代码确实在工作;节点发送分块响应,但浏览器只是等待更多,然后再费心去显示它。
Node Documentation中的更多信息:
第一次调用response.write()时,它会发送缓冲的 标题信息和客户端的第一个主体。第二次 调用response.write(),Node假定你将要流式传输 数据,并单独发送。也就是说,响应被缓冲了 到了第一块身体。
如果你将内容类型设置为像response.setHeader('Content-Type', 'text/html; charset=UTF-8');
这样的html,它会使chrome渲染内容,但是当我使用一系列设置超时调用并且内部有response.write调用时,这只是诀窍;当我尝试使用你的代码时,它仍然没有更新dom,所以我挖得更深......
麻烦的是,浏览器真的应该在看到合适时呈现内容,所以我设置代码来发送ajax请求以检查状态:
首先,我更新了服务器,只是将其状态存储在一个全局变量中,并打开一个“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>
请注意,尽管我没有结束响应,但服务器仍然能够处理传入的状态请求。
所以为了回答你的问题,Dahl被一个flickr应用程序所吸引,他看到上传了一个文件并进行了长时间轮询以检查它的状态。他被处理的原因是服务器能够处理这些ajax请求,同时继续处理上传。这是多任务处理。看到他谈到this video正好14分钟 - 甚至说,“所以这就是它的运作方式......”。几分钟后,他提到了一种iframe技术,并将长轮询与简单的ajax请求区分开来。他声称他想编写一个针对这些行为进行优化的服务器。
无论如何,这在当时并不常见。大多数Web服务器软件一次只能处理一个请求。如果他们去了一个数据库,调用了一个web服务,与文件系统或类似的东西进行了交互,那么这个过程就会等待它完成而不是在等待时处理其他请求。
如果您想同时处理多个请求,则必须启动另一个线程或使用负载均衡器添加更多服务器。
另一方面,Nodejs通过执行非阻塞IO来非常有效地使用主进程。 Node并不是第一个这样做的,但是在非阻塞IO领域中它的区别在于它的所有默认方法都是异步的,你必须调用“sync”方法来执行错误的事情。这有点迫使用户做正确的事情。另外,应该注意,选择javascript的原因是因为它已经是一个在事件循环中运行的语言; make 处理异步代码。您可以拥有匿名函数和闭包,这使得异步操作更容易维护。
我还想提一下,使用promise库也可以使编写异步代码更加清晰。例如,检查bluebirdjs - 它有一个很好的“promisify”方法,它将转换具有回调签名(function(error,params){})的对象原型上的函数,而不是返回一个promise。 / p>
答案 1 :(得分:0)
Node 更擅长解决这个上传问题,因为它的单线程 事件循环。 http 事件处理程序中的代码可以轻松访问其他事件处理程序使用的内存。在传统的 Web 服务器环境中,主守护进程启动工作线程来处理请求。我可以想象,在传统的线程模型中,很难检查文件上传状态,因为客户端需要对服务器进行新调用,询问“文件进度是什么?”然后由一个完全独立的线程处理。该新线程现在需要与当前运行的上传线程进行通信。