Как отменить загрузку пользователя в Formidable (Node.js)?

Я работаю над этой проблемой уже два дня, и я застрял. Я использую Node.js с Express, и я пытаюсь реализовать форму загрузки. В принципе, я хочу, чтобы форма делала следующее:

  • проверьте размер файла и отмените загрузку, если она слишком велика (когда я говорю "отмена", я имею в виду предотвращение записи любых дополнительных данных на диск и удаление временного файла)

  • Проверьте тип файла и убедитесь, что это правильный тип.( формат JPG. ,png и др.), если это не так, то прекратите дальнейшую запись на диск и удалите временный файл.

в настоящее время у меня работает загрузка, и я выдаю ошибки, когда файл слишком большой или он не соответствует правильному типу, а затем я удаляю файл с fs.unlink() после того, как весь файл не будет записан на диск. Но я вижу потенциальную проблему с этим подходом: что, если пользователь загружает огромный файл (размер ГБ)? С моим подходом прямо сейчас, это будет в конечном итоге будет удален с моей машины, но не после потери тонны ресурсов. Поэтому в основном я хочу использовать минимальное количество ресурсов, чтобы подтвердить, что файл подходит для загрузки. Это код у меня есть до сих пор:

    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, он говорит:

запрос, который выдает ошибку автоматически приостанавливается, вам придется запрос вызова вручную.resume() если вы хотите, чтобы запрос продолжал запускать события "данные".

это было бы здорово, но я не могу заставить его работать. Всякий раз, когда я выдаю "ошибку", события "данные" продолжают срабатывать до завершения.

попытки

Я попытался отменить запрос когда произошла ошибка, но безрезультатно. req.pause() ничего не сделал для меня, req.end() и req.abort() дал мне ошибку, сказав, что это не метод, и req.connection.destroy() и req.connection.end() просто отправил цикл запросов POST.

Заключение

Итак, то, что я ищу, кажется, должно быть общим местом, но я провел последние два дня в интернете по тщательной реализации, и я ничего не могу найти. Я имею в виду, что легко проверить размер и тип файла после того, как все это было загружено, но кто хочет тратить все эти ресурсы? Не говоря уже о том, что злонамеренные пользователи могут это сделать.

Я буду продолжать работать, пока не получу именно то, что я ищу, но я думал, что эта проблема может быть актуальна для некоторых других пользователей, и, надеюсь, я могу получить некоторую помощь!

Спасибо за ваше время.

7 ответов


прошло некоторое время и теперь вы можете использовать мултэр вместо грозного или многопартийной. Multer имеет желаемую встроенную функциональность.


я попытаюсь ответить на свой собственный вопрос...

поэтому после некоторых проб и ошибок с грозным, я просто сдался и переключился на многопартийной. Multiparty фактически отменяет загрузку, когда выдается ошибка, которая была огромной.

Мое Решение

поэтому мое решение использует проверку размера и типа на стороне клиента (не показано в коде). Затем запрос отправляется на сервер. На сервере я снова проверяю, если файл размер и тип правильны перед записью на диск. Я могу сделать это с помощью многопартийной это part событие. Если они не верны, я просто отправляю ответ с ошибкой 413. (Спасибо josh3736 для выяснения того, что должно произойти в браузере.) После отправки обратно 413, поведение браузера немного спорадический характер. Для браузера, в котором я тестировал, он просто показал pending запрос post. Я считаю, что такое поведение связано с тем, что форма не обрабатывается, поэтому она не будет принимать никаких ответов. Это может показаться не самым элегантным способом справиться с этим, так как код ошибки не отображается, но это поведение будет встречено только злоумышленниками, которые обходят проверку на стороне клиента (мой сайт зависит от 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, - это беспорядок.

правильная, spec-совместимая вещь, которую нужно сделать здесь, - это просто отправить 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, все браузеры, очевидно, немедленно прекратят загрузку, но в настоящее время они показывают " соединение ошибка "reset" (или аналогичная), а не любой HTML, который вы можете отправить в ответе.

    опять же, это, вероятно, нарушение спецификации (которая позволяет серверу отправлять ответ раньше и закрывать соединение), но я бы не ожидал, что исправления браузера в ближайшее время здесь.

тот факт, что вы видите цикл POST запросы подозрительны. Вы используете какой-то загрузчик AJAX? Он может автоматически повторить попытку загрузки после закрытия розетка рано.


форма.пауза фактически предотвратит отправку браузером большего количества данных. Вызов ниже функция сделала это для меня:

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'); в форме.по разделу. Похоже, моя программа была заблокирована. это означало, что вы не можете вызвать свой метод загрузки дважды. второй вызов не был выполнен, так как ваша программа была заблокирована этой строкой: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);
    }

единственным решением, которое я нашел, был call 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 упоминалось в комментарии выше, вы должны установить заголовок соединения, чтобы закрыть!

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