如何在Formidable(Node.js)中取消用户上传
How to cancel user upload in Formidable (Node.js)?
我已经处理这个问题两天了,我被卡住了。我在Express中使用Node.js,我正在尝试实现一个上传表单
-
检查文件的大小,如果太大,则取消上传(当我说取消时,我的意思是防止任何进一步的数据写入磁盘并删除临时文件)
-
检查文件类型并确认它是正确的类型(.jpg、.png等),如果不是,则停止对磁盘的任何进一步写入并删除临时文件。
目前,我正在进行上传,当文件太大或与正确的类型不匹配时,我会发出错误,然后在整个文件写入磁盘后,我用fs.unlink()
删除该文件。但我看到了这种方法的一个潜在问题:如果用户上传了一个巨大的文件(GB大小)怎么办?按照我现在的方法,它最终会从我的机器上删除,但不会浪费大量资源。所以基本上,我希望使用最少量的资源来确认文件可以上传。这是我迄今为止的代码:
var path = absolutePath + '/public/images/users/' + req.session.userId + '/';
var maxSize = 3146000; // 3MB
var form = new formidable.IncomingForm();
form.uploadDir = path;
form.keepExtensions = true;
form.on('error', function(message) {
if(message)
{
res.json({err: message});
}
else
{
res.json({err: 'Upload error, please try again'});
}
});
form.on('fileBegin', function(name, file){
if(form.bytesExpected > maxSize)
{
this.emit('error', 'Size must not be over 3MB');
}
});
form.on('file', function(name, file) {
var type = file.type;
type = type.split('/');
type = type[1];
if(type != 'jpeg' && type != 'png' && type != 'gif')
{
this.emit('error', "JPG's, PNG's, GIF's only");
fs.unlink(file.path);
}
else
{
fs.rename(file.path, path + 'profile.' + type);
}
});
form.on('progress', function(bytesReceived, bytesExpected) {
console.log(bytesReceived); //This is just to view progress
});
form.parse(req);
我也很困惑,因为根据https://github.com/felixge/node-formidable,上面写着:
遇到错误的请求会自动暂停,如果您希望请求继续触发"数据"事件,则必须手动调用request.resume()。
这将是伟大的,但我似乎无法使它发挥作用。每当我发出"错误"时,"数据"事件都会一直触发,直到完成。
尝试次数
我曾尝试在出现错误时取消请求,但没有成功。req.pause()
对我没有任何帮助,req.end()
和req.abort()
给了我一个错误,说这不是一个方法,而req.connection.destroy()
和req.connection.end()
只是发送了一个POST请求循环。
最后的想法
因此,我所寻找的似乎应该是一个普通的地方,但我花了两天时间在互联网上搜索一个彻底的实施,似乎什么都找不到。我的意思是,在上传完整个文件后,检查文件的大小和类型很简单,但谁想浪费所有这些资源呢?更不用说恶意用户可能做的事情了。
我会继续工作,直到我得到我想要的东西,但我认为这个问题可能与其他一些用户有关,希望我能得到一些帮助!
谢谢你抽出时间。
我将尝试回答我自己的问题。。。
因此,在对Formidable进行了一些尝试和错误之后,我干脆放弃了它,转而使用Multiparty。当发出一个巨大的错误时,多方实际上取消了上传。
我的解决方案
因此,我的解决方案利用了客户端大小和类型检查(代码中未显示)。然后将请求发送到服务器。在服务器上,在写入磁盘之前,我会再次检查文件大小和类型是否正确。我可以通过使用Multiparty的part
事件来做到这一点。如果它们不正确,那么我只需发送一个413错误的响应。(感谢josh3736澄清了浏览器中应该发生的事情。)在发送回413后,浏览器的行为有点零星。对于我正在测试的浏览器,它只显示了一个pending
发布请求。我认为这种行为是由于没有处理整个表单,因此,它不会接受任何回复。这似乎不是最优雅的处理方式,因为不会显示错误代码,但只有绕过客户端检查的恶意用户才会遇到这种行为(我的网站依赖于Javascript,所以如果所有用户都想使用我的网站,他们都会启用它)。这就是我的文字解决方案,现在对于一些代码。。。
app.post('/profile/profile_pic', urlencoded, function (req, res) {
var path = absolutePath + '/public/images/users/' + req.session.userId + '/';
var maxSize = 3146000; // 3MB
var options = {uploadDir: path};
var form = new multiparty.Form();
form.on('error', function(message) {
res.status = 413;
res.send(413, 'Upload too large');
res.end();
});
form.on('file', function(name, file) {
var type = file.headers['content-type'];
type = type.split('/');
type = type[1];
fs.rename(file.path, path + 'profile.' + type);
path = '/images/users/' + req.session.userId + '/profile.' + type;
});
form.on('part', function(part) {
var type = part.headers['content-type'];
var size = part.byteCount - part.byteOffset;
if(type != 'image/jpeg' && type != 'image/png' && type != 'image/gif' != 'application/pdf' || size > maxSize)
{
this.emit('error');
}
});
form.on('close', function(){
res.json({err: 0, path: path});
});
form.parse(req);
});
要中止上传,正确的做法是关闭套接字。
req.socket.end();
不幸的是,服务器想要中止正在进行的HTTP上载的情况非常糟糕。
这里要做的正确的、符合规范的事情就是尽早发送HTTP 413响应——也就是说,一旦你检测到客户端发送的字节数超过了你想要处理的字节数。发送错误响应后是否终止套接字取决于您。这符合RFC 2616。[…]接下来会发生什么并不理想。
如果你打开套接字,所有浏览器(Chrome 30、IE 10、Firefox 21)都会继续发送数据,直到整个文件上传完毕。只有到那时,浏览器才会显示您的错误消息。这真的很糟糕,因为用户必须等待整个文件完成上传,却发现服务器拒绝了它。这也浪费了你的带宽。
浏览器的当前行为违反了RFC 2616§8.2.2:
发送消息正文的HTTP/1.1(或更高版本)客户端在发送请求时应监控网络连接的错误状态。如果客户端看到错误状态,则应立即停止传输正文。如果主体是使用";分块的";编码(第3.6节)、零长度块和空尾部可以用于过早地标记消息的结束。如果正文前面有Content-Length标头,则客户端必须关闭连接。
有开放的Chrome和Firefox问题,但不要指望很快就能解决。
如果你在发送HTTP 413响应后立即关闭套接字,所有浏览器显然会立即停止上传,但它们目前显示一个";连接复位";错误(或类似错误),而不是您可能在响应中发送的任何HTML。
同样,这可能违反了规范(允许服务器提前发送响应并关闭连接),但我也不希望浏览器很快修复。
您看到的POST
请求循环是可疑的。你在使用某种AJAX上传程序吗?它可能会在您提前关闭套接字后自动重试上载。
一段时间过去了,现在您可以使用multer而不是强大的或多方的。Multer内置了所需的功能。
Form.pause实际上会阻止浏览器发送更多数据。调用下面的函数为我做到了:
var closeConnection = function(res, form){
try{
if (!res.ended){
form.onPart=null;
form.pause();
res.end();
res.ended = true;
}
}catch(e){console.log(e);}
}
我试过你的解决方案,它似乎在我的机器上不起作用。在我将这行this.emit('error');
放入form.on部分后,客户端无法得到响应。我的程序似乎被阻止了。这意味着你不能调用上传方法两次。第二个调用没有执行,因为您的程序已被以下行阻止:this.emit('Error');
。
upload:function(req, res) {
var form = new multiparty.Form();
form.on('error', function(message) {
res.send('Not Okay');
});
form.on('part', function(part) {
console.log('enter form on');
this.emit('Error');
res.send('Not Okay');
});
form.on('close', function(){
res.send('Not Okay');
});
form.parse(req);
}
我找到的唯一解决方案是在form.on部分中调用part.resume();
。它可以在不接触磁盘的情况下使用服务器上的上传文件。但它会消耗您的网络和服务器资源。无论如何,这比程序被阻止的情况要好得多。这就是为什么我一直在寻找另一种更好的方法来解决它
var maxSize = 30 * 1024 * 1024; //30MB
app.post('/upload', function(req, res) {
var size = req.headers['content-length'];
if (size <= maxSize) {
form.parse(req, function(err, fields, files) {
console.log("File uploading");
if (files && files.upload) {
res.status(200).json({fields: fields, files: files});
fs.renameSync(files.upload[0].path, uploadDir + files.upload[0].originalFilename);
}
else {
res.send("Not uploading");
}
});
}
else {
res.send(413, "File to large");
}
如果在得到响应之前浪费了客户端的上传时间,请在客户端javascript中进行控制。
if (fileElement.files[0].size > maxSize) {
....
}
这里有一个使用多方的解决方案。
jfizz给出的答案并不适用于所有浏览器。
非常重要:正如josh3736在上面的评论中提到的,您必须将Connection标头设置为关闭!
form.on('part', function(part) {
if (![some condition I want to be met]) {
form.emit('error', new Error('File upload canceled by the server.'));
return;
}
... code to handle the uploading process normally ...
});
form.on('error', function(err) {
console.log('ERROR uploading the file:',err.message);
res.header('Connection', 'close');
res.status(400).send({ status:'error' });
setTimeout(function() { res.end(); }, 500);
next(err);
});
我不知道线程是否仍在等待答案,但在这种情况下,您可以通过将form.maxFileSize设置为零来阻止文件上传。如果满足条件,他将发送一个错误事件,指出超出了maxFileSize。
fileUpload = false
form = new require("formidable").IncomingForm()
if (fileUpload)
// Do stuff here
else
form.maxFileSize = 0
- 如何解决取消抖动的用户输入上的竞争条件
- 如果用户在输入上按 Enter 键,则取消挖空速率限制扩展器
- Jquery:如何处理"停止/取消”;用户停止表单提交时的事件
- 如何在Formidable(Node.js)中取消用户上传
- 如何在点击链接后从用户取消对话框中获得回调以选择移动应用程序
- 您如何判断用户何时从默认的 JavaScript 提示符 () 中单击取消
- 如果用户发送另一个请求,请使用 .abort() 取消现有的 ajax 请求
- 动态覆盖jqueryUI对话框按钮(保存,取消等到用户选择)文本
- 保持选中复选框状态,直到用户取消选中
- 用户取消选择,然后从数组中删除项目并进行排列,具体取决于它们的选择方式
- 从我的应用中打开/关闭或取消授权用户,并列出应用中用户的操作
- “用户取消了身份验证”时,从Chrome扩展程序使用AuthWithOAuthPopup
- 高图:防止用户取消选中图表中的所有图例
- 如何编码,以便在用户单击箭头时,段落隐藏和取消隐藏
- 如何'取消缓存'当用户离开页面时,ajax加载了html
- JavaScript:如果用户取消confirm(),文件输入onclick将阻止更改
- 即使用户选择“取消”,我如何保留FileDialog输入值?(ASP.Net/Javascript)
- 取消用户点击确认框'Cancel '
- 如何取消用户的单选按钮选择,在确认对话框后取消选择
- 取消用户最后选择的选项