热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:Node.js如何解析Form上传?

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Node.js如何解析Form上传?相关的知识,希望对你有一定的参考价值。本

篇首语:本文由编程笔记#小编为大家整理,主要介绍了Node.js如何解析Form上传?相关的知识,希望对你有一定的参考价值。




本文为作者投稿。


NPM 和 GitHub 里的开源组件帮我们解决了很多繁琐的工作,但是也让我们失去了很多深入技术细节的机会。在现有组件无法满足我们需求的时候,就需要我们来自己动手丰衣足食了。

作者前段时间遇到了一个需要手动解析 Form 表单上传的机会,也借此为各位解析一下 Node.js 解析 Form 上传的实现细节。




背景


半年前微信小程序的推出,掀起了一股小程序开发的热潮,作者一样收到了来自产品妹子的小程序开发需求。

需求中涉及到照片 / 视频上传的功能,而我们本身是全国最大的照片上传服务产品 --QQ 空间相册,我们各主要上传渠道的上传都采用了 socket 分片上传技术,包括客户端和 PC 浏览器端。微信小程序提供的上传接口只能是 Form 表单上传,且又没有提供读取本地文件内容的接口,打消了我们通过客户端分片上传的念头。

所以现在的问题是从小程序发出的是 Form 表单的上传请求,后端提供的是基于 socket 的分片上传服务,我们需要把两者对接起来。

我们的解决方案就是在 Node.js 层加一个适配接入,接收来自小程序的 Form 上传请求,然后解析图片内容,并分片上传到后端。




方案


现在的任务就分为两部分:



  1. 解析 Form 上传表单,将图片 / 视频内容解析出来;


  2. 将图片 / 视频内容拆分成固定大小的小片数据包,通过后端提供的私有协议接口上传;


那在这里我们又面临了一个选择,是等整个 Form 表单接收完成再进行分片上传,还是收到 Form 表单就开始上传呢?

前者我们可以直接引入现成的 multipart 的 Node.js 解析组件,临时存储到本地或者内存,然后进行上传,但是缺点是整个上传过程变成了两个串行过程,上传延时会增加,另外我们同时支持图片和视频的上传,会导致 Node.js 侧的内存占用比较高。

而后者则是尽量同步,接收到图片 / 视频 part 我们就可以开始上传,只是没有刚好满足我们需求的组件来支持。基于以上对比我们选择后面的方案,自己来解析 Form 上传表单,整个上传流程可以看下面的时序图。

Node.js如何解析Form上传?




Form 表单


Form 表单我们平时并不少接触,但是需要自己去解析的场景并不多,在解析 Form 表单前,我们先简单看一下 Form 表单的结构是怎么样的。

--${bound}
Content-Disposition: form-data; name="Filename"
IMG-0719.jpg
--${bound}
Content-Disposition: form-data; name="filedata"; filename="IMG-0719.jpg"
Content-Type: application/octet-stream
file content
--${bound}
Content-Disposition: form-data; name="Upload"
Submit Query
--${bound}--

可以看到每一个表单 field 都是以一个 --{bound} 开头的,然后是一些属性信息,属性和内容之间都有一个空行,整个 Form 表单则以 --{bound}-- 结束。那 bound 是一个什么存在呢?答案就在 Http 请求头里,因为 Form 请求的 Content-type 一般是这样的:

Content-Type: multipart/form-data; boundary=--49348984387434655602146

--49348984387434655602146 就是我们要找的 bound,作用就是将 Form 表单里的 field 分隔开来,所以这个串一般比较复杂,并且有足够的长度。

再整理一下我们的思路:



  1. 先从 Http 请求头里找到 Content-type,并渠道 boundary 的内容,该内容为表单分隔符;


  2. 对表单内容从前往后查找 --{bound}--{bound}----{bound}--{bound}或者 --{bound}-- 之间的内容就是单个表单 field 内容;


  3. 单个表单内容第一个空行之前的部分为表单头部,头部以换行符分隔,空行后面的内容则为 field 内容;





解析


Form 表单是流式的,表单 field 的查找其实是线性的,且是有状态的,整个解析过程我们可以看成是一个有限状态机,而 Form 请求的 data 事件则是这个状态机的驱动器。

Node.js如何解析Form上传?

可以看到解析器可能存在 5 个状态,分别为:



  1. 空闲,即初始状态;


  2. 解析到 field,即进入到待解析 field 内容的状态,后面的内容都存入临时 buffer,每次 data 事件都会更新这个 buffer,并触发一次检查是否有一个空行存在,即一组\r\n\r\n


  3. 解析 field 头部,在遇到\r\n\r\n之后,临时 buffer 空行前面的内容即为 field 头部,根据换行拆分出单个头部,基于字符串解析就能获取到头部信息,这里就不详解了;


  4. 解析 field 内容,再遇到\r\n\r\n之后,临时 buffer 空行后面的内容就包含了 field 的内容,但是要注意,空行后面的内容并不是全都是当前 field 的内容,我们需要对临时 buffer 空行后面的内容中寻找--{bound}或者--{bound}--,如果查找到--{bound},则分隔符前面的内容为当前 field 内容,分隔符后面的内容为下一个 field,那解析器则进入到状态 2 《解析到 field》;如果查找到--{bound}--,则意味着当前 field 为 Form 表单最后一个 field,分隔符前面的内容为 field 内容,同时进入到状态 5 《Form 结束》 ;如果临时 buffer 里--{bound}--{bound}--都没有找到,意味着当前 field 内容还没有接受完成,那么空行后面的内容均为 field 内容,每次 data 事件则会触发上述状态转换查询逻辑进行一遍;


  5. Form 结束,即整个 Form 表单解析完成了。


再回到我们的上传场景中,我们需要将收到的图片 / 视频内容同步打包传给后端,意味着在状态 4 的处理中,如果 field 是图片 / 视频内容,就需要将收到的文件部分打包传输。在查找到--{bound}--{bound}--的情况下比较好处理,分隔符前的内容都是文件内容,直接拆分打包传输就可以了,但是在--{bound}--{bound}--都没有找到的时候,我们将哪部分内容进行打包呢?

虽然没有查找到这两种分隔符,但是有可能当前 buffer 的结尾已经包含了部分分隔符的内容,所以我们需要预留出一部分 buffer,这段 buffer 前面的部分认为是文件内容是安全的,这段 buffer 只能等与后续收到的 data 拼接在一起才知道是不是文件内容。因为预留的这段 buffer 的目的是为了防止将分隔符当做文件内容传输了,所以预留的这段 buffer 长度应该不短于 buffer 长度减一,即 buffer 的长度为bound.length + 4 - 1,4 代表 4 个-长度。




总结


本文只是根据实际需求和应用场景介绍了 Form 表单的解析细节,并附带介绍了在 Form 表单流式接收的过程中流式代理上传的方案。

目前前端技术栈越来越丰富,前端社区越来越活跃,我们有海量的开源组件,唾手可得的框架库以及全链路支持的工具集,但是我们仍然需要掌握能探究技术细节的能力,在关键技术节点上往往还是要回归到技术细节本身。





前端之巅



关注「前端之巅」,紧跟前端发展,共享一线技术。各位淀粉投稿请发邮件到 editors@cn.infoq.com,注明“前端之巅投稿”。



推荐阅读
  • 本文讨论了如何使用GStreamer来删除H264格式视频文件中的中间部分,而不需要进行重编码。作者提出了使用gst_element_seek(...)函数来实现这个目标的思路,并提到遇到了一个解决不了的BUG。文章还列举了8个解决方案,希望能够得到更好的思路。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 本文介绍了在mac环境下使用nginx配置nodejs代理服务器的步骤,包括安装nginx、创建目录和文件、配置代理的域名和日志记录等。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
  • 本文介绍了Swing组件的用法,重点讲解了图标接口的定义和创建方法。图标接口用来将图标与各种组件相关联,可以是简单的绘画或使用磁盘上的GIF格式图像。文章详细介绍了图标接口的属性和绘制方法,并给出了一个菱形图标的实现示例。该示例可以配置图标的尺寸、颜色和填充状态。 ... [详细]
  • 本文详细介绍了git常用命令及其操作方法,包括查看、添加、提交、删除、找回等操作,以及如何重置修改文件、抛弃工作区修改、将工作文件提交到本地暂存区、从版本库中删除文件等。同时还介绍了如何从暂存区恢复到工作文件、恢复最近一次提交过的状态,以及如何合并多个操作等。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 如何在php文件中添加图片?
    本文详细解答了如何在php文件中添加图片的问题,包括插入图片的代码、使用PHPword在载入模板中插入图片的方法,以及使用gd库生成不同类型的图像文件的示例。同时还介绍了如何生成一个正方形文件的步骤。希望对大家有所帮助。 ... [详细]
  • 2016 linux发行版排行_灵越7590 安装 linux (manjarognome)
    RT之前做了一次灵越7590黑苹果炒作业的文章,希望能够分享给更多不想折腾的人。kawauso:教你如何给灵越7590黑苹果抄作业​zhuanlan.z ... [详细]
  • AFNetwork框架(零)使用NSURLSession进行网络请求
    本文介绍了AFNetwork框架中使用NSURLSession进行网络请求的方法,包括NSURLSession的配置、请求的创建和执行等步骤。同时还介绍了NSURLSessionDelegate和NSURLSessionConfiguration的相关内容。通过本文可以了解到AFNetwork框架中使用NSURLSession进行网络请求的基本流程和注意事项。 ... [详细]
  • 使用freemaker生成Java代码的步骤及示例代码
    本文介绍了使用freemaker这个jar包生成Java代码的步骤,通过提前编辑好的模板,可以避免写重复代码。首先需要在springboot的pom.xml文件中加入freemaker的依赖包。然后编写模板,定义要生成的Java类的属性和方法。最后编写生成代码的类,通过加载模板文件和数据模型,生成Java代码文件。本文提供了示例代码,并展示了文件目录结构。 ... [详细]
  • 流数据流和IO流的使用及应用
    本文介绍了流数据流和IO流的基本概念和用法,包括输入流、输出流、字节流、字符流、缓冲区等。同时还介绍了异常处理和常用的流类,如FileReader、FileWriter、FileInputStream、FileOutputStream、OutputStreamWriter、InputStreamReader、BufferedReader、BufferedWriter等。此外,还介绍了系统流和标准流的使用。 ... [详细]
author-avatar
赖雨蓉744_128
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有