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



推荐阅读
  • PHP 5.2.5 安装与配置指南
    本文详细介绍了 PHP 5.2.5 的安装和配置步骤,帮助开发者解决常见的环境配置问题,特别是上传图片时遇到的错误。通过本教程,您可以顺利搭建并优化 PHP 运行环境。 ... [详细]
  • 网络运维工程师负责确保企业IT基础设施的稳定运行,保障业务连续性和数据安全。他们需要具备多种技能,包括搭建和维护网络环境、监控系统性能、处理突发事件等。本文将探讨网络运维工程师的职业前景及其平均薪酬水平。 ... [详细]
  • 从零开始构建完整手机站:Vue CLI 3 实战指南(第一部分)
    本系列教程将引导您使用 Vue CLI 3 构建一个功能齐全的移动应用。我们将深入探讨项目中涉及的每一个知识点,并确保这些内容与实际工作中的需求紧密结合。 ... [详细]
  • 深入解析TCP/IP五层协议
    本文详细介绍了TCP/IP五层协议模型,包括物理层、数据链路层、网络层、传输层和应用层。每层的功能及其相互关系将被逐一解释,帮助读者理解互联网通信的原理。此外,还特别讨论了UDP和TCP协议的特点以及三次握手、四次挥手的过程。 ... [详细]
  • 新冠肺炎疫情期间,各大银行积极利用手机银行平台,满足客户在金融与生活多方面的需求。线上服务不仅激活了防疫相关的民生场景,还推动了银行通过互联网思维进行获客、引流与经营。本文探讨了银行在找房、买菜、打卡、教育等领域的创新举措。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 如何配置Unturned服务器及其消息设置
    本文详细介绍了Unturned服务器的配置方法和消息设置技巧,帮助用户了解并优化服务器管理。同时,提供了关于云服务资源操作记录、远程登录设置以及文件传输的相关补充信息。 ... [详细]
  • 尽管某些细分市场如WAN优化表现不佳,但全球运营商路由器和交换机市场持续增长。根据最新研究,该市场预计在2023年达到202亿美元的规模。 ... [详细]
  • 网络攻防实战:从HTTP到HTTPS的演变
    本文通过一系列日记记录了从发现漏洞到逐步加强安全措施的过程,探讨了如何应对网络攻击并最终实现全面的安全防护。 ... [详细]
  • 在现代网络环境中,两台计算机之间的文件传输需求日益增长。传统的FTP和SSH方式虽然有效,但其配置复杂、步骤繁琐,难以满足快速且安全的传输需求。本文将介绍一种基于Go语言开发的新一代文件传输工具——Croc,它不仅简化了操作流程,还提供了强大的加密和跨平台支持。 ... [详细]
  • 本文深入探讨了计算机网络的基础概念和关键协议,帮助初学者掌握网络编程的必备知识。从网络结构到分层模型,再到传输层协议和IP地址分类,文章全面覆盖了网络编程的核心内容。 ... [详细]
  • 在众多不为人知的软件中,这些工具凭借其卓越的功能和高效的性能脱颖而出。本文将为您详细介绍其中八款精品软件,帮助您提高工作效率。 ... [详细]
  • 使用 GitHub、JSDelivr、PicGo 和 Typora 构建高效的图床解决方案
    本文详细介绍了如何利用 GitHub 仓库、JSDelivr CDN、PicGo 图床工具和 Typora 编辑器,搭建一个高效且免费的图床系统。通过此方案,用户可以轻松管理和上传图片,并在 Markdown 文档中快速插入高质量的图片链接。 ... [详细]
  • 本文详细介绍了网络存储技术的基本概念、分类及应用场景。通过分析直连式存储(DAS)、网络附加存储(NAS)和存储区域网络(SAN)的特点,帮助读者理解不同存储方式的优势与局限性。 ... [详细]
  • 本文作者分享了在阿里巴巴获得实习offer的经历,包括五轮面试的详细内容和经验总结。其中四轮为技术面试,一轮为HR面试,涵盖了大量的Java技术和项目实践经验。 ... [详细]
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社区 版权所有