coolaj86 / formaline

formaline is a module for handling form requests ( HTTP POSTs / PUTs ) and for fast parsing of file uploads.
Apache License 2.0
239 stars 16 forks source link

( expressjs ) Failed to parse request data . Request was empty or already completed #25

Closed HugoMag closed 11 years ago

HugoMag commented 13 years ago

Hi,

I'm trying to use formaline with the jquery File-Upload plugin https://github.com/blueimp/jQuery-File-Upload but I always keep getting the error "Failed to parse request data . Request was empty or already completed". I can see in Firebug that the request is made as "multipart/form-data" and that the files are sent. I also see that the files dir is created.

Can you help me?

Here is the code:

jade file:

div#fileupload form(action='/upload/', multipart=true, enctype="multipart/form-data" ) div.fileupload-buttonbar label.fileinput-button span Add files... input(id="file_upload", name="files[]", type="file", multiple) button.start(type="submit") Start upload button.cancel(type="submit") Cancel upload button.delete(type="submit") Delete files

        div.fileupload-content
            table.files
            div.fileupload-progressbar

albums.js:

function add_album_image(req, res, next) {

var tmp_dir = node_dir + '/tmp/';

var album_dir = req.album.id;

var receivedFields = {},
    form = null,
    currFile = null,
    config = {
    holdFilesExtensions : !false,
    uploadRootDir: tmp_dir,
    getSessionID : function ( req ) {
        return album_dir;
    },

    requestTimeOut : 5000, // 5 secs
    resumeRequestOnError : true,
        // minimum factor value is 2 
    emitProgress : false,
    uploadThreshold : 1024 * 1024 * 1024 ,
    serialzedFieldThreshold : 10 * 1024 * 1024,
    maxFileSize : 1024 * 1024 * 1024, 
    checkContentLength : false,
    removeIncompleteFiles : false,
    logging : 'debug:on,1:on,2:off,3:off,4:off,console:on,file:off,record:off', // <-- turn off 2nd level to see only warnings, and parser overall results

        // listeners
    listeners : {
        'message' : function ( json ) {
        },
        'error' : function ( json ) { // json:{ type: '..', isupload: true/false , msg: '..', fatal: true/false }
        },
        'abort' : function ( json ) {   
        },
        'timeout' : function ( json ) {   
        },
        'loadstart' : function ( json ){
        },
        'fileprogress' : function ( json, payload ) { 
            // json is the same for 'load' event ( when a file was received, see Readme ) , 
            // 'payload' is a binary Buffer
            // you can direct the data payload to another stream, while the file is being uploaded
            /** /
            if( currFile === null ) {
              currFile = new fs.WriteStream( json.value.path + '*' );
            }
            currFile.write( payload );
            /**/
        },
        'progress' : function ( json ) {                              
        },
        'load' : function ( json ){
            currFile = null;
        },
        'loadend' : function ( json, res, cback ) {
            console.log( '\n\033[1;32mPost Done..\033[0m' );
            // log( '\n JSON -> \n', json, '\n' );
            res.writeHead( 200, { 'content-type' : 'text/plain' } );
            res.write( '-> ' + new Date() + '\n' );
            res.write( '-> request processed! \n' );   
            res.write( '\n-> stats -> ' + JSON.stringify( json.stats, null, 4 ) + '\n' );
            res.write( '\n Initial Configuration : ' + JSON.stringify( form.initialConfig, function ( key, value ) {
                if ( typeof value === 'function' ) {
                    return '..';
                } 
                return value;
            }, 4 ) + '\n' );

            res.write( '\n-> fields received: [ { .. } , { .. } ] \n   ****************\n' + JSON.stringify( json.fields, null, 1 ) + '\n' );
            res.write( '\n-> files written: [ { .. } , { .. } ] \n   **************\n ' + JSON.stringify( json.files, null, 1 ) + '\n' );
            if ( form.removeIncompleteFiles ) {
                res.write( '\n-> partially written ( removed ): [ { .. } , { .. } ] \n   *****************\n'+ JSON.stringify( json.incomplete, null, 1 ) + '\n' );
            } else {
                if ( json.incomplete.length !== 0 ) {
                    res.write( '\n-> partially written ( not removed ): \n   *****************\n' + JSON.stringify( json.incomplete, null, 1 ) + '\n' );
                }
            }
            res.end();
            try {
                cback();
            } catch ( err ) {
                console.log( 'error', err.stack );
            }
        }
    }

}; //end config obj
console.log( ' -> req url :', req.url );
form = new formaline( config ) ;
form.parse( req, res, function () {
    console.log("parse");
});

}

Thanks. Best regards, Hugo

HugoMag commented 13 years ago

Hello again,

I've launched the simple example has a standalone server and its working.

I've also tried to use the simple example inside a page in my server but it does not work. From what I gathered this happens when I use connect-mongo as the session store. Here is the code to reproduce the error:

var http = require( 'http' ),
    formaline = require( 'formaline' ),
    connect = require( 'connect' ),
    fs = require( 'fs' ),
    log = console.log,
    dir =  '/tmp/';

var express = require('express')
    extras = require('express-extras'),
    path = require('path'),
    mongoose = require('mongoose'),
    url = require('url'),
    util = require('util'),
    DEBUG = true;

var app = express.createServer();

//save sessions in mongodb.
var MongoStore = require('connect-mongo');
var sessionStore = new MongoStore({db: "app" });

// Configuration
app.configure(function(){
    app.use(express.methodOverride());
    app.use(express.bodyParser());
    app.use(express.cookieParser());
    app.use(express.session({ secret: 'aaa', store: sessionStore}));
    app.use(app.router);
    app.set('view engine', 'jade');
});

app.configure('development', function(){
    app.use(express.static(__dirname + '/public'));
    app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});

app.get('/test/', function (req, res, next) {
    getHtmlForm(req, res, next);
});

app.post('/test/post', function (req, res, next) {
    handleFormRequest(req, res, next);
});

app.post('/test/upload', function (req, res, next) {
    handleFormRequest(req, res, next);
});

getHtmlForm = function ( req, res, next ) {
        if ( req.url === '/test/' ) {
        log( ' -> req url :', req.url );
        res.writeHead( 200, { 'content-type' : 'text/html' } );
        res.end( '<html><head>\
                 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> \
                 </head><body>\
                 <style type="text/css">\
                 label,input { display: block; width: 236px; float: left; margin: 2px 4px 4px 4px; }\
                 label { text-align: center; width: 110px; color: #444; background-color: #f0f0f0; border: 1px solid #a0a0a0; padding: 1px; font-size: 14px; }\
                 form { margin-bottom: 10px; border: 1px solid #b0b0b0; padding: 16px; height: 200px;}\
                 form#mufile{ width: 380px; } form#simple{ width: 380px; } form#mframe{ width: 380px; }\
                 br { clear: left;}\
                 </style>\
                 <br/>\
                 <b>Simple Url Encoded Post:</b><br/><br/>\
                 <form id="simple" action="/test/post" method="post">\
                 <label for="sfield1">simplefield1</label> <input type="text" name="simplefield1" id="sfield1"/><br/>\
                 <label for="sfield2">simplefield2</label> <input type="text" name="simplefield2" id="sfield2"/><br/>\
                 <label for="sfield3">simplefield2</label> <input type="text" name="simplefield2" id="sfield3"/><br/>\
                 <label for="sfield4">simplefield3</label> <input type="text" name="simplefield3" id="sfield4"/><br/>\
                 <label for="sfield5">simplefield3</label> <input type="text" name="simplefield3" id="sfield5"/><br/>\
                 <label for="sfield6">simplefield3</label> <input type="text" name="simplefield3" id="sfield6"/><br/>\
                 <input type="submit" value="Submit" id="submit1">\
                 </form><br/>\
                 <b>Multiple File Upload:</b><br/><br/>\
                 <form id="mufile" action="/test/upload" enctype="multipart/form-data" method="post">\
                 <label for="id1">demotitle1</label> <input type="text" name="demotitle1" id="id1"/><br/>\
                 <label for="id2">multiplefield1</label> <input type="file" name="multiplefield1" multiple="multiple" id="id2"><br/>\
                 <label for="id3">demotitle2</label> <input type="text" name="demotitle2" id="id3"><br/>\
                 <label for="id4">multiplefield2</label> <input type="file" name="multiplefield2" multiple="multiple" id="id4"><br/>\
                 <label for="id5">demotitle3</label> <input type="text" name="demotitle3" id="id5"><br/>\
                 <label for="id6">multiplefield2</label> <input type="file" name="multiplefield3" multiple="multiple" id="id6"><br/>\
                 <input type="submit" value="Upload" id="upload1"/>\
                 </form><br/>\
                 <b>Iframe Multiple File Upload:</b><br/><br/>\
                 <form id="mframe" action="/test/upload" method="post" enctype="multipart/form-data" target="iframe" >\
                 <label for="ffield1">iframefield1</label> <input type="text" name="iframefield1" id="ffield1"/><br/>\
                 <label for="ffield2">iframefile1</label> <input type="file" name="iframefile1" multiple  src="" frameborder="1" id="ffield2"/><br/>\
                 <label for="ffield3">iframefield2</label> <input type="text" name="iframefield2" id="ffield3"/><br/>\
                 <label for="ffield4">iframefile2</label> <input type="file" name="iframefile2" multiple  src="" frameborder="1" id="ffield4"/><br/>\
                 <label for="ffield5">iframefield2</label> <input type="text" name="iframefield2" id="ffield5"/><br/>\
                 <label for="ffield6">iframefile2</label> <input type="file" name="iframefile2" multiple  src="" frameborder="1" id="ffield6"/><br/>\
                 <input type="submit" value="Upload" id="upload2"/>\
                 </form>\
                 <iframe name="iframe" width="100%" height="600px"></iframe>\
                 </form>\
                 </body></html>'
            );
        } else {
            next();
        }
    },
handleFormRequest = function ( req, res, next ) {
        var receivedFields = {},
            form = null,
            currFile = null,
            config = {

                    // default is false -->

                holdFilesExtensions : !false,

                    // specify a path, with at least a trailing slash
                    // default is /tmp/ -->
                uploadRootDir : dir,

                    // default is false
                    // to create and check directories existence in the sync way
                mkDirSync : false,

                    // retrieve session ID for creating unique upload directory for authenticated users
                    // the upload directory gets its name from the returned session identifier,
                    // and will remain the same across multiple posts ( for the authenticated user with this session identifier )
                    // this function have to return the request property that holds session id 
                    // the returned session id param, must contain a String, not a function or an object 
                    // the function takes http request as a parameter at run-time 

                getSessionID : function ( req ) {
                    return "ola";
                },

                    // default is 120000 milliseconds ( default nodeJS timeout for connection requests )
                    // the client connection is closed after the specified milliseconds ( minimum is 100 millisecs )
                requestTimeOut : 5000, // 5 secs

                    // default is true
                    // when a fatal exception was thrown, the client request is resumed instead of immediately emitting 'loadend' event
                    // if false, the client request will be never resumed, the 'loadend' event will be emitted and the module doesn't handle the request anymore  
                resumeRequestOnError : true,

                    // default is false
                    // return sha1 digests for files received?
                    // turn off for better perfomances
                sha1sum : false,

                    // switch on/off 'fileprogress' event
                    // default is false
                    // it serves to monitor the progress of the file upload
                    // and also to move the data to another stream, while the file is being uploaded 
                emitFileProgress : false,

                    // switch on/off 'progress' event
                    // default is false, or integer chunk factor, 
                    // every n chunk emits a dataprogress event:  1 + ( 0 * n ) 1 + ( 1 * n ), 1 + ( 2 * n ), 1 + ( 3 * n ), 
                    // minimum factor value is 2 
                emitProgress : false, // 3, 10, 100

                    // max bytes allowed for file uploads ( multipart/form-data ), it is a writing threshold, this is the max size of bytes written to disk before stopping
                uploadThreshold : 1024 * 1024 * 1024 ,// bytes

                    // max bytes allowed for serialized fields, it limits the parsing of data received with serialized fields ( x-www-urlencoded ) 
                    // when it was exceeded, no data was returned 
                serialzedFieldThreshold : 1024 * 1024 * 1024,

                    // max bytes allowed for a single file
                maxFileSize : 1024 * 1024 * 1024, // bytes, default 1GB

                    // default is false, bypass content-length header value ( it must be present, otherwise an 'error'->'header' will be emitted ), 
                    // also if it exceeds max allowable bytes; the module continues to write to disk until |uploadThreshold| bytes are written. 
                    // if true ->  when headers content length exceeds uploadThreshold, module stops to receive data
                checkContentLength : false,

                    // default is false
                    // remove file not completed due to uploadThreshold, 
                    // if true formaline emit fileremoved event, 
                    // otherwise return a path array of incomplete files 
                removeIncompleteFiles : false,

                    // default is 'debug:off,1:on,2:on,3:off,4:off,console:on,file:off,record:off';
                    // enable various logging levels
                    // it is possible to switch on/off one or more levels at the same time
                    // debug: 'off' turn off logging
                    // file: 'on' --> create a log file in the current upload directory with the same name and .log extension
                    // console: 'off' --> disable console log output 
                    // record: 'on' --> record binary data from client request
                    // See the Readme!
                logging : 'debug:on,1:on,2:off,3:off,4:off,console:on,file:off,record:off', // <-- turn off 2nd level to see only warnings, and parser overall results

                    // listeners
                listeners : {
                    'message' : function ( json ) {
                    },
                    'error' : function ( json ) { // json:{ type: '..', isupload: true/false , msg: '..', fatal: true/false }
                    },
                    'abort' : function ( json ) {   
                    },
                    'timeout' : function ( json ) {   
                    },
                    'loadstart' : function ( json ){
                    },
                    'fileprogress' : function ( json, payload ) { 
                        // json is the same for 'load' event ( when a file was received, see Readme ) , 
                        // 'payload' is a binary Buffer
                        // you can direct the data payload to another stream, while the file is being uploaded
                        /** /
                        if( currFile === null ) {
                          currFile = new fs.WriteStream( json.value.path + '*' );
                        }
                        currFile.write( payload );
                        /**/
                    },
                    'progress' : function ( json ) {                              
                    },
                    'load' : function ( json ){
                        currFile = null;
                    },
                    'loadend' : function ( json, res, cback ) {
                        log( '\n\033[1;32mPost Done..\033[0m' );
                        // log( '\n JSON -> \n', json, '\n' );
                        res.writeHead( 200, { 'content-type' : 'text/plain' } );
                        res.write( '-> ' + new Date() + '\n' );
                        res.write( '-> request processed! \n' );   
                        res.write( '\n-> stats -> ' + JSON.stringify( json.stats, null, 4 ) + '\n' );
                        res.write( '\n Initial Configuration : ' + JSON.stringify( form.initialConfig, function ( key, value ) {
                            if ( typeof value === 'function' ) {
                                return '..';
                            } 
                            return value;
                        }, 4 ) + '\n' );

                        res.write( '\n-> fields received: [ { .. } , { .. } ] \n   ****************\n' + JSON.stringify( json.fields, null, 1 ) + '\n' );
                        res.write( '\n-> files written: [ { .. } , { .. } ] \n   **************\n ' + JSON.stringify( json.files, null, 1 ) + '\n' );
                        if ( form.removeIncompleteFiles ) {
                            res.write( '\n-> partially written ( removed ): [ { .. } , { .. } ] \n   *****************\n'+ JSON.stringify( json.incomplete, null, 1 ) + '\n' );
                        } else {
                            if ( json.incomplete.length !== 0 ) {
                                res.write( '\n-> partially written ( not removed ): \n   *****************\n' + JSON.stringify( json.incomplete, null, 1 ) + '\n' );
                            }
                        }
                        res.end();
                        try {
                            cback();
                        } catch ( err ) {
                            log( 'error', err.stack );
                        }
                    }
                }
            }; //end config obj

        if ( ( req.url === '/test/upload' ) || ( req.url === '/test/post' ) ) {
            log( ' -> req url :', req.url );
            form = new formaline( config ) ;
            form.parse( req, res, hi );
        } else {
            if ( req.url === '/favicon.ico' ) { // short-circuit annoying favicon requests
                res.writeHead( 200, { 'Content-Type' : 'image/x-icon' } );
                res.end();
            } else {
                log( ' -> req url 404 error :', req.url );    
                res.writeHead( 404, { 'content-type' : 'text/plain' } );
                res.end( '404' );
            }
        }
    },
    hi = function () {
        form = null;
        console.log( '\n\033[1;33mHi!, I\'m the callback function!\033[0m' );
    };

if (!module.parent) {
  app.listen(3000);
  console.log("Express server listening on port %d", app.address().port)
}

I'm using nodeJS v0.4.1, connect 1.7.1 and connect-mongo 0.1.5.

Best regards, Hugo

Philmod commented 13 years ago

Hi, I have the same error, but only when I upload some files...

Thanks for helping! Philippe

matylla commented 12 years ago

I am having some issues with express as well :/ This is the error I get while parsing upload if it comes from Express routing:

{"isupload":true,"msg":"req.headers[..] not found, or HTTP method not handled","fatal":true,"type":"headers"}

and this is a simple code i'm using:

app.post('/up', function(req, res) {
  form = new formaline({});

  form.on('error', function(json) {
    console.log('error: ' + JSON.stringify(json));
  });

  form.parse(req, res, function() {
    console.log('parsed');
  });
});
canxerian commented 12 years ago

I'm having the same issue as matylla, again, with the Express routing

Philmod commented 12 years ago

I finally switched to connect-form, which works perfectly!

matylla commented 12 years ago

I recently talked to TJ (express author) and he pointed me to the simple solution - deleting whole object responsible for mutlipart parsing:

delete express.bodyParser.parse['multipart/form-data'];

After this, everything works like a charm.

canxerian commented 12 years ago

I will make the switch too. If this is any use, I get the following error on submitting any type of form inputs:

formaline, event "headersexception" --> {"isupload":false,"msg":"req.headers[..] not found, or HTTP method not handled","fatal":true}

Here's console.log of req.headers:

host: 'localhost:3000', 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_68) AppleWebKit/534.51.22 (KHTML, like Gecko) Version/5.1.1 Safari/534.51.22', accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,/_;q=0.8', 'accept-language': 'en-us', 'accept-encoding': 'gzip, deflate', cookie: 'utmz=111872281.1319622455.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); utma=111872281.1162108877.1319622455.1321483235.1321793892.3; connect.sid=zBWDRfvtpNjRSiccgMulJmRg.zd3oU4UG8g1lnDyWWEInppFXuQGIkh3VAA8xoDZpT5s', connection: 'keep-alive' }

canxerian commented 12 years ago

I just disabled express.bodyParser and it now works!

canxerian commented 12 years ago

Thanks for that matylla, I hadn't refreshed the page hence didn't see your comment. I'll give it a go now..

canxerian commented 12 years ago

Is there a way to temporarily disbale bodyParser rather than delete it?

dheerajaggarwal commented 12 years ago

Hey guys, I was also having the same issue and I am not using any framework. It was just happening because I was throwing the same event two times.

I just debug my code using console.log("is Request Complete" + req.complete); to find out the exact point of error and finally I solve my issue. You can also debug to sort out your issue. Hope this helps. :)

freis commented 12 years ago

Isnt there any way to get it working with bodyParses enabled?

canxerian commented 12 years ago

Which version of express are you using? The bodyParser middleware now returns a req.files object containing your files. Please see http://tjholowaychuk.com/post/12943975936/connect-1-8-0-multipart-support.

If you're after upload progress bars, I found this article particularly useful http://www.matlus.com/html5-file-upload-with-progress/

freis commented 12 years ago

express: "version": "2.5.8" connect: "version": "1.8.6"

but im using formaline not formidable, or it will be the same?

coolaj86 commented 11 years ago

It sounds like the issue here was just a difference in the version of express.

However, know that formaline v2.0.0 is out. The API has been simplified and it has undergone some pretty decent testing that fixes most of the previous issues.