Подтвердить что ты не робот

Как отменить загрузку HTTP из событий данных?

Учитывая этот простой код веб-сервера:

console.log('starting');
var server = require('http').createServer();

server.on('connection',function(socket){console.log('*server/connection');});
server.on(
    'request',
    function(request, response){
        console.log('*server/request');
        request.on(
            'data',
            function(chunk){
                console.log('*request/data');
                // <!> How do I abort next data calls from here?
            }
        );
        request.on(
            'readable',
            function(chunk){
                console.log('*request/readable');
                // <!> How do I abort next readable calls from here?
            }
        );
        request.on(
            'end',
            function(){
                console.log('*request/end');
                response.writeHead(200,"OK");
                response.write('Hello');
                response.end();
            }
        );
        request.on('close',function(){ console.log('*request/close'); } );
        request.on('error',function(){ console.log('*request/error'); } );
    }
);
server.on('close',function(){console.log('server/close');});
server.on('checkContinue',function(request, response){console.log('*server/checkContinue');});
server.on('connect',function(request, socket, head){console.log('*server/connect');});
server.on('upgrade',function(request, socket, head){console.log('*server/upgrade');});
server.on('clientError',function(exception, socket){console.log('*server/clientError');});

server.listen(8080);
console.log('started');

Когда отправляется POST или FILE, моя функция данных запускается один или несколько или несколько раз. Иногда (как чудовищный большой файл отправляется) Я хочу отменить это на событиях данных и запустить функцию конца для пользователя (позже я покажу "ваш пост/файл слишком большой" ), Как это сделать?

4b9b3361

Ответ 1

Собственная, совместимая со спецификацией вещь, которую нужно сделать, это просто отправить HTTP 413 ответ раннего времени – то есть, как только вы обнаружите, что клиент отправил больше байтов, чем вы хотите обработать. Это зависит от вас, прекратите ли вы сокет после отправки ответа об ошибке. Это соответствует RFC 2616: (выделено курсивом)

413 Объект запроса слишком большой

Сервер отказывается обрабатывать запрос, поскольку объект запроса больше, чем сервер готов или способен обрабатывать. Сервер МОЖЕТ закрыть соединение, чтобы клиент не продолжал запрос.

То, что происходит дальше, не является идеальным.

  • Если вы оставите сокет открытым, все браузеры (Chrome 30, IE 10, Firefox 21) будут продолжать отправлять данные до тех пор, пока весь файл не будет загружен. Затем и только тогда браузер отобразит ваше сообщение об ошибке. Это действительно отстой, поскольку пользователь должен дождаться завершения всего файла, только чтобы узнать, что сервер отклонил его. Он также снижает вашу пропускную способность.

    Текущее поведение браузеров является нарушением RFC 2616 § 8.2.2:

    Клиент HTTP/1.1 (или более поздний), отправляющий тело сообщения, ДОЛЖЕН отслеживать сетевое подключение для состояния ошибки, пока он передает запрос. Если клиент видит состояние ошибки, он ДОЛЖЕН немедленно прекратить передачу тела. Если тело отправляется с использованием "закодированного" кодирования (раздел 3.6), то для того, чтобы преждевременно пометить конец сообщения, можно использовать кусок нулевой длины и пустой трейлер. Если телу предшествовал заголовок Content-Length, клиент ДОЛЖЕН закрыть соединение.

    Есть открыть Chrome и Firefox, но не ожидайте исправления в ближайшее время.

  • Если вы закроете сокет сразу после отправки ответа HTTP 413, все браузеры, очевидно, перестанут загружаться сразу, но they в настоящее время отображается сообщение "connection reset" (или аналогичное), а не HTML, который вы могли бы отправить в ответ.

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

    Обновление: Начиная с 4/15, Chrome может отображать ваш 413 HTML, когда вы рано закрываете соединение. Это работает только в том случае, если браузер работает в Linux и Mac OS X. В Windows Chrome по-прежнему отображает сетевую ошибку ERR_CONNECTION_RESET, а не отправляемый HTML-код. (IE 11 и Firefox 37 продолжают показывать сетевую ошибку на всех платформах.)

Итак, ваш выбор с традиционными обычными HTTP-загрузками:

  • Показать дружественное сообщение об ошибке, но только после завершения загрузки. Это отнимает время и пропускную способность.

  • Сбой быстро, но оставьте пользователей путать с криптовальным экраном ошибки браузера.

Лучше всего здесь, вероятно, использовать загрузчик AJAX, где у вас больше контроля над пользовательским интерфейсом. Вы должны по-прежнему предоставлять форму загрузки в качестве резервной копии, и я бы использовал опцию "fail fast" (закрыть сокет), чтобы предотвратить потраченное впустую время и пропускную способность.

Вот пример кода, который убивает запрос, если он получает более 1 КБ. Я использую Express, но то же самое должно применяться с node ванильной HTTP-библиотекой.

Примечание: В действительности вы должны использовать formidable multiparty для обработки ваших загрузок (это то, что использует Connect/Express), и имеет свой собственный способ для мониторинга загружаемых данных.

var express = require("express")
    , app = express();

app.get('/', function(req, res) {
    res.send('Uploads &gt; 1 kB rejected<form action="/upload" method="post" enctype="multipart/form-data"><input type="file" name="file"><input type="submit"></form>');
});

app.post('/upload', function(req, res) {
    var size = 0;

    var gotData = function(d) {
        size += d.length; // add this chunk size to the total number of bytes received thus far
        console.log('upload chunk', size);
        if (size > 1024) {
            console.log('aborting request');
            req.removeListener('data', gotData); // we need to remove the event listeners so that we don't end up here more than once
            req.removeListener('end', reqEnd);
            res.header('Connection', 'close'); // with the Connection: close header set, node will automatically close the socket...
            res.send(413, 'Upload too large'); // ... after sending a response
        }
    };

    var reqEnd = function() {
       res.send('ok, got ' + size + ' bytes');
    }

    req.on('data', gotData);

    req.on('end', reqEnd);
});

app.listen(3003);

Ответ 2

Параметр запроса представляет собой http.IncomingMessage класс, который не позволяет остановить поток.

Но у вас есть доступ к базовому socket, и вы можете прервать его:

request.socket.end('too big !');

Но я не уверен, что браузер понравится... Он, вероятно, будет жаловаться и указывает, что соединение было закрыто неправильно.

Ответ 3

Вот мое решение:

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

Ответ 4

Я использую Formableable следующим образом:

var formidable = require('formidable'),
    http = require('http'),
    util = require('util');

var MaxFieldSize = 1000 * 1000,
    MaxFields = 100,
    MaxUploadSize = 8 * 1000 * 1000;


http.createServer (function(req, res) {

    console.log (req.url);
    console.log (req.headers ["content-type"]);

    if (req.url == '/upload')
    {
        var form = new formidable.IncomingForm();

        form.maxFieldsSize = MaxFieldSize;
        form.maxFields = MaxFields;

        form.on ('progress', function (bytesReceived, bytesExpected) {
            //console.log (bytesReceived, bytesExpected);
            if (bytesReceived > MaxUploadSize)
            {
                console.log ('*** TOO BIG');

                // ***HACK*** see Formidable lib/incoming_form.js
                // forces close files then triggers error in form.parse below
                // bonus: removes temporary files
                // --> use throttling in Chrome while opening /tmp in nautilus
                //     and watch the files disappear
                form.__2big__ = true;
                form._error (new Error ('too big'));

                //req.connection.destroy (); --- moved to form.parse
            }
        });

        form.parse (req, function (err, fields, files) {
            if (err)
            {
                console.log ('*** A', err);
                try // just in case something is wrong with the connection, e.g. closed
                {
                    // might not get through?
                    if (form.__2big__)
                    {
                        res.writeHead (413, {"connection": 'close', "content-type": 'text/plain'});
                        res.end ('upload to big');
                    }
                    else
                    {
                        res.writeHead (500, {"connection": 'close', "content-type": 'text/plain'});
                        res.end ('something wrong');
                    }
                    req.connection.destroy ();
                }
                catch (err)
                {
                    console.log ('*** B', err);
                }
            }
            else
            {
                res.writeHead (200, {"content-type": 'text/plain'});
                res.write ('received upload:\n\n');
                //for (let f in files)
                //    console.log (f, files [f]);
                res.end (util.inspect ({fields: fields, files: files}));
            }
        });
    }
    else
    {
        res.writeHead (200, {"content-type": 'text/html'});
        res.end (
            '<html>\
                <head>\
                    <meta charset="UTF-8">\
                    <title>Test Formidable</title>\
                </head>\
                <body>\
                    <form action="/upload" method="POST" enctype="multipart/form-data">\
                        <input type="hidden" name="foo" value="1">\
                        <input type="text" name="fooh" value="2"><br>\
                        <input type="file" name="bar"><br>\
                        <input type="file" name="baz"><br>\
                        <input type="file" name="boo"><br>\
                        <button type="submit">Submit</submit>\
                    </form>\
                </body>\
            </html>'
        );
    }
}).listen(8080);


/* terminal:
    #> node upload
    /upload
    multipart/form-data; boundary=----WebKitFormBoundaryvqt1lXRmxeHLZtYi
    *** TOO BIG
    *** A Error: too big
        at IncomingForm.<anonymous> (/home/marc/Project/node/upload.js:33:18)
        at emitTwo (events.js:106:13)
        at IncomingForm.emit (events.js:191:7)
        at IncomingForm.write (/home/marc/Project/node/node_modules/formidable/lib/incoming_form.js:155:8)
        at IncomingMessage.<anonymous> (/home/marc/Project/node/node_modules/formidable/lib/incoming_form.js:123:12)
        at emitOne (events.js:96:13)
        at IncomingMessage.emit (events.js:188:7)
        at IncomingMessage.Readable.read (_stream_readable.js:387:10)
        at flow (_stream_readable.js:764:26)
        at resume_ (_stream_readable.js:744:3)
*/