Request.get pipe to Reqest.Post on Box

2019-08-17 01:31发布

问题:

I'm trying to GET a file file (source) and .pipe to a destination Box Upload endpoint using NodeJS Request. This approach worked fine on other cloud storage, like Dropbox, but Box requires multipart/form-data upload, so it fails with bad request. Due some requirements, I cannot use the Box NPM package and would prefer this pipe approach.

I'm probably missing some timing here as there is an error output:

stream.js:74
      throw er; // Unhandled stream error in pipe.
      ^

Error: write after end
    at ClientRequest.OutgoingMessage.write (_http_outgoing.js:439:15)
    at Request.write (/node_modules/request/request.js:1514:27)
    at Request.ondata (stream.js:31:26)
    at emitOne (events.js:96:13)

Tried use form-data, but no success, just changed the error and, aparently, the content-length was not right.

And here is my source code.

var source = {
  url: SOURCE_DOWNLOAD_URL,
  method: "GET",
  headers: {
    'Authorization': 'Bearer ' + SOURCE_TOKEN
  },
  encoding: null
};

var destination = {
  url: 'https://upload.box.com/api/2.0/files/content',
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ' + BOX_TOKEN
  },
  formData: JSON.stringify({
    attributes: {
      name: 'somename.txt',
      parent: {
        id: 1234
      }
    }
  }),
  encoding: null
};

request(source).pipe(request(destination)
  .on('response', function (resDestination) {
    // expecting 201, but returns 400
    console.log(destination.method + ' ' + destination.url + ': ' + resDestination.statusCode + ' > ' + resDestination.statusMessage);
}));

回答1:

Try creating an intermediary stream that will collect all of the data before making the request to your destination

const Readable = require('stream').Readable
// Create a new Readable instance to write the original request data to
let sourceResponseStream = new Readable()

// no-op function to prevent _read() is not implemented error
sourceResponseStream._read = () => {}

let sourceReq = request(source)
// As we get chunks push them into our new Readable stream
sourceReq.on('data', chunk => {
  sourceResponseStream.push(chunk)
})

// Listen for the end of the readable data from the request
sourceReq.on('end', () => {
  // mark as no more data to read
  sourceResponseStream.push(null)

  // Make the request to the destination
  let destReq = request(destination, (err, res, body) => {
    if (err) {
      // handle err
      console.log(err)
   } else {
      console.log(`${destination.method} ${destination.url}: ${res.statusCode} > ${res.statusMessage}`)
    }
  })

  // Pipe our response readable containing our source request data
  sourceResponseStream.pipe(destReq)
})

If this doesn't work, you may want to add the sourceResponseStream as a property on the formData object. Also, you may want to remove the JSON.stringify() around the formData.attributes object. Not sure if that will affect how the request is processed.