multer: file is not removed if upload is canceled

Consider this minimal working example (Multer v1.1.0 on Windows 7, OS X 10.11 and Ubuntu 14.04 with node v4.2.1):

var express = require('express')
var multer  = require('multer')
var upload = multer({ dest: 'uploads/' })
var app = express();

app.post("/upload", upload.single("file"), function(req, res, next){
    res.end("uploaded to: " + req.file.filename);
});
app.listen(80);

and the following test script

# create large test file
$ dd if=/dev/urandom bs=2G count=1 of=upload
# upload
$ curl -F "file=@upload" http://localhost/upload
# upload again, but cancel before upload is finished
timeout -s SIGKILL 2s curl -F "file=@upload" http://localhost/upload

Expected behavior: Only the first file is persisted, the second is removed after the upload got canceled.

Actual behaviour: Both files are stored in the uploads folder

About this issue

  • Original URL
  • State: open
  • Created 9 years ago
  • Reactions: 6
  • Comments: 40 (1 by maintainers)

Commits related to this issue

Most upvoted comments

Here is my approach worked for me

filename: (req, file, cb) => {
    const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
    const fileName =
      file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname);
    cb(null, fileName);    
    req.on('aborted', () => {
      const fullFilePath = path.join('uploads', 'videos', fileName);
      file.stream.on('end', () => {
        fs.unlink(fullFilePath, (err) => {
          console.log(fullFilePath);
          if (err) {
            throw err;
          }
        });
      });
      file.stream.emit('end');
    })
  }

I thought perhaps the error handling code from the README was the solution, but it seems the callback is not called if the request is aborted.

This post might help.

EDIT : This is what I came up with :

const upload = multer({dest: __dirname + '/../../resource/'}).single('file')
app.post('/upload', (req, res) => {

    req.on('close', () => {
        console.error('req aborted by client')
        // delete most recent file ?
    })

    upload(req, res, () => {
        // do normal stuff
    })
})

We know when if the upload gets canceled. However the partially uploaded file still can’t be deleted, since only multer knows its name ; deleting the most recent file could be a temporary solution.

@Ealhad You can get its name by storage. And req has Event aborted:

var multer = require('multer');
var tmpDir = require('os').tmpdir();
var path = require('path');

var storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, tmpDir);
  },
  filename: function (req, file, cb) {
    req._originalname = file.originalname;
    cb(null, req._originalname);
  }
})

var upload = multer({storage}).single('file');

router.put('*',  function(req, res, next){

  req.on('aborted', () => {
    console.error('req aborted by client', path.join(tmpDir, req._originalname))
  })

  upload(req, res, function(err){
    if(err){
      return next(err);
    }
    res.end('ok');
  })
});

@adskjohn you were close, really close!. What I did is only modify the “disk.js” file adding these lines after the “finish” stream event:

outStream.on('finish', function () {
  cb(null, {
    destination: destination,
    filename: filename,
    path: finalPath,
    size: outStream.bytesWritten
  })
})
// The new code....
req.on('close', function() {
  outStream.close();
  fs.unlink(finalPath, cb);
})

This should solve the issue 😄

Came across this issue today, nice to see that a 4 year old issue like this is unresolved. I think it’s poor form to have ‘fixed’ it in the next version when the next version is many years in the making.

//saveTo is the path where the file is going to be saved to //writeStream=fs.createWriteStream(saveTo); req.on(“aborted”,()=>{ writeStream.close(); fs.unlinkSync(saveTo); }); This worked for me using busboy

@jonchurch I see you’ve assigned this to yourself. Have you had a chance to address it? I, for one, am anxiously awaiting the resolution. Shall I create a PR?

@yuwanlin had the right approach, I believe, to causing busboy to close the file handles. Just adding his first code block is sufficient to close this issue.

Insert at line 97 of make-middleware.js

req.on('close', function() { busboy.emit('finish'); })

Once that is done one can use a solution such as @ifree92 suggests to delete the file. That part doesn’t need to be part of the fix, although I do hope that version 2 will address the whole problem internally. It is a reasonable assumption that a partial file from an aborted upload should be deleted.

In the meantime, anyone else needing this patch can use: "multer": "kyleerik/multer#kyleerik-patch-1" … in their package.json file.

Well, got the same problem. Another solution would be adding a secondary array to the Request object:

const formdata = multer({
    storage: multer.diskStorage({
        destination: (req, file, cb) => cb(null, "/my/path"),
        filename(req, file, cb) {
            const name = generateName();
            req.fileNames.push(name);
            cb(null, name);
        }
    })
}).any();

module.exports = (req, res, next) => {

    // Holds custom-filenames
    req.fileNames = [];

    // Listen if request has been aborted
    req.on('aborted', () => {
        if (req.fileNames) {
            for (const fileName of req.fileNames) {
                // Delete files
            }
        }
    });

    formdata(req, res, next);
};