keystonejs / keystone-classic

Node.js CMS and web app framework
http://v4.keystonejs.com
MIT License
14.64k stars 2.21k forks source link

Unable to access raw IncomingMessage from req. Busboy/Multiparty/Multer not working #4374

Open nickanna42 opened 7 years ago

nickanna42 commented 7 years ago

busboy, multiparty, multer, etc are not working. The issue seems to be that by the time they are passed the req stream, it has already been parsed. The goal is to have one route have access to the raw IncomingMessage stream.

./public/test-upload.html

<html>
    <body>
        <form name="sampleForm" enctype="multipart/form-data" action="/upload_api" method="post">
            <p>Method</p>
            <input type="text" name="method"><br>
            <p>Options</p>
            <input type="text" name="options"><br>
            <p>File</p>
            <input type="file" name="file_upload"><br><br>
            <input type="submit" value="Click to Send">
        </form>
    </body>
</html>

./routes/index.js

var keystone = require('keystone'),
    middleware = require('./middleware'),
    kbalerts = require('./middleware/kbAlerts'),
    importRoutes = keystone.importer(__dirname);

var routes = {
    views: importRoutes('./views'),
    api: importRoutes('./api')var bodyParser = require('body-parser');
}

exports = module.exports = function(app) {
    app.use(/\/\w*(?!upload\_api)$/, bodyParser.json({limit: '10mb'}));
    app.use(/\/\w*(?!upload\_api)$/, bodyParser.urlencoded({limit: '10mb', extended: true}));

    app.post('/upload_api', routes.api.temp);
}

./routes/api/temp.js

var stream = require('stream');
var http = require('http');
var Busboy = require('busboy');

exports=module.exports = function(req, res) {
    console.log(req.body);
    var busboy = new Busboy({
        'headers' : req.headers,
        'limits'  : {
            'fields'  : 3,
            'files'   : 1,
            'parts'   : 4,
        },
    });

    var formFields = {'file_upload' : null};

    busboy.on('field', function(fieldName, value, nameTrunc, valueTrunc, xferEnc, mimetype) {
        console.log('busboy got a field called ' + fieldName);
        formFields[feildName] = value;
    });

    busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
        console.log('Busboy got a file, fieldnamed ' + fieldname);
        formFields.file_upload = filename;
        file.pipe(writeableStreamObject);
    });

    busboy.on('finish', function() {
        console.log('done parsing form');
        res.json({'status':'OK', 'details':'all ok jumpmaster'});
    });

    req.pipe(busboy);
    console.log(formFields);
};

Expected behavior

From console:

undefined
busboy got a field called method
busboy got a field called options
Busboy got a file, fieldnamed file_upload
done parsing form
{'method': 'some content', 'options': 'some options', 'file_upload': 'somefile.txt'}    

Actual/Current behavior

From console

{method: 'some content', 'options': 'somecontent'}
done parsing form
{'file_upload': null}

Steps to reproduce the actual/current behavior

Environment

Software Version
Keystone 0.3.22
Node 6.10.2
fueokbq commented 7 years ago

did you solve this? i have the same question T T

nickanna42 commented 7 years ago

Fueokbq, my personal work around was to use the "bring your own express" option for keystone. I initiated my express instance, and injected my routing which required busboy to this express instance. I then feed the custom express instance to keystone when I start it. This ensures my routes which require a custom request parsing are not effected by any peculiarities in keystone.

aphotix commented 6 years ago

@fueokbq

We forked keystone and added an exception where multer is included in express (mount.js for v0.3.x and bindBodyParser.js for v4.x)

BusBoy was used on the excluded route for handling large streaming uploads (largest file uploaded was >100GB)

We also updated multer to 1.3.0 in the fork.

app.use(/\/((?!attachments\/upload).)*/, bodyParser.json(bodyParserParams));
bodyParserParams.extended = true;
app.use(/\/((?!attachments\/upload).)*/, bodyParser.urlencoded(bodyParserParams));
app.use(/\/((?!attachments\/upload).)*/, multer({
    includeEmptyFields: true,
}).any());