Nodejs 获取文件上传进度
Nodejs 获取文件上传进度
国庆节对于一个大三党来说是一整块难得的时间,出去玩也好,做其他的事也好,都可以随心所欲地安排。想着利用这个时间开发 一个社交网站作为学习node的一个总结,不怎么精,也算凑合,无奈还有太多的事儿要做啊。废话不多说,转入正题:
今天遇到图片上传的问题,服务器能成功收到图片信息,但如何将进度返给客户端呢?用 <form method=‘post’ action="" target=“hidden- iframe” enctype=‘multipart/form-data’></form>发送文件是一个post请求,服务器只可能回复一次,而且这个回复主要用来回复前端上传 文件的获取地址,不适合再做他用。
网上查了查,说是可以用flash做,好吧,表示不会flash… 目前主要的问题是如何不断地从服务器获得上传进度,socket貌似能做到这一点,问题是不知道上传结束时客户端如何主动断开socket链接,有知道的朋友麻烦告诉一声^-^,于是决定采用轮询了。 先贴前端部分代码: html部分
<iframe name="hidden_iframe" id='hidden_iframe' style="display:none" src=""> </iframe>
<form method='post' action='/uploadimage' enctype='multipart/form-data' target="hidden_iframe">
<input type="text" name="randomID" id="uploadTag" style="display:none" />
选择图片:<input type='file' name='img' id='filearea' onchange="setTag()"/>
<input type='submit' value='提交' onclick=" startRequest()" />
</form>
<div id="showbox" ></div>
JS部分
var uploadTag;
function startRequest()
{
var json={"randomID":uploadTag};
setTimeout(post_data(obj,'/getuploadprocess',dealResponse),1000);
}
function dealResponse(json)
{
var box=document.getElementById("showbox");
box.innerText=json.progress+'%';
if(json.progress!=100)
{
startRequest();
}
}
//上面这俩个function 组成一个不达目的不罢休的轮询,post_data是个ajax,就不贴了
function setTag()
{
uploadTag=Math.ceil(Math.random()*1000000);
var tag=document.getElementById("uploadTag");
tag.value=uploadTag;
}
//设置随机id,避免搞不清是请求哪个文件的进度
function uploadErr(message)
{
alert(message);
}
//处理错误,当然这里只是简单alert,没有考虑用户体验,就写简单点吧
function uploadEnd(json)
{
var obj=JSON.parse(json)
alert(obj.message)
}
//上传结束的callback,返回图片的获取地址,前端应该将其设置成一个img元素的src属性,也从简了:)
** 后端:**
后端大部分人都是使用的express,然后就顺便设置了app.use(multer(dest:""))解析请求,那可就做不了进度条了,建议把这个设置删了,然后 var formidable=require(“formidable”);(写在路由模块里);
代码:
var uploadprogress={};//全局变量,存进度
module.exports=function(app)
{
...........................................................................
...........................................................................
app.post('/uploadimage',function(req,res){
var id=1;
var uploadfile;
var form=new formidable.IncomingForm();//formidable模块的用法自己去github看文档吧
form.uploadDir='';
from.keepExtensions=true;
form.on("field",function(name,value){//设置唯一可标识的id
id=req.session.user._id+req.body.randomID;//这个value就是表单中文本框的值
});
form.on("err",function(err){
var callback="<script>top.uploadErr('"+err+"');</script>";
res.end(callback);//这段文本发回前端就会被同名的函数执行
})
from.on("progress",function(bytesRecieved,bytesExpected){
uploadprogress.id=Math.ceil((bytesRecieved/bytesExpected)*100);//更新进度
//这里有个id的值是否已经设置的问题,不过没关系,就算仍是最初的1,也只耗一丁点内存
})
form.on("abort",function(){
............
})
form.on("file",function(err,file){
if(err)
{
console.log(err);
var callback="<script>top.uploadErr('"+err+"');</script>";
res.end(callback);
}
uploadfile=file;//记录文件信息
})
form.on("end",function(){
var file=new File()
.......
......
file.save();//将文件信息存入数据库,如果是文章里面的图片的话,就没有必要存了,在前端直接设置src就相当于存了
var fpath=uploadfile.path;
var callback="<script>top.uploadEnd('"+fpath+"');</script>";
res.end(callback);
})
form.parse(req);
})
app.post('/getuploadprocess',function(req,res){
var id=req.session.user._id+req.body.randomID;
var pr;
if(uploadprogress.id)
{
pr=uploadprogress.id;
}
else
{
pr=0;
}
if(pr==100)
{
delete uploadprogress.id;
}
res.status(200).json({"progress":pr});
})
}
ok主要的代码已经贴完了,下面来说一下工作的流程,首先由表单提交文件,在确定文件后会自动生成一个随机ID用于标识,这个ID发给后台和user的id组合为文件的标识;后台接受文件上传请求,formidable开始解析文件,主要监听progress事件,由其返回的参数来更新上传进度。在表单点击提交的时候同时触发startRequest函数,向后台获取上传进度,这个请求由/getuploadprogress处理,里面的代码就是简单的响应和清理功能。自此全部结束,前端获得进度后想要以什么方式展现就随看官发挥吧,我就简单展现其百分比了
PS:如果有大神路过,小弟请教一个问题:这个代码 是实现了功能,但获得进度是个长轮询,不知道对服务器来讲压力大不大。。测试时我同时上传俩个400多M的文件,电脑风扇就转个不停。虽然文件上传成功了,但还是担心性能问题(我用本机做的服务器),求大神留下高见!
Node.js 获取文件上传进度
国庆节对于一个大三党来说是一整块难得的时间,出去玩也好,做其他的事也好,都可以随心所欲地安排。想着利用这个时间开发一个社交网站作为学习Node.js的一个总结,不怎么精,也算凑合,无奈还有太多的事儿要做啊。废话不多说,转入正题:
今天遇到图片上传的问题,服务器能成功收到图片信息,但如何将进度反馈给客户端呢?使用 <form method='post' action="" target="hidden-iframe" enctype='multipart/form-data'>
发送文件是一个POST请求,服务器只可能回复一次,而且这个回复主要用来回复前端上传文件的获取地址,不适合再做他用。
在网上查找了一些资料,有人说可以使用Flash实现,但是本人不会Flash… 目前主要的问题是如何不断从服务器获得上传进度,Socket似乎可以做到这一点,但是不知道上传结束时客户端如何主动断开Socket连接。于是决定采用轮询的方式。
前端部分代码
HTML 部分
<iframe name="hidden_iframe" id='hidden_iframe' style="display:none" src=""></iframe>
<form method='post' action='/uploadimage' enctype='multipart/form-data' target="hidden_iframe">
<input type="text" name="randomID" id="uploadTag" style="display:none" />
选择图片:<input type='file' name='img' id='filearea' onchange="setTag()"/>
<input type='submit' value='提交' onclick="startRequest()" />
</form>
<div id="showbox"></div>
JS 部分
var uploadTag;
function startRequest() {
var json = {"randomID": uploadTag};
setTimeout(post_data(json, '/getuploadprocess', dealResponse), 1000);
}
function dealResponse(json) {
var box = document.getElementById("showbox");
box.innerText = json.progress + '%';
if (json.progress != 100) {
startRequest();
}
}
function setTag() {
uploadTag = Math.ceil(Math.random() * 1000000);
var tag = document.getElementById("uploadTag");
tag.value = uploadTag;
}
function uploadErr(message) {
alert(message);
}
function uploadEnd(json) {
var obj = JSON.parse(json);
alert(obj.message);
}
后端部分代码
后端大部分人都是使用的Express,然后就顺便设置了 app.use(multer(dest: ""))
解析请求,这样就不能实现进度条了。建议把这个设置删了,然后使用 var formidable = require("formidable");
(写在路由模块里)。
var uploadprogress = {}; // 全局变量,存储进度
module.exports = function (app) {
app.post('/uploadimage', function (req, res) {
var id = 1;
var uploadfile;
var form = new formidable.IncomingForm(); // formidable模块的用法请参考官方文档
form.uploadDir = '';
form.keepExtensions = true;
form.on("field", function (name, value) { // 设置唯一的ID
id = req.session.user._id + req.body.randomID; // 这个value就是表单中的文本框值
});
form.on("error", function (err) {
var callback = "<script>top.uploadErr('" + err + "');</script>";
res.end(callback); // 将这段文本发送回前端会执行同名的函数
});
form.on("progress", function (bytesReceived, bytesExpected) {
uploadprogress[id] = Math.ceil((bytesReceived / bytesExpected) * 100); // 更新进度
});
form.on("abort", function () {
// 处理中断逻辑
});
form.on("file", function (name, file) {
if (err) {
console.log(err);
var callback = "<script>top.uploadErr('" + err + "');</script>";
res.end(callback);
}
uploadfile = file; // 记录文件信息
});
form.on("end", function () {
var file = new File();
// 存储文件信息
file.save();
var fpath = uploadfile.path;
var callback = "<script>top.uploadEnd('" + fpath + "');</script>";
res.end(callback);
});
form.parse(req);
});
app.post('/getuploadprocess', function (req, res) {
var id = req.session.user._id + req.body.randomID;
var pr = uploadprogress[id] || 0;
if (pr == 100) {
delete uploadprogress[id];
}
res.status(200).json({ "progress": pr });
});
}
工作流程
- 表单提交文件时,生成一个随机ID用于标识。
- 随机ID与用户的ID组合为文件的标识。
- 后台接收文件上传请求,使用
formidable
模块解析文件,监听progress
事件,通过其返回的参数更新上传进度。 - 在表单点击提交按钮时,触发
startRequest
函数,向后台获取上传进度,这个请求由/getuploadprocess
处理,返回当前进度。 - 前端根据返回的进度展示到页面上。
这种方式虽然实现了功能,但由于采用了轮询机制,可能会对服务器造成一定的压力。如果需要优化性能,可以考虑使用WebSockets或类似的技术来实现实时通信。
代码格式化做好点嘛
虽然不懂,但是之前有看过 HTML5的进度事件 , 上传处理不是那么麻烦的事情吧…
不明白为什么这样复杂处理。xhr本身就支持监听上传的progress事件。
看下jquery-file-upload
iframe …
用 XMLHttpRequest 2.0 - ProgressEvent
不好,轮询太low了。应该用html5。 html5 有现成的file api. 细心的会发现chrome在上传文件的时候,右下角会有个进度条的。
留着学习
为了实现文件上传进度的获取,可以使用WebSocket来实现实时通信,这样可以在不增加服务器负担的情况下获取上传进度。下面提供一个简单的示例代码,展示如何使用WebSocket来实现这一功能。
前端代码
首先,我们需要修改前端代码以支持WebSocket:
<iframe name="hidden_iframe" id='hidden_iframe' style="display:none" src=""></iframe>
<form method='post' action='/uploadimage' enctype='multipart/form-data' target="hidden_iframe">
<input type="text" name="randomID" id="uploadTag" style="display:none" />
选择图片:<input type='file' name='img' id='filearea' onchange="setTag()"/>
<input type='button' value='提交' onclick="startUpload()" />
</form>
<div id="showbox"></div>
<script>
var ws;
function startUpload() {
var socket = new WebSocket('ws://yourserver.com/socket');
socket.onopen = function () {
var formData = new FormData(document.querySelector('form'));
fetch('/uploadimage', {
method: 'POST',
body: formData
}).then(response => response.json()).then(data => {
ws = socket;
ws.send(JSON.stringify({ randomID: data.randomID }));
});
};
socket.onmessage = function (event) {
var progress = JSON.parse(event.data).progress;
document.getElementById("showbox").innerText = progress + '%';
};
socket.onclose = function () {
console.log('Socket closed.');
};
}
function setTag() {
document.getElementById("uploadTag").value = Math.ceil(Math.random() * 1000000);
}
</script>
后端代码
接下来,我们修改后端代码以支持WebSocket,并且在上传过程中实时发送进度信息:
const express = require('express');
const http = require('http');
const WebSocket = require('ws');
const path = require('path');
const fs = require('fs');
const app = express();
const server = http.createServer(app);
const wss = new WebSocket.Server({ server });
wss.on('connection', function connection(ws) {
ws.on('message', function incoming(message) {
console.log('received: %s', message);
});
});
app.post('/uploadimage', (req, res) => {
const form = new formidable.IncomingForm();
let bytesReceived = 0;
let bytesExpected;
form.on('progress', (bytesRead, totalBytes) => {
bytesReceived += bytesRead;
bytesExpected = totalBytes;
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify({
progress: Math.round((bytesReceived / bytesExpected) * 100),
randomID: req.body.randomID
}));
}
});
});
form.on('error', err => {
res.status(500).send({ error: err.toString() });
});
form.on('end', () => {
res.json({ randomID: req.body.randomID });
});
form.parse(req);
});
server.listen(3000, () => {
console.log('Server is listening on port 3000');
});
通过上述代码,我们可以实现文件上传进度的实时反馈。WebSocket相比轮询方式,对服务器的压力更小,因为每次数据变化时只需发送一次消息,而不是每隔一段时间请求一次。