Search code examples
javascriptnode.jsexpressnode-request

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


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?


Solution

  • 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]);