a file upload progress bar with node (socket.io and formidable) and ajax

LubosB picture LubosB · Apr 18, 2014 · Viewed 7k times · Source

I was in the middle of teaching myself some Ajax, and this lesson required building a simple file upload form locally. I'm running XAMPP on windows 7, with a virtual host set up for http://test. The solution in the book was to use node and an almost unknown package called "multipart" which was supposed to parse the form data but was crapping out on me.

I looked for the best package for the job, and that seems to be formidable. It does the trick and my file will upload locally and I get all the details back through Ajax. BUT, it won't play nice with the simple JS code from the book which was to display the upload progress in a progress element. SO, I looked around and people suggested using socket.io to emit the progress info back to the client page.

I've managed to get formidable working locally, and I've managed to get socket.io working with some basic tutorials. Now, I can't for the life of me get them to work together. I can't even get a simple console log message to be sent back to my page from socket.io while formidable does its thing.

First, here is the file upload form by itself. The script inside the upload.html page:

document.getElementById("submit").onclick = handleButtonPress;
var httpRequest;

function handleResponse() {
    if (httpRequest.readyState == 4 && httpRequest.status == 200) {
        document.getElementById("results").innerHTML = httpRequest.responseText;
    }
}

function handleButtonPress(e) {
    e.preventDefault();

    var form = document.getElementById("myform");
    var formData = new FormData(form);

    httpRequest = new XMLHttpRequest();
    httpRequest.onreadystatechange = handleResponse;
    httpRequest.open("POST", form.action);
    httpRequest.send(formData);
}

And here's the corresponding node script (the important part being form.on('progress')

var http = require('http'),
    util = require('util'),
    formidable = require('formidable');

http.createServer(function(req, res) {
  if (req.url == '/upload' && req.method.toLowerCase() == 'post') {
    var form = new formidable.IncomingForm(),
        files = [],
        fields = [];

    form.uploadDir = './files/';
    form.keepExtensions = true;

    form
      .on('progress', function(bytesReceived, bytesExpected) {
        console.log('Progress so far: '+(bytesReceived / bytesExpected * 100).toFixed(0)+"%");
      })
      .on('file', function(name, file) {
        files.push([name, file]);
      })
      .on('error', function(err) {
        console.log('ERROR!');
        res.end();
      })
      .on('end', function() {
        console.log('-> upload done');
        res.writeHead(200, "OK", {
            "Content-Type": "text/html", "Access-Control-Allow-Origin": "http://test"
        });
        res.end('received files: '+util.inspect(files));
      });
    form.parse(req);
  } else {
    res.writeHead(404, {'content-type': 'text/plain'});
    res.end('404');
  }
  return;
}).listen(8080);

console.log('listening');

Ok, so that all works as expected. Now here's the simplest socket.io script which I'm hoping to infuse into the previous two to emit the progress info back to my page. Here's the client-side code:

var socket = io.connect('http://test:8080');

socket.on('news', function(data){
    console.log('server sent news:', data);
});

And here's the server-side node script:

var http = require('http'),
    fs = require('fs');

var server = http.createServer(function(req, res) {
    fs.createReadStream('./socket.html').pipe(res);
});

var io = require('socket.io').listen(server);

io.sockets.on('connection', function(socket) {
    socket.emit('news', {hello: "world"});
});

server.listen(8080);

So this works fine by itself, but my problem comes when I try to place the socket.io code inside my form.... I've tried placing it anywhere it might remotely make sense, i've tried the asynchronous mode of fs.readFile too, but it just wont send anything back to the client - meanwhile the file upload portion still works fine. Do I need to establish some sort of handshake between the two packages? Help me out here. I'm a front-end guy so I'm not too familiar with this back-end stuff. I'll put this aside for now and move onto other lessons.

Answer

Stofkn picture Stofkn · Apr 22, 2014

Maybe you can create a room for one single client and then broadcast the percentage to this room.

I explained it here: How to connect formidable file upload to socket.io in Node.js