javascript NodeJS - 使用 Core NodeJS 和原始 Node 解决方案上传带有进度条的文件
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31529013/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
NodeJS - file upload with progress bar using Core NodeJS and the original Node solution
提问by HelpMeStackOverflowMyOnlyHope
Ryan Dahl has said he invented NodeJS to solve the file upload progress bar problem (https://youtu.be/SAc0vQCC6UQ). Using technology available in 2009 when Node was introduced, so before Express and more advanced client-side javascript libraries that automagically tell you progress updates, how exactly did NodeJS solve this problem?
Ryan Dahl 曾表示他发明了 NodeJS 来解决文件上传进度条问题(https://youtu.be/SAc0vQCC6UQ)。使用 2009 年引入 Node 时可用的技术,所以在 Express 和更高级的客户端 javascript 库会自动告诉您进度更新之前,NodeJS 究竟是如何解决这个问题的?
Trying to use just Core NodeJS now, I understand with the request stream I can look at the header, get the total file size, and then get the size of each chunk of data as it comes through, to tell me the percent complete. But then I don't understand how to stream those progress updates back to the browser, since the browser doesn't seem to update until request.end().
现在尝试仅使用 Core NodeJS,我了解请求流,我可以查看标头,获取总文件大小,然后获取通过的每个数据块的大小,告诉我完成的百分比。但是后来我不明白如何将这些进度更新流式传输回浏览器,因为浏览器似乎直到 request.end() 才会更新。
Once again I want to wrap my ahead around how NodeJS originally solved this progress update problem. WebSockets weren't around yet, so you couldn't just open a WebSocket connection to the client and stream the progress updates back to the browser. Was there another client-side javascript technology that was used?
我想再次围绕 NodeJS 最初是如何解决这个进度更新问题的。WebSockets 还没有出现,所以你不能只是打开一个到客户端的 WebSocket 连接并将进度更新流回浏览器。是否使用了另一种客户端 javascript 技术?
Here is my attempt so far. Progress updates are streamed to the server-side console, but the browser only updates once the response stream receives response.end().
到目前为止,这是我的尝试。进度更新流式传输到服务器端控制台,但浏览器仅在响应流收到 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:
文件选择器.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>
Here is an Updated attempt.The browser now displays progress updates, but I'm pretty sure this isn't the actual solution Ryan Dahl originally came up with for a production scenario. Did he use long polling? What would that solution look like?
这是更新的尝试。浏览器现在显示进度更新,但我很确定这不是 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++);
};
采纳答案by JohnnyFun
Firstly, your code is indeed working; node sends chunked responses, but the browser is simply waiting for more before bothering to show it.
首先,您的代码确实有效;node 发送分块响应,但浏览器只是在等待更多,然后才显示它。
More info in Node Documentation:
节点文档中的更多信息:
The first time response.write() is called, it will send the buffered header information and the first body to the client. The second time response.write() is called, Node assumes you're going to be streaming data, and sends that separately. That is, the response is buffered up to the first chunk of body.
第一次调用 response.write() 时,它会将缓冲的头信息和第一个正文发送给客户端。第二次调用 response.write() 时,Node 假设您要流式传输数据,并单独发送。也就是说,响应被缓冲到正文的第一个块。
If you set content-type to html like response.setHeader('Content-Type', 'text/html; charset=UTF-8');
, it makes chrome render the content, but that only did the trick when I used a series of set timeout calls with response.write calls inside; it still didn't update the dom when I tried with your code, so I dug deeper...
如果您将 content-type 设置为 html like response.setHeader('Content-Type', 'text/html; charset=UTF-8');
,它会使 chrome 呈现内容,但是只有当我在内部使用一系列设置超时调用和 response.write 调用时才起作用;当我尝试使用您的代码时,它仍然没有更新 dom,所以我进行了更深入的挖掘...
The trouble is that it's really up to the browser to render content when it sees fit, so I set up code to send ajax requests to check status instead:
问题在于,当它认为合适时呈现内容实际上取决于浏览器,所以我设置了代码来发送 ajax 请求来检查状态:
Firstly, I updated the server to simply store its status in a global variable and open a "checkstatus" endpoint to read it:
首先,我更新了服务器以简单地将其状态存储在一个全局变量中并打开一个“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');
});
Then, I updated the filechooser.html to check the status with ajax requests:
然后,我更新了 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>
Note that despite me not ending the response, the server is still able to handle incoming status requests.
请注意,尽管我没有结束响应,服务器仍然能够处理传入的状态请求。
So to answer your question, Dahl was facinated by a flickr app he saw that uploaded a file and long-polled to check it's status. The reason he was facinated was that the server was able to handle those ajax requests while it continued to work on the upload. It was multi-tasking. See him talk about it exactly 14 minutes into this video--even says, "So here's how it works...". A few minutes later, he mentions an iframe technique and also differentiates long-polling from simple ajax requests. He states that he wanted to write a server that was optimized for these types of behavior.
因此,为了回答您的问题,Dahl 被一个 flickr 应用程序所吸引,他看到该应用程序上传了一个文件并经过长时间轮询以检查其状态。他着迷的原因是服务器能够在继续处理上传的同时处理这些 ajax 请求。这是多任务处理。看到他在这个视频的第14 分钟谈论它——甚至说,“所以这就是它的工作原理......”。几分钟后,他提到了一种 iframe 技术,并将长轮询与简单的 ajax 请求区分开来。他说他想编写一个针对这些类型的行为进行了优化的服务器。
Anyway, this was un-common in those days. Most web server software would only handle one request at a time. And if they went to a database, called out to a webservice, interacted with the filesystem, or anything like that, the process would just sit and wait for it to finish instead of handling other requests while it waited.
无论如何,这在那个年代并不常见。大多数网络服务器软件一次只能处理一个请求。如果他们访问数据库、调用 Web 服务、与文件系统交互或任何类似的事情,进程将只是坐等它完成,而不是在等待时处理其他请求。
If you wanted to handle multiple requests concurrently, you'd have to fire up another thread or add more servers with a load balancer.
如果您想同时处理多个请求,则必须启动另一个线程或使用负载平衡器添加更多服务器。
Nodejs, on the other hand, makes very efficient use of the main process by doing non-blocking IO. Node wasn't the first to do this, but what sets it apart in the non-blocking IO realm is that all its default methods are asynchronous and you have to call a "sync" method to do the wrongthing. It kind of forces users to do the rightthing.
另一方面,Nodejs 通过执行非阻塞 IO 来非常有效地利用主进程。Node 不是第一个这样做的,但它在非阻塞 IO 领域的区别在于它的所有默认方法都是异步的,你必须调用“同步”方法来做错误的事情。它有点强迫用户做正确的事情。
Also, it should be noted, the reason javascript was chosen was because it is already a language that is running in an event-loop; it was madeto handle asynchronous code. You can have anonymous functions and closures, which makes async actions much easier to maintain.
另外,应该注意的是,选择 javascript 的原因是因为它已经是一种在事件循环中运行的语言;有人提出处理异步代码。您可以使用匿名函数和闭包,这使得异步操作更易于维护。
I also want to mention that using a promise library also makes writing async code much cleaner. For instance, check out bluebirdjs--it has a nice "promisify" method that will convert functions on an object's prototype that have the callback signature (function(error, params){}) to instead return a promise.
我还想提一下,使用 Promise 库还可以使编写异步代码更加简洁。例如,查看bluebirdjs——它有一个很好的“promisify”方法,它可以将具有回调签名 (function(error, params){}) 的对象原型上的函数转换为返回一个 promise。