3

I'm sending data from a nodejs application to a spring rest endpoint. I followed the example on form-data to fs.createReadStream from a file, and that's fine. But since my application generates this data, what I actually need is to send the data that's stored in a variable (as a string). So I followed this answer to create a stream from a string, but on the Java end I receive a "stream ended unexpectedly" error.

var form = new FormData();
form.append('id', id);

// this works
//form.append('inputFiles[]', fs.createReadStream('test.xml'));

//this does not
const Readable = require('stream').Readable;
let s = new Readable();
s._read = () => {};
s.push('some data generated by my application');
s.push(null);

form.append('inputFiles[]', s);

form.submit(url, function(error, result) {
  if (error) {
    console.log('Error!');
  }
});

Do I need to pipe this data somehow? Or specify content length (if so, how do I determine the length?) I tried to add { filename : 'test.xml', contentType: 'application/xml' } to the readable to maybe mimic the file, but still get the same error.

otgw
  • 390
  • 1
  • 5
  • 16

3 Answers3

5

In case anyone else runs into the same issue -- I needed to have all 3 of the extra fields, including knownLength

    let stream = new Readable();
    stream.push(foo);
    stream.push(null);
    form.append('inputFiles[]', stream, {
      filename : 'test.xml',
      contentType: 'application/xml',
      knownLength: foo.length
    }); //extra fields necessary
otgw
  • 390
  • 1
  • 5
  • 16
  • Thanks a lot. Exact solution that I was looking for. I think the form-data module documentation is not clearly mentioning the arguments for form.append function. – ARUN KUMAR Nov 29 '21 at 05:23
0

Can I suggest different approach? Since the answer you are using is from 2014...

let form = new FormData();
                form.append('fieldName', 'fileBuffer/DataString', 'fileName');

                axios.post('url', form, {
                    headers: {
                        'Content-Type': `multipart/form-data; boundary=${form._boundary}`
                    }
                }).then((res) => {
                    console.log(res.data);
                    res.json(responseFromServer.data);
                }).catch((err) => {
                    res.status(400).send(err);
                });
Ido Cohen
  • 621
  • 5
  • 16
0

I spent some time to find a propper solution for this and finally was able to solve it.

I understood you actually need to create the readable from a buffer in order to work.

I created a class to make this easier:

const { Readable } = require('stream')

module.exports = class FormData {
  /**
   *
   * @param {string} data The data to be converted
   * @param {string} name The file name
   * @param {string} type The file type
   */
  constructor (data, name, type) {
    const readStream = new Readable();
    readStream._read = () => {}
    readStream.push(Buffer.from(data))
    readStream.push(null)

    return {
      file: {
        value: readStream,
        options: {
          filename: name,
          contentType: type,
          knownLength: data.length,
        }
      }
    }
  }
}

Now, all you have to do is to create an instance and call the request function with it.

const request = require('request')

const csv = // your csv content

const formData = new FormData(csv, 'conversion.csv', 'text/csv; charset=utf-8')

const options = {// the normal request options (headers etc.)

options.formData = formData

request('https://...', options).then(// your normal request call

Eduardo Russo
  • 4,121
  • 2
  • 22
  • 38