javascript - NodeJS - file upload with progress bar using Core NodeJS and the original Node solution -


ryan dahl has said invented nodejs solve file upload progress bar problem (https://youtu.be/sac0vqcc6uq). using technology available in 2009 when node introduced, before express , more advanced client-side javascript libraries automagically tell progress updates, how did nodejs solve problem?

trying use core nodejs now, understand request stream can @ header, total file size, , size of each chunk of data comes through, tell me percent complete. don't understand how stream progress updates browser, since browser doesn't seem update until request.end().

once again want wrap ahead around how nodejs solved progress update problem. websockets weren't around yet, couldn't open websocket connection client , stream progress updates browser. there client-side javascript technology used?

here attempt far. progress updates streamed server-side console, browser updates once response stream receives 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 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> 

here updated attempt. browser displays progress updates, i'm pretty sure isn't actual solution ryan dahl came production scenario. did use long polling? solution like?

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 listening on 8080'); });  var delay = 100; //delay of 100 ms per chunk var count =0; var fakenetworklatency = function(callback){     settimeout(function() {         callback();     }, delay*count++); }; 

firstly, code indeed working; node sends chunked responses, browser waiting more before bothering show it.

more info in node documentation:

the first time response.write() called, send buffered header information , first body client. second time response.write() called, node assumes you're going streaming data, , sends separately. is, response buffered first chunk of body.

if set content-type html response.setheader('content-type', 'text/html; charset=utf-8');, makes chrome render content, did trick when used series of set timeout calls response.write calls inside; still didn't update dom when tried code, dug deeper...

the trouble it's browser render content when sees fit, set code send ajax requests check status instead:

firstly, updated server store status in global variable , open "checkstatus" endpoint read it:

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 listening on 8080'); }); 

then, updated filechooser.html check status ajax requests:

<!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 form submitted, begin checking status periodically.      * note not long-polling--that's when server waits respond until changed.       * in prod env, recommend using websockets library long-polling fall-back older broswers--socket.io 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 server                 if (parseint(status) === 100) {                     clearinterval(longpoll);                 }             });         }, 500);     }); </script> </html> 

note despite me not ending response, server still able handle incoming status requests.

so answer question, dahl facinated flickr app saw uploaded file , long-polled check it's status. reason facinated server able handle ajax requests while continued work on upload. multi-tasking. see him talk 14 minutes this video--even says, "so here's how works...". few minutes later, mentions iframe technique , differentiates long-polling simple ajax requests. states wanted write server optimized these types of behavior.

anyway, un-common in days. web server software handle 1 request @ time. , if went database, called out webservice, interacted filesystem, or that, process sit , wait finish instead of handling other requests while waited.

if wanted handle multiple requests concurrently, you'd have fire thread or add more servers load balancer.

nodejs, on other hand, makes efficient use of main process doing non-blocking io. node wasn't first this, sets apart in non-blocking io realm default methods asynchronous , have call "sync" method wrong thing. kind of forces users right thing.

also, should noted, reason javascript chosen because language running in event-loop; made handle asynchronous code. can have anonymous functions , closures, makes async actions easier maintain.

i want mention using promise library makes writing async code cleaner. instance, check out bluebirdjs--it has nice "promisify" method convert functions on object's prototype have callback signature (function(error, params){}) instead return promise.


Comments