热门标签 | 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,注明“前端之巅投稿”。



推荐阅读
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 解决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手机。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • 本文讨论了如何使用GStreamer来删除H264格式视频文件中的中间部分,而不需要进行重编码。作者提出了使用gst_element_seek(...)函数来实现这个目标的思路,并提到遇到了一个解决不了的BUG。文章还列举了8个解决方案,希望能够得到更好的思路。 ... [详细]
  • loader资源模块加载器webpack资源模块加载webpack内部(内部loader)默认只会处理javascript文件,也就是说它会把打包过程中所有遇到的 ... [详细]
  • Vue基础一、什么是Vue1.1概念Vue(读音vjuː,类似于view)是一套用于构建用户界面的渐进式JavaScript框架,与其它大型框架不 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
  • 基于Socket的多个客户端之间的聊天功能实现方法
    本文介绍了基于Socket的多个客户端之间实现聊天功能的方法,包括服务器端的实现和客户端的实现。服务器端通过每个用户的输出流向特定用户发送消息,而客户端通过输入流接收消息。同时,还介绍了相关的实体类和Socket的基本概念。 ... [详细]
  • 必须先赞下国人npm库作品:node-images(https:github.comzhangyuanweinode-images),封装了跨平台的C++逻辑,形成nodejsAP ... [详细]
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社区 版权所有