Search code examples
expressproxymultipartform-datahttp-proxy-middleware

NodeJS - Edit and proxy a multipart/form-data request


I have a microservice that proxies every request adding one more field to it. With normal requests it's very easy, just add the field in the request.body and properly set the headers but for multipart/form-data requests I'm in trouble since days because if I add a field in the request.body, it will disappear.

const router = express()
const routes = require('~/routes')
const passport = require('passport')
const proxy = require('http-proxy-middleware')

router.use(passport.initialize())
require('./modules/passport-jwt')(passport)

router.use('/', routes)

router.use(
    '/account',
    passport.authenticate('jwt', { session: false }),
    proxy({
        target: process.env.ACCOUNT_SERVICE,
        pathRewrite: { '/account': '/' },
        onProxyReq: restream
    })
)

const restream = async function (proxyReq, req, res, options) {
    if (req.user) {
        if (
            req.headers['content-type'] &&
            req.headers['content-type'].match(/^multipart\/form-data/)
        ) {
            req.body.reqUser = req.user
        } else {
            const requestBody = JSON.stringify({ ...req.body, reqUser: req.user })
            proxyReq.setHeader('Content-Type', 'application/json')
            proxyReq.setHeader('Content-Length', Buffer.byteLength(requestBody))
            proxyReq.write(requestBody)
        }
    }
}

When the request arrives to the other microservice, the request.body is empty and after it will be written by multer that will put multipart/form-data params into the request.body.

I really need a solution that let me append a field into a multipart/form-data request in the proxy restream function.

I tried everything to succeed in this but I'm stuck. I hope everything is clear from my side. Don't be afraid to ask for more details if you need them. I'm begging for your help.


Solution

  • I finally figured out how to succeed. My code now looks like this:

    const router = express()
    const routes = require('~/routes')
    const passport = require('passport')
    const proxy = require('http-proxy-middleware')
    
    router.use(passport.initialize())
    require('./modules/passport-jwt')(passport)
    
    router.use('/', routes)
    
    router.use(
        '/account',
        passport.authenticate('jwt', { session: false }),
        proxy({
            target: process.env.ACCOUNT_SERVICE,
            pathRewrite: { '/account': '/' },
            onProxyReq: restream
        })
    )
    
    const restream = async function (proxyReq, req, res, options) {
        if (req.user) {
            if (
                req.headers['content-type'] &&
                req.headers['content-type'].match(/^multipart\/form-data/)
            ) {
                // build a string in multipart/form-data format with the data you need
                const formdataUser =
                    `--${request.headers['content-type'].replace(/^.*boundary=(.*)$/, '$1')}\r\n` +
                    `Content-Disposition: form-data; name="reqUser"\r\n` +
                    `\r\n` +
                    `${JSON.stringify(request.user)}\r\n`
    
                // set the new content length
                proxyReq.setHeader(
                    'Content-Length',
                    parseInt(request.headers['content-length']) + Buffer.byteLength(formdataUser)
                )
    
                proxyReq.write(formdataUser)
            } else {
                const body = JSON.stringify({ ...req.body, reqUser: req.user })
                proxyReq.setHeader('Content-Type', 'application/json')
                proxyReq.setHeader('Content-Length', Buffer.byteLength(body))
                proxyReq.write(body)
            }
        }
    }
    

    As I wrote in the code comments:

    1. Build a string in multipart/form-data format that must look like this:

      ------WebKitFormBoundaryiBtoTWFkpAG6CgXO\r\n
      Content-Disposition: form-data; name="firstname"\r\n
      \r\n\
      Andrea\r\n
      

      (In my code I stringified the data because was an object);

    2. Set the header 'Content-Length' by adding the above string's byte length to the original request length;

    3. Use proxyReq.write function to send the new data.