CombineStream 与 Multipart Form
最近开始研究http 特别是multipart 表单,想弄明白他是怎么work 的。在nodejs 里,可以使用form-data 来组合一个multipart 表单,然后使用http.request 发送出去
var formData = require('form-data');
var http = require('http');
var urlParse = require('url').parse;
var fs = require('fs');
f = new formData();
f.append('image', fs.createReadStream('./test.jpg'));
var options = urlParse(url);
options.method = 'post';
options.headers = f.getHeaders();
var req = http.request(options);
f.pipe(req);
在服务端就可以接受到post 过去的表单内容(node 可以使用multiparty 中间件)
form-data 之所以能pipe 到req,说明他至少是一个Readable stream, 查看代码后发现他继承于combine-stream。继续查看combine-stream 发现这是一个duplex stream,功能是接受多个readable stream 把他们串起来然后合并成一个readable stream。很有意思。
了解了combine-stream 是干什么用的以后,再了解一下multipart 表单是怎么组成的 发现其于其它表单的区别在于
- header 里的Content-Type。
- body 里的内容由定义的boundary 分隔。
最后发现,用combine-stream 来组装multipart 表单是一个很好的选择。因为stream 是资源的抽象,目的是为了在速度和系统资源占用之间做个取舍(想象一下如果没有stream,发送任何文件需要把整个文件放进内存;资源还没完全生产的情况,比如直播,没有流的抽象就没法实现)
http 的req 就是一个writeable stream(res 也一样),所有请求内容都可以通过req.write(或者res.write) 发送过去。具体后面是怎么发送的,是用socket?然后再tcp?则不用关心,stream 这层抽象屏蔽了underlying system。我们只用关心从readable stream 读,往writeable stream 里面写就可以了。
所以在发送multipart 表单的时候,我们只用:
- 往req 里写header,注意特殊的Content-Type。
- 往req 里pipe 文件流。
- 往req 里写boundray 分隔符。
- 重复2,3步。直到完成所有文件发送。
- 往req 里写尾信息。
可以发现在2,3两步就涉及很多文件流,比如上传多张图片到服务器,那么我们就要往req 里pipe 第一个文件(然后加上boundray)等待其结束后再接着pipe 下一张图片的流。这样重复多次非常麻烦。不如把所有这些流串在一起,合并成一整个流,这样把流之间切换的重复逻辑包装起来,既不容易出错也更简洁易理解。
CombineStream 要怎么实现呢。刚才了解到了,CombineStream 的逻辑不过是把一串readable stream 按顺序串起来,一个流结束了马上换另一个,直到所有添加的流都结束为止。那么首先CombineStream 必须是duplex 的。node 里的transform stream 就是最佳人选。
var Transform = require('stream').Transform;
var util = require('util');
var assert = require('assert');
var fs = require('fs');
function CombineStream(options) {
Transform.call(this, options);
this._streams = [];
this._currentStream = null;
this._prepare = function(){
var stream = this._currentStream = this._streams.shift();
if (stream) {
if (typeof stream === 'string') {
this.push(stream);
this._prepare();
return;
}
stream.pipe(this, {end: false});
stream.on('end', function(){
this._prepare();
}.bind(this));
stream.on('error', function(err){
console.error(err);
});
} else {
this.end();
}
};
}
util.inherits(CombineStream, Transform);
CombineStream.prototype.append = function(stream) {
this._streams.push(stream);
}
CombineStream.prototype._transform = function(chunk, encoding, callback) {
callback(null ,chunk);
}
CombineStream.prototype.pipe = function(dest, options) {
this._prepare();
Transform.prototype.pipe.call(this, dest, options);
}
module.exports = CombineStream;
这样就实现了一个简单的CombineStream。只加了很少代码:append 方法添加流,这里对string 做了适配;_prepare 这个私有方法用来实现流切换的逻辑。
然后就可以试试用CombineStream来构造一个multipart表单然后发送。
var cs = new CombineStream();
cs.append('-----------------------------287032381131322\r\nContent-Disposition: form-data; name="image"; filename="test.jpg"\r\nContent-Type: image/jpg\r\n\r\n');
cs.append(gmStream);
cs.append('\r\n-----------------------------287032381131322--'); var options = urlParse(url);
options.method = 'post';
options.headers = {
'keep-alive': 300,
'content-type':'multipart/form-data; boundary=---------------------------287032381131322',
'Transfer-Encoding': 'chunked'
}; var req = http.request(options);
cs.pipe(req); req.on('error', function(err){
console.error(err);
});
req.on('response', function(res) {
//deal res here.
});
这里我hardcode 了分隔信息和尾信息,中间有很多\r\n 可以看出没有系统的处理方法的话很容易出错。这样如果在server 端使用multiparty 中间件来解析表单的话可以得到正确的文件上传内容。
{ image:
[ { fieldName: 'image',
originalFilename: 'test.jpg',
path: '/var/folders/02/pwvm1df51nsfvg373jf8ksg00000gn/T/hwM6h-88fqiemXyna1Hd09eK.jpg',
headers: [Object],
size: 10988 } ] }
Conclusion:
可见form-data 模块基本就是这么工作的。
CombineStream 与 Multipart Form的更多相关文章
- 002-06-RestTemplate-请求示例-form、json、multipart、okhttp3
一.概述 请求示例集合 服务端:https://github.com/bjlhx15/common-study.git 中的 http-client-webserver 服务端:RequestBody ...
- SPRING IN ACTION 第4版笔记-第七章Advanced Spring MVC-003- 上传文件multipart,配置StandardServletMultipartResolver、CommonsMultipartResolver
一.什么是multipart The Spittr application calls for file uploads in two places. When a new user register ...
- Multipart Upload with HttpClient 4--reference
by Eugen Paraschiv on May 23, 2014 in HttpClient http://www.baeldung.com/httpclient-multipart-upload ...
- ANDROID使用MULTIPARTENTITYBUILDER实现类似FORM表单提交方式的文件上传
最近在做 Android 端文件上传,要求采用 form 表单的方式提交,项目使用的 afinal 框架有文件上传功能,但是始终无法与php写的服务端对接上,无法上传成功.读源码发现:afinal 使 ...
- html5 file upload and form data by ajax
html5 file upload and form data by ajax 最近接了一个小活,在短时间内实现一个活动报名页面,其中遇到了文件上传. 我预期的效果是一次ajax post请求,然后在 ...
- Django中的form设置field的html属性
在Django中无论何种field,都有一个widget的属性: class Field(object): widget = TextInput # Default widget to use whe ...
- 怎么利用jquery.form 提交form
说明:开发环境 vs2012 asp.net mvc c# 利用jQuery.form.js提交form 1.HTML前端代码 <%@ Page Language="C#" ...
- jquery.form.min.js
/*! * jQuery Form Plugin * version: 3.51.0-2014.06.20 * Requires jQuery v1.5 or later * Copyright (c ...
- ASP.NET 服务端接收Multipart/form-data文件
在网络编程过程中需要向服务器上传文件. Multipart/form-data是上传文件的一种方式. /// <summary> /// 上传工程文件 /// </summary&g ...
随机推荐
- 线段树 HDU 3397(真)
5 种操作 0 1 然后 异或 似乎这种2个更新的先后每次都搞不清 覆盖有覆盖就可以不异或 也不知道为什么 #include<stdio.h> #include<string.h& ...
- 解决:ERROR: Cannot launch Jack server
问题重现: Install: /home/dinphy/sm/out/target/product/ido/system/lib/libdl.so java -Xmx3500m -jar /home/ ...
- Python Day2
一.列表 列表是我们最以后最常用的数据类型之一,通过列表可以对数据实现最方便的存储.修改等操作 定义列表 新建一个列表 name = ['ShaoLin','Tom','Kimi','Rain','A ...
- struts2+hibernate 项目实战:图书管理系统
经典项目,练手必备. 图书管理系统 需求分析(大致,并不专业):1.需要有用户管理: 1.1 用户注册: 1.2 用户登录: 1.3 用户信息修改: 1.4 用户修改密码: 2.需要有书本管理: 2. ...
- 如何学习Oracle
如何学习Oracle?分清几个概念是关键 经常有一些Oracle的初学者问到以下几个问题,这里集中解答一下,希望对大家有帮助. 1.如果有一定的数据库基础,知道SQL是怎么回事,即使写不出 ...
- SourceTree推送时,增加额外的远程仓库,不用每次都自定义粘贴复制网络
一.命令行添加 二.软件界面可以查看到结果 更新,以上是代码添加了远程仓库,最近,找到了不用代码,直接在文件夹里写地址来添加的方式.直接上图了.
- Git入门
转: http://www.cnblogs.com/luxiaojun/p/5944145.html
- 使用safari对webview进行调试
在web开发的过程中,抓包.调试页面样式.查看请求头是很常用的技巧.其实在iOS开发中,这些技巧也能用(无论是模拟器还是真机),不过我们需要用到mac自带的浏览器Safari.所以,本文将讲解如何使用 ...
- 移动端框架篇-控制父容器的滑屏框架-slip.js
设计滑屏框架的办法一般有2种 控制父容器法 控制子容器法 这个算是控制父容器法 通过控制父容器的transform: translateY或margin-top或top的值来上下滑动页面,每次的位移的 ...
- 大熊君{{bb}}------春节期间你跳槽了吗?
时间过的很快,转眼间又快过春节了,推荐你在春节期间跳槽,是基于以下几个原因: 1,命中率高 通常情况下,所有公司都会在年底进行一定幅度的裁员,而惟独这家公司在招工,这等于明摆着告诉公众他们现在面临严重 ...