aheckmann / gridfs-stream

Easily stream files to and from MongoDB
MIT License
615 stars 120 forks source link

[Bug] Destroy method does not trigger Close event except the first stream! #153

Open hnguyen48206 opened 3 years ago

hnguyen48206 commented 3 years ago

Hi everyone, I'm new to Nodejs and MongoDB in general and currently trying to use GridFs as a file storage. My setup is basically using busboy to handle the multipart form-data and then check when the uploading file exceeds the pre-specified file size limit, then I will use destroy method to cancel the stream and using the close event to check if I need to remove uploaded chunks from gridfs. All works just fine for the very first time file upload but not from the second one onward. The destroy method is called but 'close' event is not which means I do not get the callback to see if chunks deletion is needed.

router.post('/uploadfile', function (req, res) {
    //Limit file size to 6MB only
    var busboy = new Busboy({
        headers: req.headers, limits: {
            fileSize: 6 * 1024 * 1024
        }
    });

    var limit_reach = false;

    busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
        console.log('got file', filename, mimetype, encoding);
        // If the file is larger than the set limit, then destroy the streaming
        file.on('limit', function () {
            // writeStream.emit('close')
            writeStream.destroy(new Error('Destroyed the stream cause File was too large'))
            limit_reach = true;
            res.status(500).send('Destroyed the stream cause File was too large');
        });

        var writeStream
        try {
            //Remove Vietnamese characters and spaces
            let nameAfterProcessed = removeAccents(filename).replace(/\s/g, "")
            console.log(nameAfterProcessed)

            writeStream = gfs.createWriteStream({
                filename: nameAfterProcessed,
                content_type: mimetype,
            });
        } catch (error) {
            console.log(error)
        }
        if (writeStream != null) {
            writeStream.on('error', (err) => {
                //All the info of the uploaded file has been return. Storing the fileID to your data model for later use. 
                console.log(err)
            });
            writeStream.on('close', (file) => {
                //All the info of the uploaded file has been return. Storing the fileID to your data model for later use. 
                console.log(file)
                //check if File has been sucessfully uploaded or the stream was canceled by any reason.
                if(limit_reach)
                {
                    //Delete all unessary chunks in grid fs chunks using fileID
                    deleteUnfinishedUploadedFile(file._id)
                }
            });
            file.pipe(writeStream);
        }
    }).on('finish', function () {
        // show a link to the uploaded file
        if (!limit_reach)
            res.status(200).send('uploaded successfully');
    });
    req.pipe(busboy);
});

My development environment (node version: 10.16.3):

{
  "name": "vietnamagro_backend",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "axios": "^0.21.1",
    "body-parser": "^1.19.0",
    "busboy": "^0.3.1",
    "cheerio": "^1.0.0-rc.6",
    "cors": "^2.8.5",
    "email-validator": "^2.0.4",
    "express": "^4.17.1",
    "firebase-admin": "^9.5.0",
    "gridfs-stream": "^1.1.1",
    "http-proxy-middleware": "^1.2.0",
    "mongodb": "^3.6.6",
    "node-cron": "^3.0.0",
    "nodemon": "^2.0.7",
    "socket.io": "^4.1.2",
    "toad-scheduler": "^1.3.0",
    "uuid": "^8.3.2"
  }
}
hnguyen48206 commented 3 years ago

For anyone who's having the same situation with what I mentioned above, here is my work-around at this moment though I'm not so certain why. In short, delaying the logic in _closeInternal method in writestream.js does the trick.

GridWriteStream.prototype._closeInternal = function (cb) {
    console.log('abc jhjkjhj')
    if (!this._opened) return;
    if (this._closing) return;
    this._closing = true;

    var self = this;
       // Here is where you want to delay with a settimeout
    setTimeout(() => {
        self._store.close(function (err, file) {
            self._closing = false;
            self._opened = false;
            if (err) return self._error(err);
            self.emit('close', file);

            if (cb) cb();
        });
    }, 2000);
}