Skip to content

Commit d63b2cb

Browse files
authored
Merge pull request #226 from kuzzleio/network-error-handling
Network error handling
2 parents aa473dc + 30064fe commit d63b2cb

File tree

8 files changed

+393
-119
lines changed

8 files changed

+393
-119
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"bluebird": "3.4.7",
3131
"eslint-loader": "^1.6.1",
3232
"uuid": "3.0.1",
33-
"ws": "^2.0.3"
33+
"ws": "3.0.0"
3434
},
3535
"peerDependencies": {
3636
"bufferutil": "^2.0.1",
@@ -54,4 +54,4 @@
5454
"engines": {
5555
"node": ">= 6.9.1"
5656
}
57-
}
57+
}

src/Kuzzle.js

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ var
2222
* @param host - Server name or IP Address to the Kuzzle instance
2323
* @param [options] - Connection options
2424
* @param {responseCallback} [cb] - Handles connection response
25-
* @constructor
2625
*/
2726
function Kuzzle (host, options, cb) {
2827
var self = this;
@@ -80,11 +79,15 @@ function Kuzzle (host, options, cb) {
8079
subscriptions: {
8180
/*
8281
Contains the centralized subscription list in the following format:
83-
pending: <number of pending subscriptions>
82+
pending: {
83+
subscriptionUid_1: kuzzleRoomInstance_1,
84+
subscriptionUid_2: kuzzleRoomInstance_2,
85+
subscriptionUid_...: kuzzleRoomInstance_...
86+
},
8487
'roomId': {
85-
kuzzleRoomID_1: kuzzleRoomInstance_1,
86-
kuzzleRoomID_2: kuzzleRoomInstance_2,
87-
kuzzleRoomID_...: kuzzleRoomInstance_...
88+
subscriptionUid_1: kuzzleRoomInstance_1,
89+
subscriptionUid_2: kuzzleRoomInstance_2,
90+
subscriptionUid_...: kuzzleRoomInstance_...
8891
}
8992
9093
This was made to allow multiple subscriptions on the same set of filters, something that Kuzzle does not permit.
@@ -360,12 +363,14 @@ Kuzzle.prototype.connect = function () {
360363
});
361364

362365
self.network.onConnectError(function (error) {
363-
var connectionError = new Error('Unable to connect to kuzzle proxy server at "' + self.host + '"');
366+
var connectionError = new Error('Unable to connect to kuzzle proxy server at "' + self.host + ':' + self.port + '"');
364367

365368
connectionError.internal = error;
366369
self.state = 'error';
367370
self.emitEvent('networkError', connectionError);
368371

372+
disableAllSubscriptions.call(self);
373+
369374
if (self.connectCB) {
370375
self.connectCB(connectionError);
371376
}
@@ -1541,4 +1546,15 @@ function discardRequest(object, cb) {
15411546
}
15421547
}
15431548

1549+
function disableAllSubscriptions() {
1550+
var self = this;
1551+
1552+
Object.keys(self.subscriptions).forEach(function (roomId) {
1553+
Object.keys(self.subscriptions[roomId]).forEach(function (subscriptionId) {
1554+
var subscription = self.subscriptions[roomId][subscriptionId];
1555+
subscription.subscribing = false;
1556+
});
1557+
});
1558+
}
1559+
15441560
module.exports = Kuzzle;

src/Room.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -157,13 +157,13 @@ Room.prototype.count = function (cb) {
157157
*/
158158
Room.prototype.renew = function (filters, notificationCB, cb) {
159159
var
160+
self = this,
160161
now = Date.now(),
161162
subscribeQuery = {
162-
scope: this.scope,
163-
state: this.state,
164-
users: this.users
165-
},
166-
self = this;
163+
scope: self.scope,
164+
state: self.state,
165+
users: self.users
166+
};
167167

168168
if (typeof filters === 'function') {
169169
cb = notificationCB;
@@ -212,7 +212,7 @@ Room.prototype.renew = function (filters, notificationCB, cb) {
212212
self.kuzzle.subscriptions.pending[self.id] = self;
213213

214214
subscribeQuery.body = self.filters;
215-
subscribeQuery = self.kuzzle.addHeaders(subscribeQuery, this.headers);
215+
subscribeQuery = self.kuzzle.addHeaders(subscribeQuery, self.headers);
216216

217217
self.kuzzle.query(self.collection.buildQueryArgs('realtime', 'subscribe'), subscribeQuery, {volatile: self.volatile}, function (error, response) {
218218
delete self.kuzzle.subscriptions.pending[self.id];

src/networkWrapper/wrappers/socketio.js

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,15 @@ function SocketIO(host, port, ssl) {
33
this.port = port;
44
this.ssl = ssl;
55
this.socket = null;
6+
this.wasConnected = false;
7+
this.forceDisconnect = false;
8+
this.handlers = {
9+
connect: [],
10+
reconnect: [],
11+
connectError: [],
12+
disconnect: []
13+
};
14+
this.retrying = false;
615

716
/**
817
* Creates a new socket from the provided arguments
@@ -12,11 +21,50 @@ function SocketIO(host, port, ssl) {
1221
* @param {int} reconnectionDelay
1322
*/
1423
this.connect = function (autoReconnect, reconnectionDelay) {
24+
var self = this;
25+
1526
this.socket = window.io((this.ssl ? 'https://' : 'http://') + this.host + ':' + this.port, {
1627
reconnection: autoReconnect,
1728
reconnectionDelay: reconnectionDelay,
1829
forceNew: true
1930
});
31+
32+
this.socket.on('connect', function() {
33+
if (self.wasConnected) {
34+
self.handlers.reconnect.forEach(function(handler) {
35+
handler();
36+
});
37+
}
38+
else {
39+
self.handlers.connect.forEach(function(handler) {
40+
handler();
41+
});
42+
}
43+
44+
self.wasConnected = true;
45+
});
46+
47+
this.socket.on('connect_error', function(error) {
48+
onClientNetworkError(self, autoReconnect, reconnectionDelay, error);
49+
});
50+
51+
this.socket.on('disconnect', function() {
52+
var error;
53+
54+
if (self.forceDisconnect) {
55+
self.handlers.disconnect.forEach(function(handler) {
56+
handler();
57+
});
58+
}
59+
else {
60+
error = new Error('An error occurred, this may due that kuzzle was not ready yet');
61+
error.status = 500;
62+
63+
onClientNetworkError(self, autoReconnect, reconnectionDelay, error);
64+
}
65+
66+
self.forceDisconnect = false;
67+
});
2068
};
2169

2270
/**
@@ -25,31 +73,39 @@ function SocketIO(host, port, ssl) {
2573
* @param {function} callback
2674
*/
2775
this.onConnect = function (callback) {
28-
this.socket.on('connect', callback);
76+
if (this.handlers.connect.indexOf(callback) === -1) {
77+
this.handlers.connect.push(callback);
78+
}
2979
};
3080

3181
/**
3282
* Fires the provided callback whenever a connection error is received
3383
* @param {function} callback
3484
*/
3585
this.onConnectError = function (callback) {
36-
this.socket.on('connect_error', callback);
86+
if (this.handlers.connectError.indexOf(callback) === -1) {
87+
this.handlers.connectError.push(callback);
88+
}
3789
};
3890

3991
/**
4092
* Fires the provided callback whenever a disconnection occurred
4193
* @param {function} callback
4294
*/
4395
this.onDisconnect = function (callback) {
44-
this.socket.on('disconnect', callback);
96+
if (this.handlers.disconnect.indexOf(callback) === -1) {
97+
this.handlers.disconnect.push(callback);
98+
}
4599
};
46100

47101
/**
48102
* Fires the provided callback whenever a connection has been reestablished
49103
* @param {function} callback
50104
*/
51105
this.onReconnect = function (callback) {
52-
this.socket.on('reconnect', callback);
106+
if (this.handlers.reconnect.indexOf(callback) === -1) {
107+
this.handlers.reconnect.push(callback);
108+
}
53109
};
54110

55111
/**
@@ -97,9 +153,34 @@ function SocketIO(host, port, ssl) {
97153
* Closes the connection
98154
*/
99155
this.close = function () {
156+
this.forceDisconnect = true;
157+
100158
this.socket.close();
101159
this.socket = null;
102160
};
103161
}
104162

163+
/**
164+
* Called when the connection closes with an error state
165+
*
166+
* @param {SocketIO}
167+
* @param {boolean} autoReconnect
168+
* @param {number} reconnectionDelay
169+
* @param {Error} error
170+
*/
171+
function onClientNetworkError(socketio, autoReconnect, reconnectionDelay, error) {
172+
if (autoReconnect && !socketio.retrying && !socketio.stopRetryingToConnect) {
173+
socketio.retrying = true;
174+
setTimeout(function () {
175+
socketio.retrying = false;
176+
socketio.connect(autoReconnect, reconnectionDelay);
177+
}, reconnectionDelay);
178+
}
179+
180+
socketio.handlers.connectError.forEach(function(handler) {
181+
handler(error);
182+
});
183+
}
184+
185+
105186
module.exports = SocketIO;

src/networkWrapper/wrappers/websocket.js

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,39 @@ function WSNode(host, port, ssl) {
4646
self.stopRetryingToConnect = false;
4747
};
4848

49-
this.client.onclose = function (code, message) {
50-
if (code === 1000) {
49+
this.client.onclose = function (closeEvent, message) {
50+
var error;
51+
var status;
52+
var reason = message;
53+
54+
if (typeof closeEvent === 'number') {
55+
status = closeEvent;
56+
}
57+
else {
58+
status = closeEvent.code;
59+
60+
if (closeEvent.reason) {
61+
reason = closeEvent.reason;
62+
}
63+
}
64+
65+
if (status === 1000) {
5166
self.emitEvent('disconnect');
5267
}
5368
else {
54-
onClientError.call(self, autoReconnect, reconnectionDelay, message);
69+
error = new Error(reason);
70+
error.status = status;
71+
72+
onClientNetworkError(self, autoReconnect, reconnectionDelay, error);
5573
}
5674
};
5775

5876
this.client.onerror = function (error) {
59-
onClientError.call(self, autoReconnect, reconnectionDelay, error);
77+
if (!(error instanceof Error)) {
78+
error = new Error(error);
79+
}
80+
81+
onClientNetworkError(self, autoReconnect, reconnectionDelay, error);
6082
};
6183

6284
this.client.onmessage = function (payload) {
@@ -133,22 +155,21 @@ WSNode.prototype.constructor = WSNode;
133155
/**
134156
* Called when the connection closes with an error state
135157
*
158+
* @param {WSNode}
136159
* @param {boolean} autoReconnect
137160
* @param {number} reconnectionDelay
138-
* @param {string|Object} message
161+
* @param {Error} error
139162
*/
140-
function onClientError(autoReconnect, reconnectionDelay, message) {
141-
var self = this;
142-
143-
if (autoReconnect && !self.retrying && !self.stopRetryingToConnect) {
144-
self.retrying = true;
163+
function onClientNetworkError(ws, autoReconnect, reconnectionDelay, error) {
164+
if (autoReconnect && !ws.retrying && !ws.stopRetryingToConnect) {
165+
ws.retrying = true;
145166
setTimeout(function () {
146-
self.retrying = false;
147-
self.connect(autoReconnect, reconnectionDelay);
167+
ws.retrying = false;
168+
ws.connect(autoReconnect, reconnectionDelay);
148169
}, reconnectionDelay);
149170
}
150171

151-
self.emitEvent('networkError', new Error(message));
172+
ws.emitEvent('networkError', error);
152173
}
153174

154175
module.exports = WSNode;

test/Room/methods.test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,18 @@ describe('Room methods', function () {
142142
should(room.queue).match([{action: 'renew', args: [{}, cb, cb]}]);
143143
});
144144

145+
it('should reset subscribing when network error occurs', function () {
146+
var cb = sinon.stub();
147+
room.renew({}, cb, cb);
148+
should(room.subscribing).be.true();
149+
150+
kuzzle.connect();
151+
kuzzle.network.emit('networkError', new Error('foo'));
152+
153+
should(room.subscribing).be.false();
154+
should(kuzzle.query).be.called();
155+
});
156+
145157
it('should register itself in the global subscription list', function () {
146158
room.renew({}, sinon.stub());
147159
kuzzle.query.yield(null, result);

test/kuzzle/connect.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe('Kuzzle connect', function () {
8181
it('should call the provided callback on a connection error', function (done) {
8282
var kuzzle = new Kuzzle('nowhere', {connect: 'manual'}, function (err, res) {
8383
should(err).be.instanceOf(Error);
84-
should(err.message).be.exactly('Unable to connect to kuzzle proxy server at "nowhere"');
84+
should(err.message).be.exactly('Unable to connect to kuzzle proxy server at "nowhere:7512"');
8585
should(err.internal.message).be.exactly('Mock Error');
8686
should(res).be.undefined();
8787
should(kuzzle.state).be.exactly('error');

0 commit comments

Comments
 (0)