Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

this.request.files no longer works under 1.0.0 pre #909

Closed
Tarang opened this issue Oct 12, 2014 · 27 comments
Closed

this.request.files no longer works under 1.0.0 pre #909

Tarang opened this issue Oct 12, 2014 · 27 comments
Milestone

Comments

@Tarang
Copy link

Tarang commented Oct 12, 2014

In 0.9.4 its possible to use this.request.files to retrieve any files uploaded to the server via a multipart POST request.

I'm not sure whether it was intended but the same bunch of code no longer provides any files for the exact same requests with iron:router@1.0.0-pre3.

If it was intentional is there a way around this?

@cmather
Copy link
Contributor

cmather commented Oct 12, 2014

I forgot this was automatic before. I'll make it automatic again. In the mean time, add the connect bodyParser middleware manually like this:
If (meteor.isserver)
Router.use(Router.bodyParser())

Typing on mobile so sorry for the syntax errors :-)

On Oct 12, 2014, at 7:30 AM, Tarang Patel notifications@github.com wrote:

In 0.9.4 its possible to use this.request.files to retrieve any files uploaded to the server via a multipart POST request.

I'm not sure whether it was intended but the same bunch of code no longer provides any files for the exact same requests with iron:router@1.0.0-pre3.

If it was intentional is there a way around this?


Reply to this email directly or view it on GitHub.

@Tarang
Copy link
Author

Tarang commented Oct 12, 2014

I can see the code for bodyParser in the lib file, was it added on pre3 or is it still on devel?

On pre3 this is what happens:
TypeError: Object [object Object] has no method 'bodyParser'

@cmather
Copy link
Contributor

cmather commented Oct 12, 2014

It's only defined on the sever because it's connect middleware. Is that the issue?

On Oct 12, 2014, at 8:46 AM, Tarang Patel notifications@github.com wrote:

I can see the code for bodyParser in the lib file, was it added on pre3 or is it still on devel?

On pre3 this is what happens:
TypeError: Object [object Object] has no method 'bodyParser'


Reply to this email directly or view it on GitHub.

@Tarang
Copy link
Author

Tarang commented Oct 12, 2014

Oh did it, I mixed up the versions of iron router, accidentally tried on 0.9.4. It does add but there is still another issue:

W20141012-19:30:32.639(3)? (STDERR) Sun, 12 Oct 2014 16:30:32 GMT body-parser deprecated bodyParser: use individual json/urlencoded middlewares at app/server/uploads/uploads.js:1:54
W20141012-19:30:32.663(3)? (STDERR) Sun, 12 Oct 2014 16:30:32 GMT body-parser deprecated undefined extended: provide extended option at ../../../../../../../.meteor/packages/iron:router/.1.0.0-pre3.yvhxuc++os+web.browser+web.cordova/npm/node_modules/body-parser/index.js:75

Perhaps its to do with the connect 3.0 deprecation of bodyParser?

@tmeasday
Copy link
Contributor

See also #914

@tmeasday
Copy link
Contributor

@Tarang you need to do something more like Router.use(Router.bodyParser.json()).

Closing this one for #842

@boulaycote
Copy link

I'm on version 1.0.0, trying to set the limit of bodyParser

if (Meteor.isServer) {
  Router.use(Iron.Router.bodyParser.json({
    limit: "100mb"
  }));
}

But I'm getting an error.

W20141104-18:22:13.412(-5)? (STDERR) TypeError: Object function router(req, res, next) {                                                          // 13
W20141104-18:22:13.412(-5)? (STDERR)     //XXX this assumes no other routers on the parent stack which we should probably fix      // 14
W20141104-18:22:13.413(-5)? (STDERR)     router.dispatch(req.url, {                                                                // 15
W20141104-18:22:13.413(-5)? (STDERR)       request: req,                                                                           // 16
W20141104-18:22:13.414(-5)? (STDERR)       response: res                                                                           // 17
W20141104-18:22:13.414(-5)? (STDERR)     }, next);                                                                                 // 18
W20141104-18:22:13.415(-5)? (STDERR)   } has no method 'use'

The use method does not exist, is it different with version 1.0.0?

@tmeasday
Copy link
Contributor

tmeasday commented Nov 4, 2014

Hey @boulaycote - yes, Router.use was pulled at the last minute, but Router.onBeforeAction does more or less the same thing in this case.

@boulaycote
Copy link

So I'm guessing that request.files should be available right off the bat, since bodyParser.json() is already being added, correct?

It's not quite working as I thought.

I am sending a file with to a /upload route with form-data fd.append("myFile", file);.

My route is defined within map() as

this.route("upload", {
    where: "server",
    path: "/gallery/upload",
    action: function () {....}
});

Within the action callback I should have access to this.request.files which is not the case. Any ideas?

@tmeasday
Copy link
Contributor

tmeasday commented Nov 4, 2014

I'm not sure. Is bodyParser.json where request.files comes from? I'm don't know but that sounds more like bodyParser.multipart to me or something like that.

@boulaycote
Copy link

Oh right. So since bodyParser.multipart was deprecated a while ago, I guess I should find another middleware to use, like busboy?

@tmeasday
Copy link
Contributor

tmeasday commented Nov 4, 2014

/me isn't super on top of connect middleware but it sounds like you are on the right track.

Router.onBeforeAction() should be an able substitute for connect.use() though.

@boulaycote
Copy link

Huhu. Ok when I find a solution I'll post it here.

@boulaycote
Copy link

Allright,

A simple solution is to use Busboy as middleware. And then do something like this:

if (Meteor.isServer) {
  var Busboy = Meteor.npmRequire("Busboy"),
      fs = Npm.require("fs"),
      os = Npm.require("os"),
      path = Npm.require("path");

  Router.onBeforeAction(function (req, res, next) {
    var filenames = []; // Store filenames and then pass them to request.

    if (req.method === "POST") {
      var busboy = new Busboy({ headers: req.headers });
      busboy.on("file", function (fieldname, file, filename, encoding, mimetype) {
        var saveTo = path.join(os.tmpDir(), filename);
        file.pipe(fs.createWriteStream(saveTo));
        filenames.push(saveTo);
      });
      busboy.on("field", function(fieldname, value) {
        req.body[fieldname] = value;
      });
      busboy.on("finish", function () {
        // Pass filenames to request
        req.filenames = filenames;
        next();
      });
    }
    // Pass request to busboy
    req.pipe(busboy);
 });
}

Then in your next() function, in my case /upload you can access filenames with this.request.filenames.

Router.route("upload", {
    where: "server",
    path: "/gallery/upload",
    action: function () {
        // here you can access this.request.filenames which was filled previously.
    }
});

Of course, to use Busboy, you will have to meteor add meteorhacks:npm and add a packages.json file to the root of your project and add the following:

{
  "busboy": "0.2.9"
}

Hope this helps!

@paulgration
Copy link

@boulaycote Thanks for this, just been using it and it works well. In case this helps anyone else (and assuming this is the correct way of doing this) one alteration I made was to put the req.pipe within the if req.method block in case the route was hit with anything other than a POST request, in which case the code would error as busboy would be undefined:

if (Meteor.isServer) {
  var Busboy = Meteor.npmRequire("Busboy"),
      fs = Npm.require("fs"),
      os = Npm.require("os"),
      path = Npm.require("path");

  Router.onBeforeAction(function (req, res, next) {
    var filenames = []; // Store filenames and then pass them to request.

    if (req.method === "POST") {
      var busboy = new Busboy({ headers: req.headers });
      busboy.on("file", function (fieldname, file, filename, encoding, mimetype) {
        var saveTo = path.join(os.tmpDir(), filename);
        file.pipe(fs.createWriteStream(saveTo));
        filenames.push(saveTo);
      });
      busboy.on("field", function(fieldname, value) {
        req.body[fieldname] = value;
      });
      busboy.on("finish", function () {
        // Pass filenames to request
        req.filenames = filenames;
        next();
      });
      // Pass request to busboy
      req.pipe(busboy);
    } else {
      next();
    }
 });
}

@rcolepeterson
Copy link

Thank you so much for this. works.

@cfsamson
Copy link

cfsamson commented Feb 8, 2015

Just posting this for completeness. If you want to pass your files together with the request or store them in mongodb, you can use this (beware that this stores them in memory though):

if (Meteor.isServer) {
var Busboy = Npm.require("busboy");

Router.onBeforeAction(function (req, res, next) {
    var files = []; // Store files in an array and then pass them to request.

    var image = {}; // crate an image object

    if (req.method === "POST") {
        var busboy = new Busboy({ headers: req.headers });
        busboy.on("file", function (fieldname, file, filename, encoding, mimetype) {
            image.mimeType = mimetype;
            image.encoding = encoding;

            // buffer the read chunks
            var buffers = [];

            file.on('data', function(data) {
                buffers.push(data);
            });
            file.on('end', function() {
                // concat the chunks
                image.data = Buffer.concat(buffers);
                // push the image object to the file array
                files.push(image);
            });
        });

        busboy.on("field", function(fieldname, value) {
            req.body[fieldname] = value;
        });

        busboy.on("finish", function () {
            // Pass the file array together with the request
            req.files = files;
            next();
        });
        // Pass request to busboy
        req.pipe(busboy);
    }
    else{
        this.next();
    }


});
}

You can then store them in your db or somewhere else like this:

Router.route('/files/image/upload', function () {
// we assume only one image here
var image = this.request.files[0];
var id = mydb.images.insert(image);
var res = {
    link: '/image/' + id
};

this.response.end(JSON.stringify(res));

}, { where: 'server', name:'imageUpload' });

This exact example returns a link for the Froala WYSIWYG editor, but it's just an example.

@flyandi
Copy link

flyandi commented Mar 19, 2015

So is this right Iron:Router doesn't support reading directly Post data?

@papayaah
Copy link

papayaah commented Jun 8, 2015

hey guys - any update on this? Can we no access the file data from a form with Iron Router 1.0.8?

@jfurneaux
Copy link

The busboy solution worked perfectly for me.

Note: Be careful with the capitalization of 'Busboy' - if you don't use the correct lower case Meteor.npmRequire("busboy"), it will work in dev, but fail on certain prod environments. There's a thread resolving this and I suspect this may be the source of the mistakes.

@daslicht
Copy link

So using busboy is a must ?

@nicejwjin
Copy link

Yeah. It seems like working quiet well using Busboy,
I think this is kind of basic functionality-
Hopefully- to be getting easier!

@mushkab
Copy link

mushkab commented Oct 26, 2015

@pmgration @boulaycote
hey guys
im using busboy to parse multipart form data like was posted above.
the problem i have is that in different environment i get req.body undefined which causes exception when
setting the fields. do u have any idea why is this happening?

@nicejwjin
Copy link

@mushkab My project is working well still. Could you specify your case?

@mushkab
Copy link

mushkab commented Oct 26, 2015

@nicejwjin when i handle http post in server (from third party application - webhook) with content type of multipart form data(parse it in router.onbeforeaction), i get req.body undefined in production environment. this does not happen in development where req.body is object({}). the post call is totally equal in both cases.
when req.body is undefined it causes error when setting fields in the code above.

busboy.on("field", function(fieldname, value) {
req.body[fieldname] = value;
});

wondering what causes this.

@maxzzznz
Copy link

@mushkab I had the exact problem as you, it seems that the body object does not exist at 'onBeforeAction' time. My quick work around the problem was to extend the request object with a new postData 'sub-object'. I did this with underscoresj which I was already using in my project.

In the end this is working for me:

if (Meteor.isServer) {
    var Busboy = Meteor.npmRequire("busboy"),
        fs = Npm.require("fs"),
        os = Npm.require("os"),
        path = Npm.require("path");

    Router.onBeforeAction(function (req, res, next) {
        var filenames = []; // Store filenames and then pass them to request.
        _.extend(req, {postData: {}});

        if (req.method === "POST") {
            var busboy = new Busboy({ headers: req.headers });
            busboy.on("file", function (fieldname, file, filename, encoding, mimetype) {
                var saveTo = path.join(os.tmpDir(), filename);
                file.pipe(fs.createWriteStream(saveTo));
                filenames.push(saveTo);
            });
            busboy.on("field", function(fieldname, value) {
                req.postData[fieldname] = value;
            });
            busboy.on("finish", function () {
                // Pass filenames to request
                req.filenames = filenames;
                next();
            });
            // Pass request to busboy
            req.pipe(busboy);
        } else {
            this.next();
        }
    });
}

Then I can get access to the POST fields and their values using this in my route:

this.request.postData

@harvinder34
Copy link

it does not return any file name or data

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests