最近要做个图片上传的需求,因为服务端春节请假回家还没来,所以就我自己先折腾了一下,大概做出来个效果,后台就用了nodejs,刚开始做的时候想网上找一下资料,发现大部分资料都是用node-formidable插件实现上传的。但是自己又想手动实现一下,所以就开始折腾了。写此博文也就是做个记录。

  先大概整理一下整个思路,自己想要实现的效果是能够在页面上无刷新上传一个图片并且显示(后来做着做着就变成所有文件的上传了,不过都一个样)。

  在前端部分,想要无刷新首先想到的是ajax,但是ajax无法上传文件,所以还是老老实实用form上传,如果用form的话又要保证页面无刷新,那就使用iframe来实现了。所以前端需要两个页面,一个用户操作页面index.html为主页面,还有一个是专门用来上传的页面upload.html,html如下:

index.html:
<body>
您上传的东西为:<br><br>
<div class="data">
(无)
</div>
<br>
<button class="choose">上传东西</button>
<iframe src="upl" frameborder="0" id="upl"></iframe>
</body> upload.html:
<body>
<form action="/upload" method=post enctype="multipart/form-data" accept-charset="utf-8">
<input type="file" id="data" name="data" />
<input type="submit" value="上传" id="sub"/>
</form>
</body>

  index.html页面点击上传按钮,js将会触发iframe里的upload页面里的input file的click事件,所以进行文件选择,选择好后再触发upload页面里的submit的click事件,文件便开始上传,文件上传成功后,后台将会返回一段html代码,里面就包含着文件链接。index.html页面获取到文件链接,如果是图片则显示图片,如果是其他则显示下载链接。index.html的js代码如下:

window.onload = function(){
var frame = $("#upl")[0];
var cd; frameInit()
frame.onload = function(){
frameInit()
if($(cd).find("#path").length>0){
var path = $(cd).find("#path")[0].innerHTML;
if(/png|gif|jpg/g.test(path)){
$(".data").html("<img src='"+path+"'><br>")
}else {
$(".data").html("<a href='"+path+"' target='_blank'>"+path+"</a><br>")
} frame.src = "upl";
}
} $(".choose").click(function(){
$(cd).find("#data").click();
}); function frameInit(){
cd = frame.contentDocument.body; var img = $(cd).find("#data")[0]
if(img){
img.onchange = function(){
$(cd).find("#sub").click();
}
}
}
}

  通过iframe的onload事件来获取后台返回的链接。以上代码比较简单,就不具体解释了。

  接下来是后台的实现:

  首先先是要建个http server,然后,因为有两个页面,再加上还有文件下载之类的,所以先弄个最简单的路由:

var http = require('http');
var fs = require('fs'); http.createServer(function(req , res){
var imaps = req.url.split("/");
var maps = [];
imaps.forEach(function(m){
if(m){maps.push(m)}
}); switch (maps[0]||"index"){
case "index":
var str = fs.readFileSync("./index.html");
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(str , "utf-8");
break; case "upl":
var str = fs.readFileSync("./upload.html");
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(str , "utf-8");
break; case "upload":
break; default :
var path = maps.join("/");
var value = "";
var filename = maps[maps.length-1];
var checkReg = /^.+.(gif|png|jpg|css|js)+$/; if(maps[0]=="databox"){
checkReg = /.*/
} if(checkReg.test(filename)){
try{
value = fs.readFileSync(path)
}catch(e){}
} if(value){
res.end(value);
}else {
res.writeHead(404);
res.end('');
}
break;
}
}).listen(9010);

  上面代码也很简单,路由index指向index.html,upl指向upload.html,而其他如果是非指向databox里的链接则只允许访问图片、css、js文件,如果是指向databox的链接则允许访问一切,databox是用来存储上传文件的文件夹。上面代码中upload路由就是文件上传的提交地址,所以文件上传后,对文件的处理就是这里。

  对post过来的数据的处理,常用的办法就是:

var chunks = [];
var size = 0;
req.on('data' , function(chunk){
chunks.push(chunk);
size+=chunk.length;
}); req.on("end",function(){
var buffer = Buffer.concat(chunks , size);
});

  那个buffer就是post过来的所有数据了,当我们console.log(buffer.toString()),我们就可以看到post过来的数据的格式:

  

  其中,红色方框里的乱码其实就是文件数据了,前面的是文件信息报头。如果想获得里面的数据,就得先把非文件数据过滤掉,根据控制台输出的信息可知过滤的方法很简单,根据\r\n来分割就可以了,数据开头四个\r\n之后就是文件数据,而结尾的话则是去掉\r\n--WebKitFormblabla--\r\n,也是根据\r\n来过滤。所以把上面那段代码补全后就是如下:

var chunks = [];
var size = 0;
req.on('data' , function(chunk){
chunks.push(chunk);
size+=chunk.length;
}); req.on("end",function(){
var buffer = Buffer.concat(chunks , size);
if(!size){
res.writeHead(404);
res.end('');
return;
} var rems = []; //根据\r\n分离数据和报头
for(var i=0;i<buffer.length;i++){
var v = buffer[i];
var v2 = buffer[i+1];
if(v==13 && v2==10){
rems.push(i);
}
} //图片信息
var picmsg_1 = buffer.slice(rems[0]+2,rems[1]).toString();
var filename = picmsg_1.match(/filename=".*"/g)[0].split('"')[1]; //图片数据
var nbuf = buffer.slice(rems[3]+2,rems[rems.length-2]); var path = './databox/'+filename;
fs.writeFileSync(path , nbuf);
console.log("保存"+filename+"成功"); res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8'});
res.end('<div id="path">'+path+'</div>');
});

  对数据的过滤直接通过分析buffer,刚开始自己写的时候是把buffer转成string来分析,但是问题出现了,当过滤完后,把数据写入文件前需要把string再转成buffer写进去,结果写出来的文件都是错误的。改各种编码转buffer都不行,折腾了N久,最后的终于找到对应的方案,就是在buffer转string的时候写成buffer.toString("binary"),然后再过滤完后再处理成buffer的时候写成new Buffer(str , 'binary')才行,但是查了一下文件,貌似buffer中binary的编码被弃用了,或者说不建议使用。所以自己就想不转string,直接分析buffer。通过查ascii表很容易通过一个for循环把\r\n找出来了。于是问题就解决了。

  运行效果良好:

  

  这看似把上传文件的功能实现了,但是仔细一想,好像还有问题,因为自己此时是想实现个文件上传了,而不是单单的图片上传,所以如果我上传的数据几百M,那么一次性把buffer全部读出来再处理,不要说处理速度慢,就单单这文件数据就能把内存耗的差不多了。所以这种把数据全部接收过来再处理的方法貌似不行,最好就是数据一边接收一边处理,不让所有数据全部挤在内存上。所以,我就使用了stream。

  整个处理代码改成了,本来是在数据接收完成上进行处理改成在接收数据的时候进行处理: 

var imgsays = [];
var num = 0;
var isStart = false;
var ws;
var filename;
var path;
req.on('data' , function(chunk){
var start = 0;
var end = chunk.length;
var rems = []; for(var i=0;i<chunk.length;i++){
if(chunk[i]==13 && chunk[i+1]==10){
num++;
rems.push(i); if(num==4){
start = i+2;
isStart = true; var str = (new Buffer(imgsays)).toString();
filename = str.match(/filename=".*"/g)[0].split('"')[1];
path = './databox/'+filename;
ws = fs.createWriteStream(path); }else if(i==chunk.length-2){ //说明到了数据尾部的\r\n
end = rems[rems.length-2];
break;
}
} if(num<4){
imgsays.push(chunk[i])
}
} if(isStart){
ws.write(chunk.slice(start , end));
}
}); req.on("end",function(){
ws.end();
console.log("保存"+filename+"成功");
res.writeHead(200, { 'Content-Type': 'text/html;charset=utf-8'});
res.end('<div id="path">'+path+'</div>');
});

  原理差不多,对每次接收的buffer段进行判断,当经过四个\r\n后分析文件报头获取文件类型,创建一个写入流,并且开始写入,同时加上对是否到了数据尾部判断,数据尾部会跟着一个\r\n,如果到了尾部,则过滤掉尾部的信息。

  如此一来,上传的文件就不会因为太大而把内存撑爆了。

  附上github地址:https://github.com/whxaxes/node-test/tree/master/server/upload    有兴趣的可以down下来

  本人前端小菜,若有不当之处请指正。

nodejs学习之文件上传的更多相关文章

  1. MVC&WebForm对照学习:文件上传(以图片为例)

    原文  http://www.tuicool.com/articles/myM7fe 主题 HTMLMVC模式Asp.net 博客园::首页::  ::  ::  ::管理 5 Posts :: 0 ...

  2. nodejs+express-实现文件上传下载管理的网站

    Nodejs+Express-实现文件上传下载管理的网站 项目Github地址(对你有帮助记得给星哟):https://github.com/qcer/updo 后端:基于nodejs的express ...

  3. javascript结合nodejs实现多文件上传

    前端文件上传功能比较依赖后端,所以第一步用nodejs实现一个供文件上传的功能接口. 因为本人对nodejs也是一知半解,所以刚开始的想法是像原始的ajax交互那样,获取上传文件的内容,然后再通过no ...

  4. Spring MVC学习笔记——文件上传

    1.实现文件上传首先需要导入Apache的包,commons-fileupload-1.2.2.jar和commons-io-2.1.jar 实现上传就在add.jsp文件中修改表单 enctype= ...

  5. Struts学习之文件上传

    * 单文件上传:        * 在动作类action中声明相关属性:            * 在动作类action中,要声明与页面中表单name属性同名的属性,同名的属性的类型是File类型:  ...

  6. springMVC3学习(十一)--文件上传CommonsMultipartFile

    使用springMVC提供的CommonsMultipartFile类进行读取文件 需要用到上传文件的两个jar包 commons-logging.jar.commons-io-xxx.jar 1.在 ...

  7. requests 进阶用法学习(文件上传、cookies设置、代理设置)

    一.文件上传 1.模拟网站提交文件 提交此图片,图片名称:timg.jpg import requests files={ 'file':open('timg.jpg','rb') } respons ...

  8. JavaWeb学习总结——文件上传和下载

    在Web应用系统开发中,文件上传和下载功能是非常常用的功能,今天来讲一下JavaWeb中的文件上传和下载功能的实现. 对于文件上传,浏览器在上传的过程中是将文件以流的形式提交到服务器端的,如果直接使用 ...

  9. SpringMVC(四)-- springmvc的系统学习之文件上传、ajax&json处理

    资源:尚学堂 邹波 springmvc框架视频 一.文件上传 1.步骤: (1)导入jar包 commons-fileupload,commons-io (2)在springmvc的配置文件中配置解析 ...

随机推荐

  1. 第二章 Mysql 数据类型简介--(整数类型、浮点数类型和定点数类型,日期与时间类型,字符串类型,二进制类型)

    第一节:整数类型.浮点数类型和定点数类型 1,整数类型 2,浮点数类型和定点数类型 M 表示:数据的总长度(不包括小数点):D 表示:小数位:例如 decimal(5,2) 123.45存入数据的时候 ...

  2. C中不安全函数

    C 中大多数缓冲区溢出问题可以直接追溯到标准 C 库.最有害的罪魁祸首是不进行自变量检查的.有问题的字符串操作(strcpy.strcat.sprintf 和 gets).一般来讲,象“避免使用 st ...

  3. FDTD扩展到所有频率

  4. NOIP2013普及组 T2 表达式求值

    OJ地址:洛谷P1981 CODEVS 3292 正常写法是用栈 #include<iostream> #include<algorithm> #include<cmat ...

  5. HDU 3333 Turing Tree --树状数组+离线处理

    题意:统计一段序列[L,R]的和,重复元素只算一次. 解法:容易看出在线做很难处理重复的情况,干脆全部讲查询读进来,然后将查询根据右端点排个序,然后离散化数据以后就可以操作了. 每次读入一个数,如果这 ...

  6. UVALive 6449 IQ Test --高斯消元?

    题意:给你一串数字,问这串数字符合f[n] = a*f[n-1],f[n] = a*f[n-1]+b*f[n-2],f[n] = a*f[n-1]+b*f[n-2]+c*f[n-3]这几个方程中的哪个 ...

  7. js中容易被忽视的事件问题总结

    一:跨平台事件 什么叫跨平台事件?即在不同的浏览器上执行同一事件,所使用的方法不同. 什么是EventUtil对象?有什么作用?即将所有与事件相关的函数,融合在一起的一个容器,方便管理事件对象,它没有 ...

  8. 第12章 纤程(Fiber)

    12.1 纤程对象的介绍 (1)纤程与线程的比较 比较 线程(Thread) 纤程(Fiber) 实现方式 是个内核对象 在用户模式中实现的一种轻量级的线程,是比线程更小的调度单位. 调度方式 由Mi ...

  9. 改造二叉树 (长乐一中模拟赛day2T1)

    1.改造二叉树 [题目描述] 小Y在学树论时看到了有关二叉树的介绍:在计算机科学中,二叉树是每个结点最多有两个子结点的有序树.通常子结点被称作“左孩子”和“右孩子”.二叉树被用作二叉搜索树和二叉堆.随 ...

  10. 利用OpacityMask制作打洞效果

    起因 项目上存在一个连线功能,在设计的原型中,在连线中间文字上下各有15像素的空白.接手的同事觉得没思路,问我能不能在不影响连线后面的背景情况下解决该问题.我就抽了点时间给他写了个Demo.回家后趁热 ...