node express/request: piping a POST request with body parsing

drmrbrewer picture drmrbrewer · Jun 15, 2017 · Viewed 8.1k times · Source

I'm attempting to pipe a request for handling by a remote server, along the following lines:

var destination = request(url);
req.pipe(destination).pipe(res);

This works just fine for GET requests. But for POST requests I'm struggling. An important point to note, I think, is that for POST requests I'm using a body parser before my POST route handler in order to extract the body from the POST request... it's just a basic text body parser because the body contains plain text:

var postRouteHandler = someFunction;
var bodyParser = require('body-parser');
var textParser = bodyParser.text({
    limit: '50kb'
});
app.use('/postRoute', [textParser, postRouteHandler]);

From this issue and this issue it seems to me that doing any processing on the POST request before you pipe it will cause a problem. Indeed, when I remove the parser, the piping seems to work OK.

The problem is that I need to examine the body first, to do some initial processing and then to determine whether or not to pipe the request on to the remote server at all. So I need to parse the body before piping.

Is there any way around this problem?

Answer

robertklep picture robertklep · Jun 15, 2017

The issue is that with streams (like req), once you've read it you can't reset it.

Because body-parser has read the stream already, piping it won't work because that will try to read the stream again (which at that point has been exhausted).

A workaround would be take the text read by body-parser, and create a minimal req "clone" in order for request to be able to pass the request along:

var intoStream = require('into-stream');
var bodyParser = require('body-parser');
var textParser = bodyParser.text({ limit: '50kb' });

var postRouteHandler = function(req, res) {
  let text = req.body;
  if (! shouldPipe(text)) {
    return res.sendStatus(400); // or whatever
  }

  // Here's where the magic happens: create a new stream from `text`,
  // and copy the properties from `req` that `request` needs to pass
  // along the request to the destination.
  let stream     = intoStream(text);
  stream.method  = req.method;
  stream.headers = req.headers;

  // Pipe the newly created stream to request.
  stream.pipe(request(url)).pipe(res);
};
app.use('/postRoute', [textParser, postRouteHandler]);