20

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

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

4

4 回答 4

39

正确的、符合规范的做法是尽早发送 HTTP 413响应——也就是说,只要您检测到客户端发送的字节数超过您想要处理的字节数。发送错误响应后是否终止套接字取决于您。这符合 RFC 2616:(添加了重点)

413请求实体太大

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

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

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

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

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

    存在打开的 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 库。

注意:实际上,您应该使用强大的 多方来处理您的上传(这是 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'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);
于 2013-08-22T02:30:29.207 回答
2

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

但是您可以访问底层的socket,并且可以中止它:

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

但我不确定浏览器是否会喜欢它......他可能会抱怨并指出连接被不正确地关闭。

于 2013-08-21T22:11:51.857 回答
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) {
    ....
}
于 2014-05-08T05:35:00.650 回答
0

我以这种方式使用强大的:

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

于 2016-05-13T04:35:45.013 回答