node-upload-practice
上传说明
总体上来说,对于post文件上传这样的过程,主要有以下几个部分:
- 获取http请求报文肿的头部信息,我们可以从中获得是否为POST方法,实体主体的总大小,边界字符串等,这些对于实体主体数据的解析都是非常重要的
- 获取POST数据(实体主体)
- 对POST数据进行解析
- 将数据写入文件
获取http请求报文头部信息
利用nodejs中的 http.ServerRequest中获取
request.method
用来标识请求类型
request.headers
其中我们关心两个字段:
content-type
包含了表单类型和边界字符串(下面会介绍)信息。
content-length
post数据的长度
关于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数据的解析
在解析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
更加详细的信息可以参考W3C的文档Forms,不过文档中对于multiple=“multiple”
的文件表单的post数据格式使用了二级边界字符串,但是在实际测试中,multiple类型的表单和多个单文件表单上传数据的格式一致,有更加清楚的可以交流下:
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 ) 便可以进行文件写入了
文件写入
比较简单,使用 File System 模块(更好的做法是用stream)
var fs = new require( 'fs' ).writeStream,
file = new fs( filename );
fs.write( buffer, function(){
fs.end();
});
expressjs 4.*上传例子
https://github.com/i5ting/upload-cli
expressjs3和4之间的差别
一定要注意3和4之间的差别,其实最大差别你可以到
https://github.com/expressjs
里找中间件,是架构上的变化
multer
最简单最好用的上传中间件,multer
,官方推荐
https://github.com/expressjs/multer
最简答的用法
var app = express();
var multer = require('multer')
app.use(multer({
dest: './uploads',
rename: function (fieldname, filename) {
return filename //.replace(/\W+/g, '-').toLowerCase() + Date.now()
}
}))
更多Options
https://github.com/expressjs/multer#options
其他
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request
版本历史
- v0.1.0 初始化版本
欢迎fork和反馈
- write by
i5ting
shiren1118@126.com
如有建议或意见,请在issue提问或邮件
License
this repo is released under the MIT License.