zoukankan      html  css  js  c++  java
  • 解析 form-data 数据,实现 formidable 函数的功能

    首先,"multipart/form-data" 编码格式的数据是不能用 querystring 或者 body-parser 来解析的,需要借助 formidable 或者其他的模块来解析。再学习这些第三方模块时我就在想,它们是怎么实现解析数据的呢?

    如果是 "application/x-www-form-urlencoded" 格式的数据很好解决,请求体中的数据都是以 ”&“ 作为连接,特殊字符转换为ASCII HEX,比如参数值中的 "&" 编码成 "%26" ,空格用 ”+“ 表示,但是 "multipart/form-data" 数据就有些麻烦了,因为它的请求体是这样的:

    这里我将请求体 Buffer 类型的数据转换成了 utf-8 格式的字符串,数据以 boundary 作为分隔,文件以二进制数据传输,为了解析这段数据,声明函数 "function formParse( )" ,函数需要传入两个参数,请求体中的 data,和请求头中的 boundary 。代码如下:

    module.exports = function formParse(body, boundary) {
        //将Buffer类型的数据转化成binary编码格式的字符串
        let formStr = Buffer.concat(body).toString('binary');
        let formarr = formStr.split(boundary);
        //去掉首尾两端的无用字符
        formarr.shift();
        formarr.pop();
        //存储普通key-value
        let filed = {};
        //存储文件信息
        let file = {};
        for (let item of formarr) {
            //去除首尾两端的非信息字符
            item = item.slice(0, -2).trim();
            //value存储input输入的值
            let value = '';
            //不同操作系统换行符不同,用变量a声明特殊分割点位的下标
            let a;
            if ((a = item.indexOf('
    
    ')) != -1) {
                value = item.slice(a + 4);
            } else if ((a = item.indexOf('
    
    ')) != -1) {
                value = item.slice(a + 2);
            } else if ((a = item.indexOf('
    
    ')) != -1) {
                value = item.slice(a + 2);
            }
            //正则匹配,组中内容
            let key = item.match(/name="([^"]+)"/)[1];
            if (item.indexOf('filename') == -1) {
                if (!(key in filed)) {
                    //将二进制字符串转化成utf8格式的字符串
                    filed[key] = Buffer.from(value,'binary').toString('utf8');
                } else {
                    //将复选框的数据放入一个数组中
                    let arr = [];
                    filed[key] = arr.concat(filed[key], value);
                }
            } else {
                let filename_b = item.match(/filename="([^"]*)"/)[1];
                //解决中文文件名乱码的问题
                let filename = Buffer.from(filename_b,'binary').toString();
                let contentType = item.slice(item.indexOf('Content-Type:'), a);
                let obj = {};
                obj.filename = filename;
                obj.contentType = contentType;
                obj.binaryStream = value;//文件的二进制数据
                let arr = [];
                if (!(key in file)) {
                    arr.push(obj);
                    file[key] = arr;
                } else {
                    //用于多文件上传
                    file[key] = arr.concat(file[key], obj);
                }
            }
        }
        return { filed, file };
    }
    

    然后,再使用这个函数,

    const http = require('http');
    const path = require('path');
    const fs = require('fs');
    const { promisify } = require('util');
    const formParse = require('./formParse');
    
    const server = http.createServer();
    const readFile = promisify(fs.readFile);
    const writeFile = promisify(fs.writeFile);
    
    server.on('request', async (req, res) => {
        if (req.url == '/ajax') {
    
            if (req.method == 'GET') {
                let pathName = path.join(__dirname, 'ajax.html');
                let data = await readFile(pathName, 'utf8');
                res.end(data);
            }
    
            if (req.method == 'POST') {
                // req.setEncoding('binary');
                let body = [];
                let boundary = req.headers['content-type'].split('boundary=')[1];
                //console.log(boundary);
                req.on('data', (chunk) => {
                    body.push(chunk);
                }).on('end', async () => {
                    let { filed, file } = formParse(body, boundary);
                    console.log(filed);
                    console.log(file);
                    try {
                        //文件输入框的name="files"
                        let fileArr = file.files;
                        for(let f of fileArr){
                            await writeFile(f.filename, f.binaryStream, 'binary');
                            console.log(`文件"${f.filename}"写入成功`);
                        }
                    } catch (error) {
                        console.log(error);
                        res.statusCode = 500;
                        res.end();
                    }
                    res.end('请求成功');
                })
            }
    
        } else {
            res.end('not found');
        }
    });
    
    server.listen(8080);
    console.log('服务器启动成功');
    

    关于函数 formParse 的几点说明:

    1. formParse 将非文件的参数存入再 "field" 对象中,文件参数存在 "file" 对象中,文件以二进制字符串的形式存在,这样的话,文件存在哪,以什么名字或者格式存储,你可以自己设置。在命令行窗口打印 filed 和 file 对象,格式如下:
    { username: '张三', password: '123', book: [ 'book1', 'book2' ] }
    {
      files: [
        {
          filename: '测试文档.txt',
          contentType: 'Content-Type: text/plain',
          binaryStream: 'test text --En
    中文字符串 --zh-cn'
        }
      ]
    }
    
    1. 文件在写入时,需要设置编码格式为 "binary" ,因为 binaryStream 中存的是二进制的字符串。

    2. 在函数中,我是先这样处理 Buffer 类型的数据的:

      let formStr = Buffer.concat(body).toString('binary');
      

      这样的话,会造成中文的字符串乱码,所以文件名,参数值在后面都要转换成 utf8 格式,当然,这个在函数内部实现了。

    3. 如果先将 Buffer 类型的数据转换成 utf8 格式的字符串,再将文件转换回 binary 编码,纯文本文件没什么问题,但是图片文件就会读不出来,具体原因不知道,可能这种 转换不可逆吧

    4. 我在一些博客上看见这样的处理方式:在读取请求体数据之前,设置

      req.setEncoding('binary');
      

      因为我对 node 还不是很了解,到 node.js 官网去查了,了解到这里的 req 是回调函数中的参数,是一个 IncomingMessage 对象,它继承了 stream.Readable 类,所以设置读取时的数据编码为 binary。

    5. 还有一个小细节,分隔数据的字符串是 "--"+boundary,结尾也会有两个”-“:

    关于 formParse 的代码 https://github.com/Arduka/ajax-formParser

  • 相关阅读:
    [矩阵快速幂专题]
    [hdoj6415 Rikka with Nash Equilibrium][dp]
    [codeforces][dp]
    [2019年湘潭大学程序设计竞赛(重现赛)H chat][背包dp]
    [一道区间dp][String painter]
    [hdoj4578][多延迟标记的线段树]
    [cf1138BCircus][枚举,列等式]
    [Assign the task][dfs序+线段树]
    Codeforces round 419 div2 补题 CF 816 A-E
    Educational Codeforces Round 23 A-F 补题
  • 原文地址:https://www.cnblogs.com/arduka/p/13128809.html
Copyright © 2011-2022 走看看