鉴于这个简单的网络服务器代码:
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功能(稍后我将显示“你的帖子/文件太大了”) 。我该怎么做?
答案 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 Chrome和Firefox问题,但不要指望很快就能修复。
如果您在发送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 > 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)
*/