Getting ERROR: uncaughtException: source.on is not a function, when using request and multiparty for multipart/form-data
Asked Answered
G

2

5

I am trying to send data from my node application to a 3rd party HTTP endpoint.

I am parsing the data on the request object coming from the client using the multiparty module and sending the data through the request module. I am getting the error

error: uncaughtException: source.on is not a function

var request = require('request');
const multiparty = require('multiparty');

function addAttachment(req, res) {
  let form = new multiparty.Form();
  let parsedFile = {};
  const formData = {};
  form.parse(req, function(err, fields, files){
    Object.keys(fields).forEach(function(name) {
      formData[name] = fields[name][0];
    });

    Object.keys(files).forEach(function(name) {
      logger.debug(name);
      parsedFile[name] = files[name][0];
    });
    formData.uploadFile = parsedFile.uploadFile;
    logger.debug('formData ', formData);

    reqOptions.url = imageURL;
    reqOptions.formData = formData;

    logger.debug('REQ_OPTIONS ', reqOptions);

    request.post(reqOptions, function (err, response, body) {
      if (err) {
        logger.warn(req, ' Error sending attachment', err);
        res.status(400);
        res.json({ "msg": "Error sending attachment" });
      } else {
        res.status(201);
        logger.debug('BODY ', body);
        res.send(body);
      }
    });
  });
}

The reqOptions obj contains the headers, url, auth obj, we then add the form data to it.

When I log the form data it looks to be in the correct format

{
"meta": {
    "prop1": "xxxxxx",
    "prop2": "xxxxxxxxxxxxx",
    "uploadFile": {
        "fieldName": "uploadFile",
        "originalFilename": "test.PNG",
        "path": "/tmp/W1IppPiK04JpkPrnZWEhzkmV.PNG",
        "headers": {
            "content-disposition": "form-data; name=\"uploadFile\"; filename=\"test.PNG\"",
            "content-type": "image/png"
        },
        "size": 42786
    }
}

}

Godship answered 4/9, 2019 at 12:59 Comment(1)
the solution is working in you?Theadora
G
0

So after some hair pulling and digging around, I was able to post form data to the external API. I decide to change the node modules I was using to connect-multiparty. Connect will parse the request headers and decode the post-form-data allowing you to access the data from the req obj E.G req.body now have the added properties and req.files has uploaded files.

const multipart = require('connect-multiparty');
const multipartMiddleware = multipart();

Then add the multipartMiddleware to the route.

 app.post('/api/addAttachment' multipartMiddleware, MyController.addAttachment);

Then in my controller file I changed the code to use connect-multipart.

const fs = require('fs');
var request = require('request');  

function addAttachment(req, res) {
  const TMP = '/tmp';

  let formData = {};

  Object.keys(req.body).forEach((propName) =>{
   if (typeof propName === 'string') {
    logger.debug(propName, ' is a string');
    formData[propName] = req.body[propName];
   } else {
    logger.debug(propName, ' is not a string')
   }
  });
  //The files get added to the tmp folder on the files system,
  //So we create a stream to read from tmp folder, 
  //at the end end we need to delete the file
  formData['uploadFile'] = fs.createReadStream(req.files.uploadFile.path);
  logger.debug('FORM DATA ', formData, '\n');

  reqOptions.url = imageUrl;
  reqOptions.headers = {'Content-Type': 'multipart/form-data','Accept': 'application/json'};
  reqOptions.formData = formData;

  logger.debug('REQ_OPTIONS ', reqOptions, '\n');

  request.post(reqOptions, function (err, response, body) {
    if (err) {
      removeFiles(TMP);
      logger.warn(req, ' Error sending attachment', err);
      res.status(400);
      res.json({"msg": "Error sending attachment"});
    } else {
      removeFiles(TMP);
      res.status(201);
      logger.debug('BODY ', body);
      res.send(body);
    }
  });
}
Godship answered 5/9, 2019 at 14:10 Comment(3)
It's not working in mine.Theadora
@Theadora anymore details on what was not working?Godship
But this solution works when you have backend code ready, I am trying to send request to another API where I cannot add multipart middleware in route, what should I do ?Gasholder
L
15

This error is probably one of the best examples how error message can be perfectly misleading. Therefore. it's very frustrating to do RCA of the issue:

ERROR: uncaught Exception: source.on is not a function

Actually there is nothing about any function here. In my case, I spent hours scratching my head and finally only to find it is JSON under another JSON which was causing this error:

  let subJson = 
  {
      field1: "value1",
      field2: "value2"
  }

  let myJson = 
  {
      field1: "value1",
      field2: "value2",
      field3: subJson
  }

  createFormData(myJson);

This is it! When you call createFormData with myJson as parameter, you will see exception source.on is not a function! And we keep thinking where is that function?

Solution is JSON.stringify

   field3: JSON.stringify(subJson)

Will solve this issue.

javascript!

Liquidambar answered 16/3, 2022 at 9:7 Comment(2)
Cannot upvote this enoughJackshaft
That's not a JavaScript problem. createFormData, whatever that is, is doing something wrong.Ocreate
G
0

So after some hair pulling and digging around, I was able to post form data to the external API. I decide to change the node modules I was using to connect-multiparty. Connect will parse the request headers and decode the post-form-data allowing you to access the data from the req obj E.G req.body now have the added properties and req.files has uploaded files.

const multipart = require('connect-multiparty');
const multipartMiddleware = multipart();

Then add the multipartMiddleware to the route.

 app.post('/api/addAttachment' multipartMiddleware, MyController.addAttachment);

Then in my controller file I changed the code to use connect-multipart.

const fs = require('fs');
var request = require('request');  

function addAttachment(req, res) {
  const TMP = '/tmp';

  let formData = {};

  Object.keys(req.body).forEach((propName) =>{
   if (typeof propName === 'string') {
    logger.debug(propName, ' is a string');
    formData[propName] = req.body[propName];
   } else {
    logger.debug(propName, ' is not a string')
   }
  });
  //The files get added to the tmp folder on the files system,
  //So we create a stream to read from tmp folder, 
  //at the end end we need to delete the file
  formData['uploadFile'] = fs.createReadStream(req.files.uploadFile.path);
  logger.debug('FORM DATA ', formData, '\n');

  reqOptions.url = imageUrl;
  reqOptions.headers = {'Content-Type': 'multipart/form-data','Accept': 'application/json'};
  reqOptions.formData = formData;

  logger.debug('REQ_OPTIONS ', reqOptions, '\n');

  request.post(reqOptions, function (err, response, body) {
    if (err) {
      removeFiles(TMP);
      logger.warn(req, ' Error sending attachment', err);
      res.status(400);
      res.json({"msg": "Error sending attachment"});
    } else {
      removeFiles(TMP);
      res.status(201);
      logger.debug('BODY ', body);
      res.send(body);
    }
  });
}
Godship answered 5/9, 2019 at 14:10 Comment(3)
It's not working in mine.Theadora
@Theadora anymore details on what was not working?Godship
But this solution works when you have backend code ready, I am trying to send request to another API where I cannot add multipart middleware in route, what should I do ?Gasholder

© 2022 - 2024 — McMap. All rights reserved.