From ec8e8c67083404440d40067e5a40351521295397 Mon Sep 17 00:00:00 2001 From: Jannis R Date: Wed, 3 Mar 2021 13:54:07 +0100 Subject: connect()/request(): add connectTimeout with 60s default closes #7 --- client.js | 3 +++ connect.js | 29 +++++++++++++++++++++++++++++ readme.md | 4 ++++ 3 files changed, 36 insertions(+) diff --git a/client.js b/client.js index f5c034a..afe37ee 100644 --- a/client.js +++ b/client.js @@ -135,6 +135,7 @@ const sendGeminiRequest = (pathOrUrl, opt, done) => { useClientCerts, letUserConfirmClientCertUsage, clientCertStore, + connectTimeout, tlsOpt, verifyAlpnId, } = { @@ -150,6 +151,7 @@ const sendGeminiRequest = (pathOrUrl, opt, done) => { useClientCerts: false, letUserConfirmClientCertUsage: null, clientCertStore: defaultClientCertStore, + connectTimeout: 60 * 1000, // 60s tlsOpt: {}, ...opt, } @@ -175,6 +177,7 @@ const sendGeminiRequest = (pathOrUrl, opt, done) => { let reqOpt = { hostname: target.hostname || 'localhost', port: target.port || DEFAULT_PORT, + connectTimeout, tlsOpt, } diff --git a/connect.js b/connect.js index 1c216a5..d508d35 100644 --- a/connect.js +++ b/connect.js @@ -16,11 +16,13 @@ const connectToGeminiServer = (opt, cb) => { hostname, port, cert, key, passphrase, + connectTimeout, tlsOpt, } = { hostname: '127.0.0.1', port: DEFAULT_PORT, cert: null, key: null, passphrase: null, + connectTimeout: 60 * 1000, // 60s tlsOpt: {}, ...opt, } @@ -34,6 +36,30 @@ const connectToGeminiServer = (opt, cb) => { ...tlsOpt, }) + // Sets the socket to timeout after timeout milliseconds of inactivity on + // the socket. By default net.Socket do not have a timeout. + // When an idle timeout is triggered the socket will receive a 'timeout' + // event but the connection will not be severed. The user must manually + // call socket.end() or socket.destroy() to end the connection. + // https://nodejs.org/api/net.html#net_socket_setnodelay_nodelay + let timeoutTimer = null + const onTimeout = () => { + clearTimeout(timeoutTimer) + const err = new Error('connect timeout') + err.timeout = connectTimeout + err.code = 'ETIMEDOUT' // is it okay to mimic syscall errors? + err.errno = -60 + socket.destroy(err) + } + socket.once('timeout', onTimeout) + if (connectTimeout !== null) { + // This sets the timeout for inactivity on the *socket* layer. But the + // TLS handshake might also stall. This is why we also set one manually. + // see also https://github.com/nodejs/node/issues/5757 + socket.setTimeout(connectTimeout) + timeoutTimer = setTimeout(onTimeout, connectTimeout) + } + let cbCalled = false socket.once('error', (err) => { if (cbCalled) return; @@ -42,6 +68,9 @@ const connectToGeminiServer = (opt, cb) => { }) socket.once('secureConnect', () => { if (cbCalled) return; + // If timeout is 0, then the existing idle timeout is disabled. + // https://nodejs.org/api/net.html#net_socket_setnodelay_nodelay + socket.setTimeout(0) cbCalled = true cb(null, socket) }) diff --git a/readme.md b/readme.md index 791efe2..3b3ff89 100644 --- a/readme.md +++ b/readme.md @@ -135,6 +135,8 @@ request(pathOrUrl, opt = {}, cb) useClientCerts: false, letUserConfirmClientCertUsage: null, clientCertStore: defaultClientCertStore, + // time to wait for socket connection & TLS handshake + connectTimeout: 60 * 1000, // 60s // additional options to be passed into `tls.connect` tlsOpt: {}, } @@ -155,6 +157,8 @@ connect(opt = {}, cb) port: 1965, // client certificate cert: null, key: null, passphrase: null, + // time to wait for socket connection & TLS handshake + connectTimeout: 60 * 1000, // 60s // additional options to be passed into `tls.connect` tlsOpt: {}, } -- cgit v1.3