From 548381f06069918c04773b3c8f20c08ce9493cad Mon Sep 17 00:00:00 2001 From: David Patty Date: Tue, 25 Jun 2019 15:46:11 -0600 Subject: [PATCH 1/3] clean up test --- test/eventsource_test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/eventsource_test.js b/test/eventsource_test.js index a734cad4..180f704d 100644 --- a/test/eventsource_test.js +++ b/test/eventsource_test.js @@ -885,7 +885,7 @@ describe('Reconnection', function () { var fn = writeEvents(events) fn(req, res) eventsSent++ - // now cause a parse error! + // now cause a few errors fn(req, res) eventsSent++ } else { @@ -899,7 +899,6 @@ describe('Reconnection', function () { assert.equal(EventSource.CONNECTING, es.readyState) es.reconnectInterval = 0 es.onerror = function (err) { - console.log(err); errorOccurred = !!(errorOccurred || err) } }) From 409469fc242facd768bfc091e10eb175c0e11a20 Mon Sep 17 00:00:00 2001 From: David Patty Date: Fri, 28 Jun 2019 12:26:58 -0600 Subject: [PATCH 2/3] EventSource connection retry timeouts is configurable to allow exponential backoff. use EventSource.setupReconnectInterval. this resets back to the default after every successful reconnection --- lib/eventsource.js | 33 ++++++++++++++++++- test/eventsource_test.js | 70 ++++++++++++++++++++++++++++++++++------ 2 files changed, 93 insertions(+), 10 deletions(-) diff --git a/lib/eventsource.js b/lib/eventsource.js index ac8f6c8a..84a7848c 100644 --- a/lib/eventsource.js +++ b/lib/eventsource.js @@ -44,7 +44,7 @@ function EventSource (url, eventSourceInitDict) { }) var self = this - self.reconnectInterval = 1000 + self.reconnectInterval = startingReconnectInterval self.connectionInProgress = false function onConnectionClosed (message) { @@ -65,6 +65,13 @@ function EventSource (url, eventSourceInitDict) { self.connectionInProgress = true connect() }, self.reconnectInterval) + + increaseReconnectInterval() + } + + function increaseReconnectInterval () { + self.reconnectInterval *= reconnectIntervalBackoffMultiple + self.reconnectInterval = Math.min(maxReconnectInterval, self.reconnectInterval) } var req @@ -133,7 +140,9 @@ function EventSource (url, eventSourceInitDict) { } req = (isSecure ? https : http).request(options, function (res) { + // release the lock on making connections self.connectionInProgress = false + // Handle HTTP errors if (res.statusCode === 500 || res.statusCode === 502 || res.statusCode === 503 || res.statusCode === 504) { _emit('error', new Event('error', {status: res.statusCode, message: res.statusMessage})) @@ -160,6 +169,9 @@ function EventSource (url, eventSourceInitDict) { } readyState = EventSource.OPEN + // reset reconnect interval now that we've actually made a connection + self.reconnectInterval = startingReconnectInterval + res.on('close', function () { res.removeAllListeners('close') res.removeAllListeners('end') @@ -351,6 +363,25 @@ EventSource.prototype.close = function () { this._close() } +// set defaults to 30s max, and exponentially back off @ 2^n +var startingReconnectInterval = 1000 +var maxReconnectInterval = 1000 * 30 +var reconnectIntervalBackoffMultiple = 2 +// setMaxReconnectInterval allows consumers to configure the max reconnect interval, and the backoff +EventSource.prototype.setupReconnectInterval = function (starting, max, multiple) { + if (Number.isInteger(starting) && starting >= 0) { + startingReconnectInterval = starting + } + if (Number.isInteger(max) && max >= 0) { + maxReconnectInterval = max + } + if (Number.isFinite(multiple) && multiple > 0) { + reconnectIntervalBackoffMultiple = multiple + } + + this.reconnectInterval = startingReconnectInterval +} + /** * Emulates the W3C Browser based WebSocket interface using addEventListener. * diff --git a/test/eventsource_test.js b/test/eventsource_test.js index 180f704d..287d5702 100644 --- a/test/eventsource_test.js +++ b/test/eventsource_test.js @@ -646,7 +646,7 @@ describe('HTTPS Client Certificate Support', function () { describe('Reconnection', function () { it('is attempted when server is down', function (done) { var es = new EventSource('http://localhost:' + _port) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onerror = function () { es.onerror = null @@ -669,7 +669,7 @@ describe('Reconnection', function () { server.on('request', writeEvents(['data: hello\n\n'])) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onmessage = function (m) { assert.equal('hello', m.data) @@ -701,7 +701,7 @@ describe('Reconnection', function () { }) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) var errored = false @@ -732,7 +732,7 @@ describe('Reconnection', function () { server.on('request', writeEvents(['data: hello\n\n'])) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onmessage = function (m) { assert.equal('hello', m.data) @@ -777,7 +777,7 @@ describe('Reconnection', function () { }) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onerror = function (e) { assert.equal(e.status, 204) @@ -814,7 +814,7 @@ describe('Reconnection', function () { server.on('request', writeEvents(['id: 10\ndata: Hello\n\n'])) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onmessage = function () { server.close(function (err) { @@ -854,7 +854,7 @@ describe('Reconnection', function () { server.on('request', writeEvents(['data: Hello\n\n'])) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onmessage = function () { server.close(function (err) { @@ -897,12 +897,64 @@ describe('Reconnection', function () { var es = new EventSource(server.url) assert.equal(EventSource.CONNECTING, es.readyState) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onerror = function (err) { errorOccurred = !!(errorOccurred || err) } }) }) + + it('should allow configurability of reconnect interval', function (done) { + createServer(function (err, server) { + if (err) return done(err) + + server.on('request', function (req, res) { + res.writeHead(500) + res.end() + }) + + var es = new EventSource(server.url) + es.setupReconnectInterval(5, 60, 1.5) + assert.equal(es.reconnectInterval, 5) + + var errored = false + es.onerror = function () { + if (errored) return + errored = true + server.close(function (err) { + if (err) return done(err) + assert.equal(es.reconnectInterval, 5 * 1.5) + done() + }) + } + }) + }) + + it('attempts to reconnect should increase the reconnect interval exponentially', function (done) { + createServer(function (err, server) { + if (err) return done(err) + var eventsSent = 0 + server.on('request', function (req, res) { + if (eventsSent === 0) { + res.writeHead(500) + res.end() + } + }) + + var es = new EventSource(server.url) + es.setupReconnectInterval(5, 25, 10) + var errored = false + es.onerror = function () { + if (errored) return + errored = true + server.close(function (err) { + if (err) return done(err) + assert.equal(es.reconnectInterval, 25) + done() + }) + } + }) + }) }) describe('readyState', function () { @@ -945,7 +997,7 @@ describe('readyState', function () { server.on('request', writeEvents([])) var es = new EventSource(server.url) - es.reconnectInterval = 0 + es.setupReconnectInterval(0) es.onopen = function (m) { server.close(function (err) { From cbef538d2f5a2d617c1820798375c872f79cc0c9 Mon Sep 17 00:00:00 2001 From: David Patty Date: Fri, 28 Jun 2019 12:57:46 -0600 Subject: [PATCH 3/3] Update history to reflect the changes in 1.0.8 and 1.1.0 --- HISTORY.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 7b3757ea..52d5f594 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,9 @@ +# [1.1.0](https://github.com/dpatty/eventsource/compare/v1.0.8...v1.1.0) +* Allow configuring of the reconnection interval using an exponential backoff ([#126](https://github.com/EventSource/eventsource/pull/126) David Patty) + +# [1.0.8](https://github.com/dpatty/eventsource/compare/v1.0.7...v1.0.8) +* Prevent simultaneous reconnection attempts on multiple errors ([#125](https://github.com/EventSource/eventsource/pull/125) David Patty) + # [1.0.7](https://github.com/EventSource/eventsource/compare/v1.0.6...v1.0.7) * Add dispatchEvent to EventSource ([#101](https://github.com/EventSource/eventsource/pull/101) Ali Afroozeh) diff --git a/package.json b/package.json index 28f59efa..6d1849fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "eventsource", - "version": "1.0.7", + "version": "1.1.0", "description": "W3C compliant EventSource client for Node.js and browser (polyfill)", "keywords": [ "eventsource",