Proxy multipart form data using NodeJS and node-fetch - node.js

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.

Related

How to send png file as binary data to node.js server from react?

I am working with an api that requires me to send png files as binary data to my backend. In their docs they show how to do this with a curl example ( I have never used curl ) but they dont show how to do this with axios to a node.js server ( despite examples of node.js on everything else).
How would I convert this following example of a curl to post request to a node.js server?
I am using axios and react for the front end and Node.js for backend.
Here is the curl example, How would I do this in axios?
Also, How do I convert a .png file to binary data in React before sending post request with axios?
curl -u "<account_sid>:<account_secret>" --data-binary #<filename.png> -H "Content-Type: <content-type of upload>" https://mcs.us1.twilio.com/v1/Services/<chat_service_sid>/Media
You can convert your cURL requests with this utility: https://kigiri.github.io/fetch/
Yours in particular will look like this, and fetch and axios are nearly identical:
fetch("https://mcs.us1.twilio.com/v1/Services//Media", {
body: "#<filename.png>",
headers: {
Authorization: "Basic PGFjY291bnRfc2lkPjo8YWNjb3VudF9zZWNyZXQ+",
"Content-Type": "<content-type of upload>"
},
method: "POST"
})
You can read this article here.
By MDN https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data
const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs/promises');
// Read image from disk as a Buffer
const image = await fs.readFile('./stickers.jpg');
// Create a form and append image with additional fields
const form = new FormData();
form.append('productName', 'Node.js Stickers');
form.append('productDescription', 'Cool collection of Node.js stickers for your laptop.');
form.append('productImage', image, 'stickers.jpg');
// Send form data with axios
const response = await axios.post('https://example.com', form, {
headers: {
...form.getHeaders(),
Authentication: 'Bearer ...',
},
});
You can convert any curl into httpRequest using
https://curlconverter.com/#javascript
and read about curl information from
https://everything.curl.dev/http/post
for your curl
curl -u "<account_sid>:<account_secret>" --data-binary #<filename.png> -H "Content-Type: " https://mcs.us1.twilio.com/v1/Services/<chat_service_sid>/Media
`fetch('https://mcs.us1.twilio.com/v1/Services/<chat_service_sid>/Media', {
method: 'POST',
headers: {
'Content-Type': '<content-type of upload>',
'Authorization': 'Basic ' + btoa('<account_sid>:<account_secret>')
},
body: '<filename.png>'
});`
You can try to use FileReader to read the given file as binary and send it using fetch/axios.
Second, the curl's -u "<account_sid>:<account_secret>" sends Authorization header. You can simply use btoa(username + ':' + 'password') to achieve that.
Here's an example using fetch and FileReader.
import React, {useState} from 'react';
function FileUploadComponent(){
const [selectedFile, setSelectedFile] = useState();
const changeHandler = (event) => {
setSelectedFile(event.target.files[0]);
};
const handleSubmission = () => {
const reader = new FileReader();
reader.onload = (event) => {
const url = 'https://mcs.us1.twilio.com/v1/Services/<chat_service_sid>/Media';
const headers = {
Authorization: "Basic " + btoa(username + ":" + password),
'Content-Type': selectedFile.type
}
fetch(url, { method: 'POST', body: event.target.result, headers })
.then((response) => response.json())
.then((result) => {
console.log('Success:', result);
})
.catch((error) => {
console.error('Error:', error);
});
};
reader.readAsBinaryString(selectedFile)
};
return (
<div>
<input type="file" name="file" onChange={changeHandler}/>
<div>
<button onClick={handleSubmission}>Submit</button>
</div>
</div>
)
}
Here's some useful links:
https://tr.javascript.info/fetch-basics#submit-an-image
https://developer.mozilla.org/en-US/docs/Web/API/FileReader/readAsBinaryString

Nodejs post Json data with headers

How to post Json data via node js either with form-data or via request ?
Using form-data. It does not allow to send custom headers and if replaced bodyParams.getHeaders() with custom headers it does not send request data
https://www.npmjs.com/package/form-data
const smsResponse = await request.post(url, bodyParams, {headers:bodyParams.getHeaders()})
Using Request it does not allow to send parameters
require('request');
const subscription = await request.post(url, body, {headers:{'Accept':'text/html'}})
Postman curl request It works via postman. tried to use postman nodejs request code but it fails
curl --location --request POST 'https://personalsite.com//test.php' \
--header 'accept: text/html' \
--header 'SECRET-TOKEN-MESSAGE: dataforport' \
--header 'Content-Type: application/json' \
--header 'Cookie: PHPSESSID=1d2shuebo7lal8sn2itgppvfk4' \
--data-raw '{
"mobileno": "888888888",
"messagetext": "Test message"
}'
Tried but it did not worked
Node.js: How to send headers with form data using request module?
Use new Headers() to build your header.
https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
var myHeaders = new Headers();
myHeaders.append('Accept', 'text/html');
var myInit = { method: 'POST',
headers: myHeaders,
mode: 'cors',
cache: 'default',
body: JSON.stringify({coffee: 'yes...'})
};
var myRequest = new Request('https://personalsite.com//test.php',myInit);
fetch(myRequest).then(function(response) {
console.log(response)
});

Polar accesslink API POST users not working

I'm currently trying to implement Polar accesslink API on an app, but when trying to POST a new user I still get this error:
url: 'https://www.polaraccesslink.com/v3/users/',
status: 400,
statusText: 'Bad Request',
headers: Headers { [Symbol(map)]: [Object: null prototype] },
counter: 0
I already have the authorization token, which I know expires every 10min, and I'm using the service through a function that takes the token and the userID as parameters. This is my code:
postUser(memberId: string, token: string) {
const inputBody = { 'member-id': memberId };
const headers = {
'Content-Type': 'application/xml', 'Accept': 'application/json', 'Authorization': 'Bearer ' + token
};
const options = {
method: "POST",
headers: headers,
body: JSON.stringify(inputBody)
}
return new Promise<object>((resolve, reject) => {
fetch('https://www.polaraccesslink.com/v3/users', options)
.then(function(res: any) {
console.log(res)
return res.json();
}).then(function(body: any) {
console.log(body);
});
})
}
I'm implementing it the same way as it is specified in https://www.polar.com/accesslink-api/?javascript--nodejs#users but really don't know what might I be doing wrong. Thanks for helping me!.
I don't have experience with this specific API but i can see you send in the header Content-Type the value application/xml but the request body is JSON formatted.
Try send application/json in that header.
The Content-Type header is used in HTTP to specify the body mime type.
more info in: Content-Type HTTP Header - MDN
I also see this is the exact code in the sample but notice they have 2 sample requests and 2 sample results, one in XML and one in JSON each.
Any ideea why this POST gives 'invalid_Request'?
curl --location --request POST 'https://polarremote.com/v2/oauth2/token?grant_type=authorization_code&code=1f9edc0c5e60a0bab4fd3f1f00571a58' --header 'Authorization: Basic ... DA4OS05ZDc2LTJlNTQwZjFkZTc5ZA==' --header 'Content-Type: application/x-www-form-urlencoded' --header 'Accept: application/json'

How to write curl 'PUT' request in node with 'request' module

I have this curl request working.
curl -v "https://developer.api.autodesk.com/oss/v2/buckets/:bucketName/objects/"
-X "PUT" -H "Authorization: Bearer tokenGoesHere"
-H "Content-Type: application/octet-stream" -T "forupload.rvt"
How can I write this in node with npm request module.
I tried the following with 'request' and 'fs'.
I get back "Token is not provided in the request".
function uploadFile(bucketData){
var uri = 'https://developer.api.autodesk.com/oss/v2/buckets/' + bucketData['bucketKey'] + '/objects/'
var authorizationHeader = ' Bearer ' + bucketData['token'] // this works in other post/get requests
var contentTypeHeader = 'application/octet-stream'
var streamTarget = 'C:\\Users\\architech\\Desktop\\Forge Node\\Test.rvt';
console.log(uri)
console.log(authorizationHeader)
console.log(contentTypeHeader)
console.log(streamTarget)
// console output:
// https://developer.api.autodesk.com/oss/v2/buckets/bucketpqglrzt/objects/
// Bearer ....token....
// application/octet-stream
// C:\Users\architech\Desktop\Forge Node\Test.rvt
request.put(
{
url: uri,
// preambleCRLF: true,
// postambleCRLF: true,
multipart:
[
{
'Authorization': authorizationHeader,
'Content-Type': contentTypeHeader,
body: fs.createReadStream(streamTarget)
},
]
},
function(error, response, body){
if(!error){
console.log(body);
}else{
console.log(error);
}
})
}
After trying several approaches, while I couldn't reproduce your specific problem, the trouble I had was with the binary attachment loading properly. Because createReadStream() runs asynchronously, it doesn't really seem to work the way the request docs say it should when added to the multipart or formData keys. Not sure why this is?
I got it working first using http://requestb.in - comparing the curl request to the same request constructed with Node. Here is the final, working version:
var request = require('request')
fs = require('fs')
var options = {
uri: 'https://developer.api.autodesk.com/oss/v2/buckets/<yourBucket>/objects/<yourFile.ext>',
headers: {
'Content-Type': 'application/octet-stream',
'Authorization': 'Bearer <token>'
}
}
fs.createReadStream(__dirname + '/<fileName.ext>').pipe(request.put(options, function(err, response, body) {
console.log(body)
/*
{
"bucketKey" : "< yourBucket>",
"objectId" : "urn:adsk.objects:os.object:brandontestbucket2/skyscpr1.3ds",
"objectKey" : "<fileName.ext>",
"sha1" : "...redacted...",
"size" : 43791,
"contentType" : "application/octet-stream",
"location" : "https://developer.api.autodesk.com/oss/v2/buckets/< yourBucket>/objects/<fileName.ext>"
}
*/
}))

Nodejs twitter api does not return expected token

I am using nodejs to get bearer token i my code looks like
var fs = require("fs");
var https = require("https");
var querystring = require("querystring");
var bearer = "cunsomer_key:cunsomer_secret"
var base64ed = new Buffer(bearer).toString("base64");
var options = {
port: 443,
hostname: "api.twitter.com",
path: "/oauth2/token",
method: "post",
headers: {
Authorization: "Basic " + base64ed,
"Content-Type": "Content-Type: application/x-www-form-urlencoded;charset=UTF-8",
"User-Agent": "socialginie"
},
key: fs.readFileSync("./testssl.key"),
cert: fs.readFileSync("./testcert.cert"),
}
var req = https.request(options, res => {
res.on("data", d => {
console.log(d.toString());
})
})
req.on("error", e => {
console.log(e);
});
req.write(querystring.stringify({
"grant_type": 'client_credentials'
}))
req.end();
The expected return from the api is my bearer token and it does so in postman app but here i get the error {"errors":[{"code":170,"message":"Missing required parameter: grant_type","label":"forbidden_missing_parameter"}]}
Does anyone have any idea why api server cannot read the grant type
Your problem is just a typo. On this line:
"Content-Type": "Content-Type: application/x-www-form-urlencoded;charset=UTF-8",
you are specifying "Content-Type" as part of the header value.
When I use this curl command, sending your invalid Content-Type, I see the same error you do:
$ curl --data "grant_type=client_credentials" -H "Authorization: Basic <credentials-omitted>" -H "Content-Type:" -H "Content-Type: Content-Type: application/x-www-form-urlencoded;charset=UTF-8" https://api.twitter.com/oauth2/token
{"errors":[{"code":170,"message":"Missing required parameter: grant_type","label":"forbidden_missing_parameter"}]}
If I correct the header, I get a token:
$ curl --data "grant_type=client_credentials" -H "Authorization: Basic <credentials-omitted>" -H "Content-Type:" -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" https://api.twitter.com/oauth2/token
{"token_type":"bearer","access_token":"<token-omitted>"}

Resources