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

How to cancel user upload in Formidable (Node.js)?

本文关键字:取消 用户 js Formidable Node      更新时间:2023-09-26

我已经处理这个问题两天了,我被卡住了。我在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