NodeJS - 如何在没有框架的情况下解析多部分表单数据?

问题描述 投票:0回答:3

我正在尝试做一件基本的事情:使用

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 源代码)?

javascript ajax node.js forms http-post
3个回答
5
投票

Buffer.from
将正文发布到
string
,然后通过
.split
请求标头中提供的
boundary
值来
Content-Type:
它。这给出了数组中的
body
部分。

现在处理它们以确定哪个是文件,哪个是 key:val 对。下面的代码说明了这一点。

NodeJS / 服务器端示例

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;
  }
}


2
投票

我也遇到同样的问题;因为我使用的网络服务器是用 C++ 和 Javascript API 编写的(与 Node.js 不同,尽管符合标准)。所以我必须自己做轮子。

然后我遇到了这个 npm 模块 parse-multipart-data。它可以工作,你可以阅读源代码,它只是一个文件,作者非常清楚地解释了它是什么,以及如何做到这一点。

附注当你走得更高时 - 你需要走得更低。有经验的程序员会明白我的意思:)


0
投票

我来这里寻找一个函数,可以在无法使用

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);

而且,我的其余代码保持不变。

© www.soinside.com 2019 - 2024. All rights reserved.