nodejs-http 对form表单上传文件数据的解析过程

前几天碰到了一个需求,允许接收前端用户上传的文件。

当时为了解决问题索性就上github搜了下,找了一个基于nodejs的开发插件。

后来功能实现后觉得意犹未尽,于是自己想试试去写一个类似功能的插件,方便以后拓展,然后就这么开始了。

先来说说应用层的http,数据从前端是怎么被它包装然后传到服务器的。

我们可以在浏览器中查看我们发一个请求的时候包什么格式的,例如我们访问百度时得到的请求包内容:

Remote Address:180.97.33.107:443
Request URL:https://www.baidu.com/
Request Method:GET
Status Code:200 OK
Request Headers
:host:www.baidu.com
:method:GET
:path:/
:scheme:https
:version:HTTP/1.1
accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
accept-encoding:gzip, deflate, sdch
accept-language:zh-CN,zh;q=0.8,en;q=0.6
cookie:BAIDUPSID=9193988659A757F51540F21C3A7DF43B; locale=zh;
referer:http://baidu.com/
user-agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36
Response Headers
bdpagetype:2
bdqid:0x9ca30ab3000c253d
bduserid:1618160418
cache-control:private
content-encoding:gzip
content-type:text/html
date:Sat, 04 Apr 2015 07:42:42 GMT
expires:Sat, 04 Apr 2015 07:42:42 GMT
server:bfe/1.0.8.1
set-cookie:BDSVRTM=124; path=/
set-cookie:BD_HOME=1; path=/
set-cookie:H_PS_PSSID=11193_1450_13074_10901_12867_13322_12691_13348_12723_12797_12737_13355_13324_13210_13162_13257_13031_8498; path=/; domain=.baidu.com
set-cookie:__bsi=18001627764121693250_31_38_R_N_124_0303_C02F_Y_I_I; expires=Sat, 04-Apr-15 07:42:47 GMT; domain=www.baidu.com; path=/
status:200 OK
version:HTTP/1.1

相同的,服务器在接收到前端发来的请求时,经过一些逻辑处理,也会对前端发送一个数据包,包头格式如下:

bdpagetype:2
bdqid:0x9ca30ab3000c253d
bduserid:1618160418
cache-control:private
content-encoding:gzip
content-type:text/html
date:Sat, 04 Apr 2015 07:42:42 GMT
expires:Sat, 04 Apr 2015 07:42:42 GMT
server:bfe/1.0.8.1
set-cookie:BDSVRTM=124; path=/
set-cookie:BD_HOME=1; path=/
set-cookie:H_PS_PSSID=11193_1450_13074_10901_12867_13322_12691_13348_12723_12797_12737_13355_13324_13210_13162_13257_13031_8498; path=/; domain=.baidu.com
set-cookie:__bsi=18001627764121693250_31_38_R_N_124_0303_C02F_Y_I_I; expires=Sat, 04-Apr-15 07:42:47 GMT; domain=www.baidu.com; path=/
status:200 OK
version:HTTP/1.1

那么数据包的核心就是服务器返回给客户端的内容,也就是包体,一般情况下,我们在写代码与客户端交互式不会去单独处理包头的内容(由http底层处理,淡然也可以人工干涉),我们只关心返回给前端什么样的数据也就是包体。

下面我们直接说说常用的post方法,如果你写过一点PHP,那么你肯定记得,在PHP里面,进行文件上传的时候,我们可以直接使用全局变量 $_FILE[‘name’ ]来获取已经被临时存储的文件信息。

但是实际上,POST数据实体,会根据数据量的大小进行分包传送,然后再从这些数据包里面分析出哪些是文件的元数据,那些是文件本身的数据。

PHP是底层做了封装,但是在nodejs里面,这个看似常见的功能却是需要自己来实现的。这篇文章主要就是介绍如何使用nodejs来解析post数据。

简单的说就是我们通过nodejs的http模块得到的文件上传数据是一个半成品,我们需要对数据进行拆分,找到那些是包头,哪些是包体。如果对接收到的数据直接进行拼接,那么结果就变成了这样子。

包头,包体都在一起。

关于content-type

  • get请求的headers中没有content-type这个字段post 的 content-type 有两种

    • application/x-www-form-urlencoded

      这种就是一般的文本表单用post传地数据,只要将得到的data用querystring解析下就可以了

    • multipart/form-data

      文件表单的传输,也是本文介绍的重点

获取POST数据

前面已经说过,post数据的传输是可能分包的,因此必然是异步的。post数据的接受过程如下:

var postData = ‘‘;
request.addListener("data", function(postDataChunk) {  // 有新的数据包到达就执行
  postData += postDataChunk;
  console.log("Received POST data chunk ‘"+
  postDataChunk + "‘.");
});

request.addListener("end", function() {  // 数据传输完毕
  console.log(‘post data finish receiving: ‘ + postData );
});

注意,对于非文件post数据,上面以字符串接收是没问题的,但其实 postDataChunk 是一个 buffer 类型数据,在遇到二进制时,这样的接受方式存在问题。

POST数据的解析(multipart/form-data)

在解析POST数据之前,先介绍一下post数据的格式:

multipart/form-data类型的post数据

例如我们有表单如下

<FORM action="http://server.com/cgi/handle"
       enctype="multipart/form-data"
       method="post">
   <P>
   What is your name? <INPUT type="text" name="submit-name"><BR>
   What files are you sending? <INPUT type="file" name="files"><BR>
   <INPUT type="submit" value="Send"> <INPUT type="reset">
 </FORM>

若用户在text字段中输入‘Neekey’,并且在file字段中选择文件‘text.txt’,那么服务器端收到的post数据如下:

   --AaB03x
   Content-Disposition: form-data; name="submit-name"

   Neekey
   --AaB03x
   Content-Disposition: form-data; name="files"; filename="file1.txt"
   Content-Type: text/plain

   ... contents of file1.txt ...
   --AaB03x--
若file字段为空:

   --AaB03x
   Content-Disposition: form-data; name="submit-name"

   Neekey
   --AaB03x
   Content-Disposition: form-data; name="files"; filename=""
   Content-Type: text/plain

   --AaB03x--

若将file 的 input修改为可以多个文件一起上传:

<FORM action="http://server.com/cgi/handle"
       enctype="multipart/form-data"
       method="post">
   <P>
   What is your name? <INPUT type="text" name="submit-name"><BR>
   What files are you sending? <INPUT type="file" name="files" multiple="multiple"><BR>
   <INPUT type="submit" value="Send"> <INPUT type="reset">
 </FORM>

那么在text中输入‘Neekey’,并在file字段中选中两个文件’a.jpg’和’b.jpg’后:

   --AaB03x
   Content-Disposition: form-data; name="submit-name"

   Neekey
   --AaB03x
   Content-Disposition: form-data; name="files"; filename="a.jpg"
   Content-Type: image/jpeg

   /* data of a.jpg */
   --AaB03x
   Content-Disposition: form-data; name="files"; filename="b.jpg"
   Content-Type: image/jpeg

   /* data of b.jpg */
   --AaB03x--// 可以发现 两个文件数据部分,他们的name值是一样的

数据规则

简单总结下post数据的规则

1.不同字段数据之间以边界字符串分隔:

 --boundary\r\n // 注意,如上面的headers的例子,分割字符串应该是 ------WebKitFormBoundaryuP1WvwP2LyvHpNCi\r\n

2.每一行数据用”CR LF”(\r\n)分隔

3.数据以 边界分割符 后面加上 –结尾,如:

------WebKitFormBoundaryuP1WvwP2LyvHpNCi--\r\n

4.每个字段数据的header信息(content-disposition/content-type)和字段数据以一个空行分隔:\r\n\r\n

关于form具体的内容可以看下W3C的文档:W3C

If the user selected a second (image) file "file2.gif", the user agent might construct the parts as follows:

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

   --AaB03x
   Content-Disposition: form-data; name="submit-name"

   Larry
   --AaB03x
   Content-Disposition: form-data; name="files"
   Content-Type: multipart/mixed; boundary=BbC04y

   --BbC04y
   Content-Disposition: file; filename="file1.txt"
   Content-Type: text/plain

   ... contents of file1.txt ...
   --BbC04y
   Content-Disposition: file; filename="file2.gif"
   Content-Type: image/gif
   Content-Transfer-Encoding: binary

   ...contents of file2.gif...
   --BbC04y--
   --AaB03x--

数据解析基本思路

必须使用buffer来进行post数据的解析

利用文章一开始的方法(data += chunk, data为字符串 ),可以利用字符串的操作,轻易地解析出各自端的信息,但是这样有两个问题:

文件的写入需要buffer类型的数据

二进制buffer转化为string,并做字符串操作后,起索引和字符串是不一致的(若原始数据就是字符串,一致),因此是先将不总的buffer数据的toString()复制给一个字符串,再利用字符串解析出个数据的start,end位置这样的方案也是不可取的。

利用边界字符串来分割各字段数据

每个字段数据中,使用空行(\r\n\r\n)来分割字段信息和字段数据

所有的数据都是以\r\n分割

利用上面的方法,我们以某种方式确定了数据在buffer中的start和end,利用buffer.splice( start, end ) 便可以进行文件写入了.

其实github上有一个很不错的开源插件,有兴趣可以去看一下:node-formidable

时间: 2024-10-19 08:56:50

nodejs-http 对form表单上传文件数据的解析过程的相关文章

android form表单上传文件

原文地址:http://menuz.iteye.com/blog/1282097 Android程序使用http上传文件 有时,在网络编程过程中需要向服务器上传文件.Multipart/form-data是上传文件的一种方式. Multipart/form-data其实就是浏览器用表单上传文件的方式.最常见的情境是:在写邮件时,向邮件后添加附件,附件通常使用表单添加,也就是用multipart/form-data格式上传到服务器.  Html代码   <form action="/Test

使用form表单上传文件

在使用form表单上传文件时候,input[type='file']是必然会用的,其中有一些小坑需要避免. 1.form的 enctype="multipart/form-data" 已经是个老生常谈的问题了,相信都能注意到,就不多说了. 2.上传下载的请求是不能用ajax提交返回json的. 3.当使用input[type='file'] 的onChange事件来触发文件上传的时候要注意当上传成功时清空input的时候,不能简单的使用$("input").val(

关于form表单上传文件的问题

最近在学习php,刚好学到利用表单上传文件这一知识.在学习的过程中,出现了这样几个问题,我是小白,还请高手指点. 大家都知道在上传文件时,我们要设置表单的MIME编码.默认情况,enctype的编码格:application/x-www-form-urlencoded,不能用于文件上传, 只有使用了multipart/form-data,才能完整的传递文件数据,进行下面的操作.但是我在接下来的操作中,遇到了这样的问题. <?php   if (isset($_POST['submit']) &

form表单上传文件

注意form表单上传文件的时候,要加上   enctype这个属性 原文地址:https://www.cnblogs.com/xiaoxiaoyao/p/8541923.html

django 基于form表单上传文件和基于ajax上传文件

一.基于form表单上传文件 1.html里是有一个input type="file" 和 'submit'的标签 2.vies.py def fileupload(request): if request.method == 'POST': print(request.POST) print(request.FILES) # from django.core.files.uploadedfile import InMemoryUploadedFile print(type(reque

form表单上传文件使用multipart请求处理

在开发Web应用程序时比较常见的功能之一,就是允许用户利用multipart请求将本地文件上传到服务器,而这正是Grails的坚固基石——spring MVC其中的一个优势.Spring通过对Servlet API的HttpServletRequest接口进行扩展,使其能够很好地处理文件上传.扩展后的接口名为org.springframework.web.multipart.MultipartHttpServletRequest,其内容如清单7-31所示. 清单7-31  org.springf

PHP 后台程序配置config文件,及form表单上传文件

一,配置config文件 1获取config.php文件数组, 2获取form 表单提交的值 3保存更新config.php文件,代码如下: 1 $color=$_POST['color']; 2 $backtype=$_POST['backtype']; 3 4 $settings=include(dirname(__DIR__).'/config.php'); 5 6 $settings['themescolor']=(int)$color; 7 $settings['themesbackg

ajax上传文件 基于jquery form表单上传文件

<script src="/static/js/jquery.js"></script><script> $("#reg-btn").click(function () { // 1. 取到用户填写的数据 var form_data_obj = new FormData(); form_data_obj.append('username',$('#id_username').val()); form_data_obj.append

C# 模拟Form表单上传文件方法

public static string UploadFile(string url, HttpPostedFileBase file,string FieldName) { Stream fs = file.InputStream; if (!fs.CanRead) { return ""; } byte[] buffer = new byte[fs.Length]; fs.Read(buffer, 0, (int)fs.Length); fs.Close(); string bou