Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node/Express Generate a one time route / link / download?

How would I go about creating a one time download link in nodeJS or Express?

I'm trying to find the simplest way to accomplish this. My ideas so far are:

Use fs stream to read and then delete the file or Somehow generate a link/route that gets removed once the download button is clicked

Are any of these implementations possible? Is there a simpler way?

Any help or example code would be greatly appreciated!

-Thanks

like image 403
gbachik Avatar asked Feb 24 '14 21:02

gbachik


2 Answers

Check this simple implementation:

You store the information of the download in a file. The filename is the download session id. The file content is the real path of the file to be downloaded.

Use these three functions to manage the lifecycle of the download sessions:

var fs     = require('fs');
var crypto = require('crypto');
var path   = require('path');

// Path where we store the download sessions
const DL_SESSION_FOLDER = '/var/download_sessions';

/* Creates a download session */
function createDownload(filePath, callback) {
  // Check the existence of DL_SESSION_FOLDER
  if (!fs.existsSync(DL_SESSION_FOLDER)) return callback(new Error('Session directory does not exist'));

  // Check the existence of the file
  if (!fs.existsSync(filePath)) return callback(new Error('File doest not exist'));

  // Generate the download sid (session id)
  var downloadSid = crypto.createHash('md5').update(Math.random().toString()).digest('hex');

  // Generate the download session filename
  var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');

  // Write the link of the file to the download session file
  fs.writeFile(dlSessionFileName, filePath, function(err) {
    if (err) return callback(err);

    // If succeeded, return the new download sid
    callback(null, downloadSid);
  });
}

/* Gets the download file path related to a download sid */
function getDownloadFilePath(downloadSid, callback) {
  // Get the download session file name
  var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');

  // Check if the download session exists
  if (!fs.existsSync(dlSessionFileName)) return callback(new Error('Download does not exist'));

  // Get the file path
  fs.readFile(dlSessionFileName, function(err, data) {
    if (err) return callback(err);

    // Return the file path
    callback(null, data);
  });
}

/* Deletes a download session */
function deleteDownload(downloadSid, callback) {
  // Get the download session file name
  var dlSessionFileName = path.join(DL_SESSION_FOLDER, downloadSid + '.download');

  // Check if the download session exists
  if (!fs.existsSync(dlSessionFileName)) return callback(new Error('Download does not exist'));

  // Delete the download session
  fs.unlink(dlSessionFileName, function(err) {
    if (err) return callback(err);

    // Return success (no error)
    callback();
  });
}

Use createDownload() to create download sessions wherever you need to. It returns the download sid, then you can use it to build your download URL like: http://your.server.com/download?sid=<RETURNED SID>.

Finally you can add a simple handler to your /download route:

app.get('/download', function(req, res, next) {
  // Get the download sid
  var downloadSid = req.query.sid;

  // Get the download file path
  getDownloadFilePath(downloadSid, function(err, path) {
    if (err) return res.end('Error');

    // Read and send the file here...

    // Finally, delete the download session to invalidate the link
    deleteDownload(downloadSid, function(err) {
      // ...
    });
  });
});

With this method, you don't have to create/move/delete big download files, which could cause slow responses and unnecessary resource consumption.

like image 62
karliwson Avatar answered Nov 16 '22 01:11

karliwson


You can delete routes from the app.routes object. See Remove route mappings in NodeJS Express for more info.

Here is my quick and not very well tested way of doing what you ask:

 var express = require('express');
 var app = express();

 app.get('/download', function(req,res,next){
    res.download('./path/to/your.file');

    //find this route and delete it.
    for(i = 0; i < app.routes.get.length; i++){
        if(app.routes.get[i].path === '/download'){
            app.routes.get.splice(i,1); 
        }
    }
});

app.listen(80);
like image 36
Ray Stantz Avatar answered Nov 16 '22 00:11

Ray Stantz