最近开始研究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 表单是怎么组成的 发现其于其它表单的区别在于

  1. header 里的Content-Type。
  2. 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 表单的时候,我们只用:

  1. 往req 里写header,注意特殊的Content-Type。
  2. 往req 里pipe 文件流。
  3. 往req 里写boundray 分隔符。
  4. 重复2,3步。直到完成所有文件发送。
  5. 往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的更多相关文章

  1. 002-06-RestTemplate-请求示例-form、json、multipart、okhttp3

    一.概述 请求示例集合 服务端:https://github.com/bjlhx15/common-study.git 中的 http-client-webserver 服务端:RequestBody ...

  2. 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 ...

  3. Multipart Upload with HttpClient 4--reference

    by Eugen Paraschiv on May 23, 2014 in HttpClient http://www.baeldung.com/httpclient-multipart-upload ...

  4. ANDROID使用MULTIPARTENTITYBUILDER实现类似FORM表单提交方式的文件上传

    最近在做 Android 端文件上传,要求采用 form 表单的方式提交,项目使用的 afinal 框架有文件上传功能,但是始终无法与php写的服务端对接上,无法上传成功.读源码发现:afinal 使 ...

  5. html5 file upload and form data by ajax

    html5 file upload and form data by ajax 最近接了一个小活,在短时间内实现一个活动报名页面,其中遇到了文件上传. 我预期的效果是一次ajax post请求,然后在 ...

  6. Django中的form设置field的html属性

    在Django中无论何种field,都有一个widget的属性: class Field(object): widget = TextInput # Default widget to use whe ...

  7. 怎么利用jquery.form 提交form

    说明:开发环境 vs2012 asp.net mvc c# 利用jQuery.form.js提交form 1.HTML前端代码 <%@ Page Language="C#" ...

  8. jquery.form.min.js

    /*! * jQuery Form Plugin * version: 3.51.0-2014.06.20 * Requires jQuery v1.5 or later * Copyright (c ...

  9. ASP.NET 服务端接收Multipart/form-data文件

    在网络编程过程中需要向服务器上传文件. Multipart/form-data是上传文件的一种方式. /// <summary> /// 上传工程文件 /// </summary&g ...

随机推荐

  1. C/C++中的声明与定义

    含义 声明(Declaration), 用于告诉编译器被声明的函数/变量的存在, 及它们的类型/调用格式信息, 以检查是否被正确调用. 声明不分配内存空间. 定义(Definition), 用于告诉编 ...

  2. Prince2七大原则(4)

    我们先来回顾一下,PRINCE2七大原则分别是持续的业务验证,经验学习,角色与责任,按阶段管理,例外管理,关注产品,剪裁. 第四个原则:按阶段管理. 阶段管理其实是给高层提供了项目生命周期中相对应的控 ...

  3. 理解javascript this指向

    匿名函数中的this指向window对象 这句话很经典: 每个函数在调用时,其活动对象都会自动获取两个特殊的变量:this和arguments.内部函数在搜索这两个变量时,只会搜到其活动对象为止,因此 ...

  4. Javascript知识点记录(二)

    Javascript入门易,精通难,基本上是共识的一个观点.在这个篇幅里,主要对一些难点进行记录. 鸭子类型 Javascript属于动态类型语言的一种.对变量类型的宽容,给了很大的灵活性.由于无需类 ...

  5. 【整理】认识MSG结构体

    在Windows程序中,消息是由MSG结构体来表示的.MSG结构体的定义如下(参见MSDN): typedef struct tagMSG { HWND hwnd; UINT message; WPA ...

  6. 自定义Docker容器的 hostname

    自定义Docker容器的 hostname   作者: John Deng 原创内容,欢迎传播,请注明出处:http://www.cnblogs.com/johnd/p/set-docker-host ...

  7. 如何查看Maven项目中的jar包依赖树情况

    对于开发人员,我想大家对于Maven应该不会陌生吧,如何在一个Maven项目中对这个项目中所引用的第三方jar包有个直观的了解呢? 其实实现很简单,只需要借助于Maven的一条命令,如下所示: mvn ...

  8. red hat关于桥接模式连不上外网或者没有IP

    很多人·在启动虚拟机后连接不上外网,即ifconfig没有ip地址,我总结了一下需要注意的地方: 以下全是在桥接模式. 1.在windows中打开任务管理器-->服务中找到一下几个服务,确保它们 ...

  9. 有米实习-用到的shell脚本和Python脚本记录

    Shell:LOG_DATE=`date -d "1 day ago" +%Y-%m-%d` #以指定格式设置一天前的年份月份日期 aws s3 ls $LAST5_BASE_PA ...

  10. 不把C作为第一门语言是个好主意么

    ----------------------------------------------------------------------------------   不把C作为第一门语言是个好主意 ...