如何从数据事件中取消HTTP上传?
鉴于这个简单的Web服务器代码:
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被引用时,我的数据函数被触发一次或者多次或者多次。 有时候(像一个巨大的文件被发送) 我想取消这个数据事件,并触发到用户的terminalfunction (稍后我会显示“你的文章/文件太大”)。 我该怎么做?
在这里做适当的规范兼容的事情只是简单地发送一个HTTP 413响应 – 也就是说,只要你检测到客户端发送了比你想处理的更多的字节。 发送错误响应后是否终止套接字取决于您。 这符合RFC 2616:(强调增加)
413请求实体太大
服务器拒绝处理请求,因为请求实体大于服务器愿意或能够处理的大小。 服务器可以closures连接以防止客户端继续请求。
接下来发生的事情并不理想。
-
如果您打开套接字,所有浏览器(Chrome 30,IE 10,Firefox 21)都将继续发送数据,直到整个file upload完成。 然后,只有这样,浏览器才会显示错误信息。 这真的很糟糕,因为用户必须等待整个文件完成上传,只有找出服务器拒绝它。 这也浪费你的带宽。
浏览器当前的行为违反了RFC 2616§8.2.2:
发送消息体的HTTP / 1.1(或更高版本)客户端应该在传输请求时监视networking连接是否有错误状态。 如果客户端看到错误状态,应立即停止传输正文。 如果正在使用“分块”编码发送正文(3.6节),则可以使用零长度的块和空的尾部过早地标记消息的结尾。 如果正文前面有一个Content-Length头,客户端必须closures连接。
有开放的Chrome和Firefox的问题,但不要指望在短时间内修复。
-
如果您在发送HTTP 413响应后立即closures套接字,所有浏览器显然将立即停止上传,但是
它们大多数显示“连接重置”错误(或类似的错误),而不是您可能在响应中发送的任何HTML。同样,这可能违反了规范(允许服务器尽早发送响应并closures连接),但是我不希望浏览器在这里很快修复。
更新:从4/15开始,当您提早closures连接时,Chrome浏览器可能会显示您的413 HTML。 这仅适用于浏览器在Linux和Mac OS X上运行。在Windows上,Chrome仍然显示
ERR_CONNECTION_RESET
networking错误,而不是您发送的HTML。 (IE 11和Firefox 37继续在所有平台上显示networking错误。)
所以你用传统的纯HTTP上传的select是:
-
显示一个友好的错误消息,但只有在上传运行完成后。 这浪费了时间和带宽。
-
快速失败,但留下用户与一个神秘的浏览器错误屏幕混淆。
这里最好的方法就是使用AJAX上传器,在这里你可以更好地控制用户体验。 你应该仍然提供一个传统的上传表单作为后备,我会使用“快速失败”选项(closures套接字),以防止浪费时间和带宽。
以下是一些示例代码,如果它收到1 kB以上的请求,则会终止该请求。 我正在使用Express,但同样应该适用于节点的香草HTTP库。
注意:实际上,您应该使用强大的 多方处理您的上传(这是Connect / Express使用的),并且它有自己的方式来监控上传数据。
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);
请求参数是一个http.IncomingMessage类,它不允许停止stream。
但是你可以访问底层套接字 ,你可以放弃它:
request.socket.end('too big !');
但我不确定浏览器会喜欢它…他可能会抱怨,并表示连接被closures。
这是我的解决scheme:
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) { .... }
我用这样的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, eg 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) */