我正在尝试做一件基本的事情:使用
FormData
API 发送表单并在 NodeJS 中解析它。
在搜索了一个小时后才使用 ExpressJS 和其他框架找到答案,我认为它值得有自己的问题:
我有这个 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));
在 NodeJS 中我正在做:
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);
});
但是在
data.message
中,我得到了 Webkit Boundary 事物(来自多部分表单数据格式)而不是预期的消息。
是否有另一个内置库来解析多部分发布数据而不是querystring
?如果不是那么如何手动完成(高级,无需阅读 Express 源代码)?
您
Buffer.from
将正文发布到string
,然后通过.split
请求标头中提供的boundary
值来Content-Type:
它。这给出了数组中的 body
部分。
现在处理它们以确定哪个是文件,哪个是 key:val 对。下面的代码说明了这一点。
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();
然后是处理函数
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;
}
}
我也遇到同样的问题;因为我使用的网络服务器是用 C++ 和 Javascript API 编写的(与 Node.js 不同,尽管符合标准)。所以我必须自己做轮子。
然后我遇到了这个 npm 模块 parse-multipart-data。它可以工作,你可以阅读源代码,它只是一个文件,作者非常清楚地解释了它是什么,以及如何做到这一点。
附注当你走得更高时 - 你需要走得更低。有经验的程序员会明白我的意思:)
我来这里寻找一个函数,可以在无法使用
req.formData
时替换使用 fetch(req)
时可用的 fetch
方法。正如这里的原始问题一样,我只对返回的键:值字符串感兴趣。但与这里不同的是,我希望结果的格式与用 .formData
转换的 Object.fromEntries()
相同。这里接受的答案加上 https://www.section.io/engineering-education/a-raw-nodejs-rest-api-without-frameworks-such-as-express/的
getReqData(req)
函数帮助我做出了这个(打字稿)功能对我有用。
function getFormData(request:any) {
return new Promise<{[key:string]:string}>((resolve, reject) => {
try {
let contentTypeHeader = request.headers["content-type"];
let boundary = "--" + contentTypeHeader.split("; ")[1].replace("boundary=","");
let body = [] as any;
request.on('data', (chunk:any) => { body.push(chunk) });
request.on('end', () => {
const formDataSubmitted: {[key:string]:string} = {};
body = Buffer.concat(body).toString();
let bodyParts = body.split(boundary);
bodyParts.forEach(function(val:any){
val = val.replace("Content-Disposition: form-data; ","").split(/[\r\n]+/);
const formDataEntry = returnKeyValObj(val);
if (formDataEntry) Object.assign(formDataSubmitted, formDataEntry);
})
if (Object.keys(formDataSubmitted).length) resolve(formDataSubmitted);
});
} catch (error) {
reject(error);
}
});
}
function returnKeyValObj(arr:string){
if (!Array.isArray(arr)) {return false};
let propKey = '';
let propVal = '';
const formDataEntries: {[key:string]:string} = {};
arr.forEach((val,index) => {
// each key has escaped "" surrounding it like \"key\"
if(val.includes('name=\"')){
propKey = arr[index].split('name=\"')[1].slice(0, -1); //slice off trailing "
propVal = arr[index + 1]
}
if (propKey) formDataEntries[propKey] = propVal;
})
if (Object.keys(formDataEntries).length)
return formDataEntries;
return false;
}
所以,而不是
const data = await req.formData();
const dataEntries = Object.fromEntries(data.entries());
我可以用
const dataEntries = await getFormData(req);
而且,我的其余代码保持不变。