I'm trying to do a basic thing: to send a form using FormData
API and parse it in NodeJS.
After searching SO for an hour only to find answers using ExpressJS and other frameworks I think it deserves its own question:
I have this HTML:
<form action="http://foobar/message" method="POST">
<label for="message">Message to send:</label>
<input type="text" id="message" name="message">
<button>Send message</button>
</form>
JS:
var xhr = new XMLHttpRequest();
xhr.open('POST', 'http://foobar/message');
xhr.send(new FormData(form));
In NodeJS I'm doing:
var qs = require('querystring');
var requestBody = '';
request.on('data', function (chunk) {
requestBody += chunk;
});
request.on('end', function () {
var data = qs.parse(requestBody);
console.log(data.message);
});
But in data.message
I get the Webkit Boundary thing (from the multipart form data format) instead of the expected message.
Is there another built-in lib to parse multipart post data instead of querystring
? If not then how to do it manually (high-level, without reading the source code of Express)?
You Buffer.from
POST body to string
then .split
it by the boundary
value provided in the Content-Type:
request header. This gives your body
parts in an array.
Now process them to determine which is a file and which is a key:val pair. The below code illustrates this.
const SERVER = http.createServer(async function(request, response) {
let statusCode = 200;
if(request.url === '/app') {
let contentTypeHeader = request.headers["content-type"];
let boundary = "--" + contentTypeHeader.split("; ")[1].replace("boundary=","");
if (request.method == 'POST') {
let body = [];
request.on('data', chunk => {
body.push(chunk)
});
request.on('end', async () => {
body = Buffer.concat(body).toString();
let bodyParts = body.split(boundary);
let result = [];
bodyParts.forEach(function(val,index){
val = val.replace("Content-Disposition: form-data; ","").split(/[\r\n]+/);
if(isFile(val)){
result.push(returnFileEntry(val))
}
if(isProperty(val)){
result.push(returnPropertyEntry(val))
}
})
console.log(result)
});
response.end();
}
response.end();
Then the processing functions
function returnPropertyEntry(arr){
if (!Array.isArray(arr)) {return false};
let propertyName = '';
let propertyVal = undefined;
arr.forEach(function(val,index){
if(val.includes("name=")){
propertyName = arr[index].split("name=")[1];
propertyVal = arr[index + 1]
}
})
return [propertyName,propertyVal];
}
function returnFileEntry(arr){
if (!Array.isArray(arr)) {return false};
let fileName = '';
let file = undefined;
arr.forEach(function(val,index){
if(val.includes("filename=")){
fileName = arr[index].split("filename=")[1];
}
if(val.toLowerCase().includes("content-type")){
file = arr[index + 1];
}
})
return [fileName,file];
}
function isFile(part){
if(!Array.isArray(part)){return false};
let filenameFound = false;
let contentTypeFound = false;
part.forEach(function(val,index){
if (val.includes("filename=")){
filenameFound = true;
}
if (val.toLowerCase().includes("content-type")){
contentTypeFound = true;
}
});
part.forEach(function(val,index){
if (!val.length){
part.splice(index,1)
}
});
if(filenameFound && contentTypeFound){
return part;
} else {
return false;
}
}
function isProperty(part){
if(!Array.isArray(part)){return false};
let propertyNameFound = false;
let filenameFound = false;
part.forEach(function(val,index){
if (val.includes("name=")){
propertyNameFound = true;
}
});
part.forEach(function(val,index){
if (val.includes("filename=")){
filenameFound = true;
}
});
part.forEach(function(val,index){
if (!val.length){
part.splice(index,1)
}
});
if(propertyNameFound && !filenameFound){
return part;
} else {
return false;
}
}