如何从数据事件中取消HTTP上传?

时间:2013-08-21 21:14:49

标签: node.js

鉴于这个简单的网络服务器代码:

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时,我的on数据功能会被触发一次或多次。有时候(就像发送一个可怕的大文件一样)我想在数据事件上取消它并触发用户的on end功能(稍后我将显示“你的帖子/文件太大了”) 。我该怎么做?

4 个答案:

答案 0 :(得分:29)

在这里做正确的,符合规范的事情就是尽早发送HTTP 413响应 - 也就是说,只要您检测到客户端发送的字节数超出了您想要处理的数量。在发送错误响应后是否终止套接字取决于您。这符合RFC 2616 :(强调添加)

  

413请求实体太大

     

服务器拒绝处理请求,因为请求实体大于服务器愿意或能够处理的请求实体。 服务器可以关闭连接以防止客户端继续请求。

接下来发生的事情并不理想。

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

    浏览器的当前行为违反了RFC2616§8.2.2

      

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

    open ChromeFirefox问题,但不要指望很快就能修复。

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

    同样,这可能违反规范(允许服务器提前发送响应并关闭连接),但我不希望浏览器在这里很快修复。

    更新:截至4月15日,当您提前关闭连接时,Chrome 可能会显示您的413 HTML。这仅适用于浏览器在Linux和Mac OS X上运行的情况。在Windows上,Chrome仍会显示ERR_CONNECTION_RESET网络错误,而不是您发送的HTML。 (IE 11和Firefox 37继续只在所有平台上显示网络错误。)

因此,您对传统普通HTTP上传的选择是:

  • 显示友好的错误消息,但仅在上传完成后才显示。这浪费了时间和带宽。

  • 快速失败,但让用户对隐藏的浏览器错误屏幕感到困惑。

这里最好的选择可能是使用AJAX上传器,您可以更好地控制用户体验。你仍然应该提供一个传统的上传表格作为后备,我会使用“快速失败”选项(关闭套接字)来防止浪费时间和带宽。

以下是一些示例代码,如果收到的请求超过1 kB,则会终止该请求。我正在使用Express,但同样适用于node的vanilla HTTP库。

注意:实际上,您应该使用 formidable multiparty来处理您的上传(这是Connect / Express使用的),它有own way来监控上传数据。

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's 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);

答案 1 :(得分:1)

请求参数是http.IncomingMessage类,不允许停止流。

但您可以访问基础socket,并且可以中止它:

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

但我不确定浏览器是否会喜欢它......他可能会抱怨并表示连接已经不正常关闭。

答案 2 :(得分:0)

这是我的解决方案:

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

答案 3 :(得分:0)

我以这种方式使用Formidable:

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)
*/