如何在Formidable(Node.js)中取消用户上传?

我已经在这个问题上工作了两天了,而且我被卡住了。 我正在使用Express的Node.js,我试图实现一个上传表单。 基本上,我想要的forms做到以下几点:

  • 检查文件的大小,并取消上传,如果它太大(当我说取消,我的意思是防止任何进一步的数据被写入磁盘并删除临时文件)

  • 检查文件types并确认它是正确的types(.jpg,.png等),如果不是,则停止进一步写入磁盘并删除临时文件。

目前,我有上传工作,并且当文件太大或者它不匹配正确的types时,我发出错误,然后在整个文件写入磁盘后,我用fs.unlink()删除文件。 但是,我发现这种方法存在一个潜在的问题:如果用户上传一个巨大的文件(GB的大小)呢? 现在用我的方法,它最终会从我的机器中删除,但不会浪费大量的资源。 所以基本上,我正在寻找使用最less量的资源,以确认文件可以上传。 这是迄今为止的代码:

  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请求的循环。

最后的想法

所以我在寻找的东西似乎应该是普通的地方,但是我花了两天的时间在网上search一下彻底的实现,我似乎找不到任何东西。 我的意思是,检查整个file upload完成后文件的大小和types是很简单的,但谁愿意浪费所有这些资源呢? 更不用说恶意用户可以做的事情了。

我会继续工作,直到我得到我正在寻找的东西,但我认为这个问题可能与其他用户有关,希望我能得到一些帮助!

谢谢你的时间。

一段时间已经过去了,现在你可以使用multer而不是强大的或多方的。 Multer具有所需的内置function。

我会试着回答我自己的问题

所以在经过一番与Formidable的试验和错误之后,我放弃了它并转到了Multiparty 。 当发生错误的时候,Multiparty ACTUALLY会取消上传。

我的解决scheme

所以我的解决scheme利用客户端大小和types检查(未在代码中显示)。 该请求然后被发送到服务器。 在服务器上,我再次检查文件大小和types是否正确之前,写磁盘。 我能够通过使用Multiparty的part事件来做到这一点。 如果他们不正确,那么我只是发送一个413错误的回应。 (感谢josh3736澄清在浏览器中应该发生什么)。在发回413之后,浏览器的行为有点零星。 对于我正在testing的浏览器,它只显示了一个pending发布请求。 我相信这种行为是由于整个表格没有被处理,所以不会接受任何回应。 由于没有显示错误代码,这可能看起来不是最优雅的方式,但是这种行为只会被绕过客户端检查的恶意用户遇到(我的站点依赖于Javascript,所以所有用户都会拥有它如果他们想要使用我的网站启用)。 所以这是我的解决scheme,现在的一些代码…

 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); }); 

要中止上传,正确的事情是closures套接字。

 req.socket.end(); 

不幸的是, 服务器想中止正在进行的HTTP上传的情况是一团糟 。

在这里做适当的规范兼容的事情只是简单地发送一个HTTP 413响应 – 也就是说,只要你检测到客户端已经发送了比你想处理的更多的字节。 发送错误响应后是否终止套接字取决于您。 这符合RFC 2616. […]接下来发生的事情并不理想。

  • 如果您打开套接字,所有浏览器(Chrome 30,IE 10,Firefox 21)都将继续发送数据,直到整个file upload完成。 然后,只有这样,浏览器才会显示错误信息。 这真的很糟糕,因为用户必须等待整个文件完成上传,只有找出服务器拒绝它。 这也浪费你的带宽。

    浏览器当前的行为违反了RFC 2616§8.2.2:

    发送消息体的HTTP / 1.1(或更高版本)客户端应该在传输请求时监视networking连接是否有错误状态。 如果客户端看到错误状态,应立即停止传输正文。 如果正在使用“分块”编码发送正文(3.6节),则可以使用零长度的块和空的尾部过早地标记消息的结尾。 如果正文前面有一个Content-Length头,客户端必须closures连接。

    有开放的Chrome和Firefox的问题,但不要指望在短时间内修复。

  • 如果您在发送HTTP 413响应后立即closures套接字,所有浏览器都会立即停止上传,但是它们当前显示“连接重置”错误(或类似错误),而不是响应中可能发送的任何HTML。

    再说一次,这可能违反了规范(允许服务器尽早发送响应并closures连接),但我不希望浏览器在这里很快修复。

你看到一个POST请求循环的事实是可疑的。 你正在使用某种AJAX上传? 在您提前closures套接字后,可能会自动重试上载。

我试着用你的解决scheme,它似乎没有在我的机器上工作。 客户无法得到响应后,我把这一行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); } 

我find的唯一解决scheme是call part.resume(); 在form.on部分。 它可以在您的服务器上使用您的上传文件,而无需触摸磁盘。 但它会消耗您的networking和服务器资源。 无论如何,这比程序被阻止的情况要好得多。 这就是为什么我一直在寻找另一种更好的解决方法。

这是我的解决scheme:

 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) { .... } 

这是一个解决scheme,适用于我使用多方。

jfizz给出的答案不适用于所有的浏览器。

非常重要:正如上面的评论中提到的,你必须设置Connection标头closures!

 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.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);} }