時間がかかってしまう可能性がある処理に対して、一定時間内に結果が返却できないときにはタイムアウトをエラーレスポンスで返す方法を調べました。結論としては、connect-timeoutを利用すれば実現できますが、いくつか注意点があります。
connect-timeout
connect-timeoutはExpress.jsでリクエストに対して一定時間でタイムアウトイベントを発生させるためのミドルウェアです。公式のドキュメントがあまり充実していないためテストコードを作り動作検証をしてみました。
わかったこと
- connect-timeoutを最初のミドルウェアに設定することで、指定時間経過時に例外を発生させ、エラー処理ミドルウェアに処理を渡すことができる。
- ルートの処理でレスポンスを送信する場合には、既にエラー処理ミドルウェアからレスポンスが送信されている可能性があるため、
req.timedout
(timeoutミドルウェアがタイムアウト発生時に設定する)を確認して、false
の場合にのみ行う。 - エラー処理でレスポンスを返却するときにはルートの処理でレスポンスが送信されている可能性があるため、
res.headersSent
がfalse
の場合のみ行う。 - タイムアウトが発生しエラー処理ミドルウェアが実行されても、実行中のミドルウェアもしくはルートの処理は停止せずに続行する。
- ただし、タイムアウト発生後に次のミドルウェアが実行されることはないので、公式ドキュメントに記載されているhaltOnTimeoutミドルウェアの設定は不要である。
動作検証コード
const co = require('co')
const express = require('express')
const sleep = require('sleep-promise')
const timeout = require('connect-timeout')
const app = express()
const timeout_ms = process.argv[2] || 1500
app.use(timeout(timeout_ms))
// ミドルウェア1
app.use(function(req, res, next) {
co(function*(){
console.log('middleware 1: begin')
yield sleep(1000)
console.log('middleware 1: end')
next()
})
})
app.use(haltOnTimedout)
// ミドルウェア2
app.use(function(req, res, next) {
console.log('middleware 2: begin')
next()
})
app.use(haltOnTimedout)
// GETルート
app.get('/', function(req, res, next) {
co(function*(){
console.log('app.get: begin')
yield sleep(1000)
if (!req.timedout) {
console.log('app.get: send response')
res.send('app.get')
}
console.log('app.get: end')
})
})
// エラー処理ミドルウェア
app.use(function (err, req, res, next) {
console.log('error filter: begin')
if(req.timedout && !res.headersSent) {
console.log('error filter: send response')
res.status(500).send('request timeout')
}
})
// タイムアウト発生時に以降のミドルウェアを停止するためのミドルウェア
// 公式のガイドラインで必要とされている
function haltOnTimedout (req, res, next) {
console.log('haltOnTimeout: begin')
if (!req.timedout) {
console.log('haltOnTimeout: call next()')
next()
}
}
app.listen(3000)
実行結果
$ node app.js 500
middleware 1: begin
error filter: begin
error filter: send response
middleware 1: end
$ node app.js 1500
middleware 1: begin
middleware 1: end
haltOnTimeout: begin
haltOnTimeout: call next()
middleware 2: begin
haltOnTimeout: begin
haltOnTimeout: call next()
app.get: begin
error filter: begin
error filter: send response
app.get: end
利用したバージョン
- Node.js 6.11.0
- express 4.16.2
- connect-timeout 1.9.0