multiparty 中间件源码解析

本文详述了作者在处理表单上传数据时遇到的问题,以及如何通过研究multiparty中间件来解决。文章重点解析了multiparty中间件的核心方法,包括对可写流`_write`方法的重写,以及pipe机制在表单解析中的作用。通过实例展示了multiparty如何处理`multipart/form-data`类型的表单数据,以供遇到类似问题的开发者参考。
摘要由CSDN通过智能技术生成

写这篇文章的起因是我在尝试使用 Node 的原生模块去接收表单上传的数据并分割过滤表单文件与数据时,由于一开始是对可写流拼接 buffer 转的字符串进行轮询,导致只能接收文件格式为 txt 的文本文件,在接收图片或其他文件格式时却发生了写入后无法打开的情况,而 txt 文件的中文也会乱码,这是因为 buffer 在转字符串时默认为 utf8 格式,进行写入后,无法识别中文,而其他文件格式在无法判别 encoding 的情况下,在编码的转换截取中极其容易被截漏或截错。

由于在网上根本没有一篇真正告诉你怎么用 Node 原生代码去处理表单的过程,即使有的也都是错的,我是都测试过的,因为它们的原理大都跟我一样都是对字符串进行轮询,这是错误的处理方式。

所以经过对各大中间件进行追本溯源的过程后,我找到了专门用来处理表单(MIME 为 multipart/form-data)的中间件 multiparty,遂决定看懂后一定要自己过滤一遍并写出文章给其他跟我有类似问题的童鞋参考。

文章已同步至我的 github https://github.com/YOLO0927/multiparty-source-code-analysis

下面放上解析

本中间件的构造函数 Form 与直接暴漏的过滤函数 Form.prototype.parse 都十分简单,我不在此赘述,因为这个 2 个环节非常简单,稍微有点基础的同学都应该能看懂,重要的是我下面分析的 Form.prototype._write 方法,这是一个对可写流内部传入底层写入方法的一个重写,在包内你不会找到任何一个地方有调用这个方法,所以大家一定要看 Node 文档 Stream 模块中对 writable._write 方法的描述

writable._write

大家一定要注意我标记的三行解释,它充分的告诉了我们,这个方法是内部方法,只要我们利用继承,将 From 继承 Stream.Writable 即可实现,下面放上我对 Form.prototype._write 的整段分析,而整个中间件的核心也就是这个方法了,其他多为此方法的辅助函数

首先童鞋们一定要注意源码 line:50 位置的util.inherits(Form, stream.Writable)
大家看到这个方法可能会找不到此包在哪里调用,其实这关键的操作在于包一旦引入便进行了 Form 继承 写入流 stream.Writable 的操作,原本可写流的内部方法 writable._write 被 Form 原型中的 _write 先传入底层,再次实例化 Form 调用 write 时将会使用的底层函数会是我们写的 Form.prototype._write由此我们不但使 Form 的实例化对象具有写入流,并且还重写了写入的逻辑
达到每次 transform 流都会调用我们在这里重写的 _write 方法,在 parse 方法的最后调用 req.pipe(self) 时由管道流将 req 写入 Form 实例时触发调用

Form.prototype._write = function(buffer, encoding, cb) {
   
  if (this.error) return;

  var self = this;
  var i = 0;
  var len = buffer.length;  // 轮询表单数据 buffer 的长度
  var prevIndex = self.index;
  var index = self.index;
  var state = self.state; // 轮询表单数据 buffer 时的状态
  var lookbehind = self.lookbehind;
  var boundary = self.boundary; // 通过 parse 方法里拿到的分隔符 boundary
  var boundaryChars = self.boundaryChars;
  var boundaryLength = self.boundary.length;
  var boundaryEnd = boundaryLength - 1;
  var bufferLength = buffer.length;
  var c;
  var cl;
  // boundary = \r\n------XXXXXXXXXXXXXX
  // 全过程对 buffer 进行循环,所以不存在任何文件的格式问题,如果是对 buffer.toString 后的字符串进行循环,是无法判断收集到的文件是何种格式的,这点是核心关键
  // 所以我们只能对 buffer 进行循环,在循环中根据 buffer 字节码判断 : - LF RF 等符号来判断到底循环到哪个步骤,这是过滤难点
  for (i = 0; i < len; i++) {
   
    c = buffer[i];
    switch (state) {
   
      case START:
        // 记住 index 永远最大只是 boundary 的长度
        index = 0;
        state = START_BOUNDARY;
        /* falls through */
      case START_BOUNDARY:
        console.log(`${
     i}, ${
     c} === ${
     boundary[index+2]}, ${
     index}, ${
     boundary[index]}`)
        if (index === boundaryLength - 2 && c === HYPHEN) {
   
          index = 1;
          state = CLOSE_BOUNDARY;
          break;
        } else if (index === boundaryLength - 2) {
   
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值