express响应超时应该怎么做?
发布于 3 年前 作者 lzszone 7563 次浏览 来自 问答

现在项目遇到了一点问题,表现是命令行有打印,能收到请求,但是不吐数据, 经坛友指点,现在怀疑是资源没释放导致的,所以想加一个超时处理, 然后看了express的教程,貌似可以用connect-timeout中间件解决 但是这个中间件在项目里始终要报can’t set headers after they are sent. 目前也没什么好的解决办法,各位有主意吗?

21 回复

你异步没处理好吧!

来自酷炫的 CNodeMD

@Aoqin 问题是,我用express-generator生成的新项目引用这个模块仍然报错啊

能放出代码看一下吗?

来自酷炫的 CNodeMD

虽然楼主没贴代码,但根据我踩坑的经验 把调用中间件的next()改成 return next()试试

代码 next 没有处理好

抱歉各位,上午电脑出问题了@cctv1005s @Yuki-Minakami @htoooth 现在也没办法贴代码 ,不过代码大概就这样子:

const timeout = require('connect-timeout');
const timeoutHandler = function(req, res, next) {
	if(!req.timedout) next();
}
app.use(timeout('10s'));
app.use(......)
app.use(timeoutHandler)

当天我没注意看文档,昨天意识到了,它是说不建议用作顶级中间件是吧 当作为顶级中间件的时候,是不是每调用一个中间件都要在后面再调用一次handler?@Yuki-Minakami 那他也没说不当顶级中间件该咋使呀。。。T T 我待会试试你的return next(); 各位 再看看

首先,你的错误和timeout是不是顶层中间件没关系 第二,如果做顶层中间件也可以,你也可以在所有中间件调用的最后调一次handler,就像这样

	app.use(timeout);
	app.use(middleware1);
	app.use(middleware2);
	app.use(middleware3);
	app.use(handler)

但这样不推荐,因为如果中间有太多中间件就不好控制整体的执行时间 最后,你目前贴的代码看不出毛病,但八九不离十是哪个异步的中间件前面需要加return

@Yuki-Minakami 那应该在哪里使用timeout中间件呢? 应该是在具体业务流程开始之前使用吗?

帖一下全部代码:

**app.js**
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
const timeout = require('connect-timeout');

var index = require('./routes/index');
var users = require('./routes/users');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));

const handleTimeout = (req, res, next) => {
  if(!req.timedout) next();
}

app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(timeout('5s'));
app.use('/', index);
app.use('/users', users);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  var err = new Error('Not Found');
  err.status = 404;
  return next(err);
});

// error handler
app.use(handleTimeout);
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  return res.render('error');
});

module.exports = app;

**users.js**
var express = require('express');
var router = express.Router();

/* GET users listing. */
router.get('/', function(req, res, next) {
  setTimeout(function() {    
    return res.send('respond with a resource');
  }, 10000);
});

module.exports = router;

当我访问users的时候,页面在5秒时会打印503报错页,是对的 但是10秒的时候,app会报错:

_http_outgoing.js:504
    throw new Error('Can\'t set headers after they are sent.');
    ^

Error: Can't set headers after they are sent.
    at validateHeader (_http_outgoing.js:504:11)
    at ServerResponse.setHeader (_http_outgoing.js:511:3)
    at ServerResponse.header (D:\test\app\node_modules\express\lib\response.js:730:10)
    at ServerResponse.send (D:\test\app\node_modules\express\lib\response.js:170:12)
    at Timeout._onTimeout (D:\test\app\routes\users.js:7:16)
    at ontimeout (timers.js:488:11)
    at tryOnTimeout (timers.js:323:5)
    at Timer.listOnTimeout (timers.js:283:5)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! app@0.0.0 start: `node ./bin/www`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the app@0.0.0 start script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\lz\AppData\Roaming\npm-cache\_logs\2017-06-05T07_01_18_145Z-debug.log

@Aoqin @cctv1005s @Yuki-Minakami @htoooth ,大家再看看,感激不尽

问题就是出在10秒后会调用res.send方法,但是应该怎么写呢,在正常的逻辑中,肯定要涉及到渲染输出到res的; 根据这个timeoutHandler的写法来看,是不是在设计的时候应该把渲染输出到res和数据库操作等有可能超时的操作分离,然后在每一处可能造成超时的中间件调用完成之后加上app.use(timeoutHandler);?

嗯,大致明白了,和开始想的不大一样,不好意思前面可能误导你了

超时时间设置了5秒,ok,5秒后超时返回了错误码,这个http请求就结束了 然而10秒后还有个res需要输出,对一个已经结束的进行响应,这就是错误的原因

要改的话就把app.use(’/users’, users);这句放到外面



	app.use(timeout('5s'));
	app.use('/', index);	
	  // catch 404 and forward to error handler
 	 app.use(function(req, res, next) {
		var err = new Error('Not Found');
		err.status = 404;
		return next(err);
 	 });

	// error handler
	app.use(handleTimeout);
	
	app.use('/users', users);

这样10秒后就没有cant send header again的错误了,但这样就舍本逐末了

根本问题应该是面对一个未知耗时的异步操作,怎么用connect-time来管理 这部分让我回家想想…

你报的这个错误我在egg里不使用yield的时候也遇到过

app.set(‘view engine’, ‘pug’);这是个什么模板

@Yuki-Minakami 有点搅,这中间件github才两百星,3个未解决的issue是关于这个问题的,貌似啊,记不清了

@yangkuo1993 原来的jade

@wangchaoduo 能具体回忆下吗

这个目前还真没啥好办法, 你这个场景其实只要想办法让10秒后的那个回调不执行就好了 具体可以使用事件监听,如果超过了timeout时间,就清除掉后面的setTimeout

@lzszone 他的问题对你的应该没啥帮助

@Yuki-Minakami 对,思路是这样的,他源码很短90行,然后看了下,感觉这玩意不好用。。。怪不得才200星,剩下的我自己想吧,谢谢

楼主我写了篇文章关于超时处理的,可以看看 https://cnodejs.org/topic/5936cd57538dbcaa6bc7dd8f

你可以设个类似开关的值,只要res.send后就变为false,发送前先判定次

来自酷炫的 CNodeMD

回到顶部