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.