nelson687
nelson687

Reputation: 4513

busboy - is there a way to send the response when all files have been uploaded?

I'm trying to upload files to a server using node.js as backend and angular.js as frontend. I'm using express 4 + busboy for this. I have a table in the frontend where I should display all the files I'm uploading. So if I have 3 files and click on upload, angular should post these files to node.js and after getting the response back, refresh the table with those three files. This is the function I'm using in angular:

function uploadFiles(files){
    var fd = new FormData();
    for(var i = 0; i<files.length; i++){
        fd.append("file", files[i]);
    }
    $http.post('http://localhost:3000/upload', fd, {
        withCredentials: false,
        headers: {'Content-Type': undefined },
        transformRequest: angular.identity
    }).success(refreshTable()).error(function(){
        console.log("error uploading");
    });
}

and this is from node.js:

app.post('/upload', function(req, res) {
  var busboy = new Busboy({ headers: req.headers });
  busboy.on('file', function (fieldname, file, filename) {
    console.log("Uploading: " + filename);
    var fstream = fs.createWriteStream('./files/' + filename);
    file.pipe(fstream);
  });
  busboy.on('finish', function(){
    res.writeHead(200, { 'Connection': 'close' });
    res.end("");
  });
  return req.pipe(busboy);
});

the problem is that if I upload three files, as soon as the first file has been uploaded node.js sends the response and hence the table is updated only with the first file uploaded, if I refresh the page, the rest of the files appear. I think the problem is with this line in node: return req.pipe(busboy); if I remove that line, the post response keeps on pending for a long time and nothing happens, I think this is an async problem, anybody knows if there's a way to send the response back only when all files have been uploaded? thanks

Upvotes: 5

Views: 7871

Answers (1)

mscdex
mscdex

Reputation: 106736

A simple and common solution to this particular problem is to use a counter variable and listening for the finish event on the fs Writable stream. For example:

app.post('/upload', function(req, res) {
  var busboy = new Busboy({ headers: req.headers });
  var files = 0, finished = false;
  busboy.on('file', function (fieldname, file, filename) {
    console.log("Uploading: " + filename);
    ++files;
    var fstream = fs.createWriteStream('./files/' + filename);
    fstream.on('finish', function() {
      if (--files === 0 && finished) {
        res.writeHead(200, { 'Connection': 'close' });
        res.end("");
      }
    });
    file.pipe(fstream);
  });
  busboy.on('finish', function() {
    finished = true;
  });
  return req.pipe(busboy);
});

The reason for this is that busboy's finish event is emitted once the entire request has been fully processed, that includes files. However, there is some delay between when there is no more data to write to a particular file and when the OS/node has flushed its internal buffers to disk (and the closing of the file descriptor). Listening for the finish event for a fs Writable stream lets you know that the file descriptor has been closed and no more writes are going to occur.

Upvotes: 10

Related Questions