-
Notifications
You must be signed in to change notification settings - Fork 124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: timeout option #201
feat: timeout option #201
Changes from 4 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,6 +36,7 @@ exports.httpsAgent.maxSockets = 1000; | |
*/ | ||
|
||
exports.TIMEOUT = ms('5s'); | ||
exports.TIMEOUTS = [ms('5s'), ms('5s')]; | ||
|
||
var REQUEST_ID = 0; | ||
var MAX_VALUE = Math.pow(2, 31) - 10; | ||
|
@@ -189,7 +190,7 @@ exports.requestWithCallback = function (url, args, callback) { | |
}); | ||
} | ||
|
||
args.timeout = args.timeout || exports.TIMEOUT; | ||
args.timeout = args.timeout || exports.TIMEOUTS; | ||
args.maxRedirects = args.maxRedirects || 10; | ||
args.streaming = args.streaming || args.customResponse; | ||
var requestStartTime = Date.now(); | ||
|
@@ -315,17 +316,29 @@ exports.requestWithCallback = function (url, args, callback) { | |
// you can use this hook to change every thing. | ||
args.beforeRequest(options); | ||
} | ||
var timer = null; | ||
var connectTimer = null; | ||
var responseTimer = null; | ||
var __err = null; | ||
var connected = false; // socket connected or not | ||
var keepAliveSocket = false; // request with keepalive socket | ||
var responseSize = 0; | ||
var responseAborted = false; | ||
var done = function (err, data, res) { | ||
if (timer) { | ||
clearTimeout(timer); | ||
timer = null; | ||
|
||
function cancelConnectTimer() { | ||
if (connectTimer) { | ||
clearTimeout(connectTimer); | ||
connectTimer = null; | ||
} | ||
} | ||
function cancelResponseTimer() { | ||
if (responseTimer) { | ||
clearTimeout(responseTimer); | ||
responseTimer = null; | ||
} | ||
} | ||
|
||
function done(err, data, res) { | ||
cancelResponseTimer(); | ||
if (!callback) { | ||
console.warn('[urllib:warn] [%s] [worker:%s] %s %s callback twice!!!', | ||
Date(), process.pid, options.method, url); | ||
|
@@ -409,9 +422,9 @@ exports.requestWithCallback = function (url, args, callback) { | |
res: response | ||
}); | ||
} | ||
}; | ||
} | ||
|
||
var handleRedirect = function (res) { | ||
function handleRedirect(res) { | ||
var err = null; | ||
if (args.followRedirect && statuses.redirect[res.statusCode]) { // handle redirect | ||
args._followRedirectCount = (args._followRedirectCount || 0) + 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 原来触发多次 callback 的问题就在这两个 error |
||
|
@@ -426,10 +439,7 @@ exports.requestWithCallback = function (url, args, callback) { | |
var _url = urlutil.resolve(url, location); | ||
debug('Request#%d %s: `redirected` from %s to %s', reqId, options.path, url, _url); | ||
// make sure timer stop | ||
if (timer) { | ||
clearTimeout(timer); | ||
timer = null; | ||
} | ||
cancelResponseTimer(); | ||
// should clean up headers.Host on `location: http://other-domain/url` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 看来原来的实现由 bug,MaxRedirectError 和 FollowRedirectError 的逻辑也得cancelResponseTimer |
||
if (args.headers && args.headers.Host && PROTO_RE.test(location)) { | ||
args.headers.Host = null; | ||
|
@@ -446,7 +456,7 @@ exports.requestWithCallback = function (url, args, callback) { | |
redirect: false, | ||
error: err | ||
}; | ||
}; | ||
} | ||
|
||
// set user-agent | ||
if (!options.headers['User-Agent'] && !options.headers['user-agent']) { | ||
|
@@ -459,7 +469,7 @@ exports.requestWithCallback = function (url, args, callback) { | |
} | ||
} | ||
|
||
var decodeContent = function (res, body, cb) { | ||
function decodeContent(res, body, cb) { | ||
var encoding = res.headers['content-encoding']; | ||
if (body.length === 0) { | ||
return cb(null, body, encoding); | ||
|
@@ -471,7 +481,7 @@ exports.requestWithCallback = function (url, args, callback) { | |
|
||
debug('gunzip %d length body', body.length); | ||
zlib.gunzip(body, cb); | ||
}; | ||
} | ||
|
||
var writeStream = args.writeStream; | ||
|
||
|
@@ -630,45 +640,78 @@ exports.requestWithCallback = function (url, args, callback) { | |
}); | ||
} | ||
|
||
var connectTimeout, responseTimeout; | ||
if (Array.isArray(args.timeout)) { | ||
connectTimeout = ms(args.timeout[0]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 这里不对,不需要再使用 ms parse 一次的,这里的 timeout 只支持 number 格式,不支持字符串的。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 额,如果传了 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 不支持,原来就不支持这种格式。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 现在不是支持两种么,一种是原来的指传一个时间,现在可以传一个或两个 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 但是timeout也是number,不是string啊 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 额,明白了。ms 只是在内部处理用的。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 好吧,原来也有这个逻辑。 |
||
responseTimeout = ms(args.timeout[1]); | ||
} else { // set both timeout equal | ||
connectTimeout = responseTimeout = ms(args.timeout); | ||
} | ||
debug('ConnectTimeout: %d, ResponseTimeout: %d', connectTimeout, responseTimeout); | ||
|
||
function startConnectTimer() { | ||
debug('Connect timer ticking, timeout: %d', connectTimeout); | ||
connectTimer = setTimeout(function () { | ||
connectTimer = null; | ||
var msg = 'Connect timeout for ' + connectTimeout + 'ms'; | ||
var errorName = 'ConnectionTimeoutError'; | ||
if (!req.socket) { | ||
errorName = 'SocketAssignTimeoutError'; | ||
msg += ', working sockets is full'; | ||
} | ||
__err = new Error(msg); | ||
__err.name = errorName; | ||
__err.requestId = reqId; | ||
debug('connectTimer boom!!!!'); | ||
debug('Request#%d %s %s: %s, connected: %s', reqId, url, __err.name, msg, connected); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 2个 debug 合并在一起吧 |
||
abortRequest(); | ||
}, connectTimeout); | ||
} | ||
|
||
function startResposneTimer() { | ||
debug('Response timer ticking, timeout: %d', responseTimeout); | ||
responseTimer = setTimeout(function () { | ||
responseTimer = null; | ||
var msg = 'Response timeout for ' + responseTimeout + 'ms'; | ||
var errorName = 'ResponseTimeoutError'; | ||
__err = new Error(msg); | ||
__err.name = errorName; | ||
__err.requestId = reqId; | ||
debug('responseTimer boom!!!!'); | ||
debug('Request#%d %s %s: %s, connected: %s', reqId, url, __err.name, msg, connected); | ||
abortRequest(); | ||
}, responseTimeout); | ||
} | ||
|
||
var req; | ||
// request headers checker will throw error | ||
try { | ||
req = httplib.request(options, onResponse); | ||
} catch (err) { | ||
return done(err); | ||
} | ||
// start connect timer just after `request` return | ||
startConnectTimer(); | ||
|
||
var abortRequest = function () { | ||
function abortRequest() { | ||
debug('Request#%d %s abort, connected: %s', reqId, url, connected); | ||
// it wont case error event when req haven't been assigned a socket yet. | ||
if (!req.socket) { | ||
__err.noSocket = true; | ||
done(__err); | ||
} | ||
req.abort(); | ||
}; | ||
|
||
var timeout = ms(args.timeout); | ||
} | ||
|
||
timer = setTimeout(function () { | ||
timer = null; | ||
var msg = 'Request timeout for ' + timeout + 'ms'; | ||
var errorName = connected ? 'ResponseTimeoutError' : 'ConnectionTimeoutError'; | ||
if (!req.socket) { | ||
errorName = 'SocketAssignTimeoutError'; | ||
msg += ', working sockets is full'; | ||
} | ||
__err = new Error(msg); | ||
__err.name = errorName; | ||
__err.requestId = reqId; | ||
debug('Request#%d %s %s: %s, connected: %s', reqId, url, __err.name, msg, connected); | ||
abortRequest(); | ||
}, timeout); | ||
|
||
req.once('socket', function (socket) { | ||
// https://github.com/iojs/io.js/blob/v1.x/lib/net.js#L342 | ||
if (socket.readyState === 'opening') { | ||
socket.once('connect', function () { | ||
// cancel socket timer at first and start tick for TTFB | ||
cancelConnectTimer(); | ||
startResposneTimer(); | ||
|
||
debug('Request#%d %s new socket connected', reqId, url); | ||
connected = true; | ||
}); | ||
|
@@ -678,6 +721,10 @@ exports.requestWithCallback = function (url, args, callback) { | |
debug('Request#%d %s reuse socket connected', reqId, url); | ||
connected = true; | ||
keepAliveSocket = true; | ||
|
||
// reuse socket, timer should be canceled. | ||
cancelConnectTimer(); | ||
startResposneTimer(); | ||
}); | ||
|
||
req.on('error', function (err) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
感觉变化了,原来是 5s 包含2个过程,现在变成了每个过程 5s。不过也还好。