1

How can I convert an incoming NodeJS/Express Request (req) object into something that can be sent over in a proxy request?

My NodeJS/express service uses node-fetch to proxy requests to a separate server. The incoming curl request looks like this (Firefox POSIX curl)

curl 'https://localhost:8080/bluecost/spreadsheet/upload/SSCSpreadsheetUpload-09192020.xlsx' -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:85.0) Gecko/20100101 Firefox/85.0' -H 'Accept: application/json, text/plain, */*' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Content-Type: multipart/form-data; boundary=---------------------------37552304939101372432093632492' -H 'Origin: https://localhost:8080' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Referer: https://localhost:8080/spreadsheet-upload' -H 'Cookie: connect.sid=s%3ARxnRck1VzcCXk05LKV1CJ5PeslCK1sWC.WqqK%2B0CHsAP0MpcFzRFbHTh19YOoeBi0mIAKCPQ%2BSGU; access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzcHJpbnQtbWlkZGxld2FyZS1pc3N1ZXIiLCJzdWIiOiJCZW4uUHJhY2h0MUBpYm0uY29tIiwibm90ZXNJZCI6IkJlbiBQcmFjaHQiLCJzZXJpYWxOdW0iOiI4Njc1NTU4OTciLCJleHAiOjE2MTQ4MjgwNzksImJsdWVHcm91cHMiOlsiQkxVRUNPU1RfU1BSRUFEU0hFRVRfVVBMT0FEX1RFU1QiXSwiaWF0IjoxNjE0ODIwODk3fQ.jWhvsHZiJLRvAHG7SDTmOkdcpMRHxiaLUWDhfJmRXv0' -H 'Pragma: no-cache' -H 'Cache-Control: no-cache' --data-binary $'-----------------------------37552304939101372432093632492\r\nContent-Disposition: form-data; name="file"; filename="SSCSpreadsheetUpload-09192020.xlsx"\r\nContent-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\r\n\r\n-----------------------------37552304939101372432093632492--\r\n'

A simplified version of the routing looks like this:

app.use('/multipart',(req,res,next) => {
            const URL = require('url').URL;
            var spreadsheet_client_url = new URL(config.spreadsheet_client_host);
            spreadsheet_client_url.pathname = req.originalUrl;
            // spreadsheet_client_url.pathname = spreadsheet_client_url.pathname.replace('/spreadsheet-upload', '/');
            logger.debug('/spreadsheet-upload: Generating token based on proxyReqOpts.user=' + req.user);

            logger.info(`@@Request ${req.method} ${spreadsheet_client_url.href}`);
            var accessToken = generateToken(req.user);
            /**
             * Set bearer token based on code from Sprint-API's lua code
             */
            // req.setHeader('Authorization', 'Bearer ' + accessToken);
            logger.debug('Route: /spreadsheet-upload: adding Authorization: Bearer ' + accessToken);
            var newHeaders = req.headers;
            newHeaders['Authorization'] = 'Bearer ' + accessToken;
            if (isMultipartRequest(req)) {
                newHeaders['Content-Length'] = req.body.length;
                // 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(req.user)}\r\n`

            } else {

                const options = {
                    port: spreadsheet_client_url.port,
                    path: spreadsheet_client_url.pathname,
                    body: req.body,
                    method: req.method,
                    headers: newHeaders,
                    redirect: true
                };
                console.log('@@@');
                console.log('@@@ url: ' + spreadsheet_client_url.href + 'post-options: ' + JSON.stringify(options));
                console.log('@@@');
                const fetch = require('node-fetch');
                fetch(spreadsheet_client_url, options).then(r => {
                    console.log('@@ reached fetch result retrieved');
                    return r.text();
                }).then(fetchText => {
                    console.log('@@ reached fetch text result retrieved');
                    /*
                    // This code complains that headders can't be set after sending to client
                    res.writeHead(res.statusCode,res.headers);
                    res.send(fetchText);
                    */
                    // res.body=fetchText;
                    res.status(200).send(fetchText);

                    next();
                }
            }

            )
        .catch(err => { console.log('Error fetching ' + err) });
})

function isMultipartRequest(req) {
    let contentTypeHeader = req.headers['content-type'];
    return contentTypeHeader && contentTypeHeader.indexOf('multipart') > -1;
}

So I have an incoming request, and I believe I can reliably distinguish between normal and multi-part requests. Many examples suggest creating this "form-data" object, then sending it over like this:

const FormData = require('form-data');     
const form = new FormData();

const buffer = // e.g. `fs.readFileSync('./fileLocation');
const fileName = 'test.txt';

form.append('file', buffer, {
  contentType: 'text/plain',
  name: 'file',
  filename: fileName,
});

fetch('https://httpbin.org/post', { method: 'POST', body: form })
    .then(res => res.json())
    .then(json => console.log(json));

I'd like to keep using node-fetch because it's the devil I know best. Should I use a form-data object and pass it over like above? Is there a better way? I'd like to know how to build a form-data object from a request object given I only know that it's multi-part, nothing else.

The ultimate destination is a Java Spring-Boot server that takes a MultipartFile file as follows:

    @PostMapping("/spreadsheet/upload/{filename}")
    public ResponseEntity<HashMap<String,Object>> uploadSpreadsheet(@RequestBody MultipartFile file, @PathVariable("filename") String filename) {

With normal non-multi-part form requests, it matches enough to reach this method, but the "file" object is null.

Woodsman
  • 901
  • 21
  • 61

0 Answers0