4
votes

I'm trying to generate a zip archive of icons on-the-fly and stream the response to the user to download, via a JSON POST request.

The zip itself is created and the response is returned, but the client-side is not prompted to download the file, and the response is garbled (which I assume is the contents of the archive).

app.post('/download', function(request, response) {

  var icons = request.body;
  var filename = 'icons.zip';

  response.attachment(filename);

  var zip = Archiver('zip');

  zip.on('finish', function(error) {
    return response.end();
  });

  zip.pipe(response);

  for (var i = 0; i < icons.length; i++) {

    var icon = getIcon(icons[i]);
    zip.append(fs.createReadStream('public/' + icon.svg), { name: icon.title + '.svg' });
  }

  zip.finalize();
});

I'm wondering if there's anything missing from the server-side code that's preventing the download on the client-side, but from the example I've followed (https://github.com/archiverjs/node-archiver/blob/master/examples/express.js), it doesn't seem to be the case.

Here's some screenshots of the request made and the response received:

Request and response headers

Response

1
How are you posting the data in the client? Given that it's JSON, I suspect through an AJAX call? In that case: AJAX doesn't do downloads. - robertklep
That's correct, I'm sending the JSON request through AJAX! Is there an alternative I can use? - Simon Finney
In the end, you need to make the client navigate to the download URL. You could convert it from a POST to a GET by passing the icon names in the query string (or as routing parameter), or dynamically create and submit a <form> client-side. - robertklep

1 Answers

1
votes

AJAX calls don't trigger file downloads in a browser, so you need to work around that.

One possibility is to change the request from a POST to a GET and put the names of the icons in the URL as parameters.

Your Express route would look like this:

app.get('/download/*?', function(request, response) {
  // Make sure icons names were provided:
  if (! request.params[0]) {
    return response.sendStatus(400);
  }

  // Split on `/`, which is used as separator between icon names:
  var icons = request.params[0].split(/\//);

  // The rest can be the same
  ...
});

Client-side, you would use this:

location.href = 'http://your-server/download/Chevron%20Down/Close/Trash';

(obviously you can also generate that URL dynamically based on user input, as long as you make sure that the icon names are properly URL-encoded)