Skip to content

Commit e32ce52

Browse files
author
epriestley
committedApr 14, 2016
Begin generalizing Aphlict server to prepare for clustering/sensible config file
Summary: Ref T10697. Currently, `aphlict` takes a ton of command line flags to configure exactly one admin server and exactly one client server. I want to replace this with a config file. Additionally, I plan to support: - arbitrary numbers of listening client ports; - arbitrary numbers of listening admin ports; - SSL on any port. For now, just transform the arguments to look like they're a config file. In the future, I'll load from a config file instead. This greater generality will allow you to do stuff like run separate HTTP and HTTPS admin ports if you really want. I don't think there's a ton of use for this, but it tends to make the code cleaner anyway and there may be some weird cross-datacneter cases for it. Certainly, we undershot with the initial design and lots of users want to terminate SSL in nginx and run only HTTP on this server. (Some sort-of-plausible use cases are running separate HTTP and HTTPS client servers, if your Phabricator install supports both, or running multiple HTTPS servers with different certificates if you have a bizarre VPN.) Test Plan: Started Aphlict, connected to it, sent myself test notifications, viewed status page, reviewed logfile. Reviewers: chad Reviewed By: chad Maniphest Tasks: T10697 Differential Revision: https://secure.phabricator.com/D15700
1 parent a2588d6 commit e32ce52

File tree

3 files changed

+167
-67
lines changed

3 files changed

+167
-67
lines changed
 

‎support/aphlict/server/aphlict_server.js

+62-19
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,29 @@ var ssl_config = {
9999
if (ssl_config.enabled) {
100100
ssl_config.key = fs.readFileSync(config['ssl-key']);
101101
ssl_config.cert = fs.readFileSync(config['ssl-cert']);
102+
} else {
103+
ssl_config.key = null;
104+
ssl_config.cert = null;
102105
}
103106

107+
var servers = [];
108+
109+
servers.push({
110+
type: 'client',
111+
port: config['client-port'],
112+
listen: config['client-host'],
113+
'ssl.key': ssl_config.key,
114+
'ssl.certificate': ssl_config.cert
115+
});
116+
117+
servers.push({
118+
type: 'admin',
119+
port: config['admin-port'],
120+
listen: config['admin-host'],
121+
'ssl.key': null,
122+
'ssl.cert': null
123+
});
124+
104125
// If we're just doing a configuration test, exit here before starting any
105126
// servers.
106127
if (config.test) {
@@ -109,27 +130,49 @@ if (config.test) {
109130
return;
110131
}
111132

112-
var server;
113-
if (ssl_config.enabled) {
114-
server = https.createServer({
115-
key: ssl_config.key,
116-
cert: ssl_config.cert
117-
}, function(req, res) {
118-
res.writeHead(501);
119-
res.end('HTTP/501 Use Websockets\n');
120-
});
121-
} else {
122-
server = http.createServer(function() {});
123-
}
133+
var aphlict_servers = [];
134+
var aphlict_clients = [];
135+
var aphlict_admins = [];
136+
137+
var ii;
138+
for (ii = 0; ii < servers.length; ii++) {
139+
var server = servers[ii];
140+
var is_client = (server.type == 'client');
141+
142+
var http_server;
143+
if (server['ssl.key']) {
144+
var https_config = {
145+
key: server['ssl.key'],
146+
cert: server['ssl.cert']
147+
};
148+
149+
http_server = https.createServer(https_config);
150+
} else {
151+
http_server = http.createServer();
152+
}
153+
154+
var aphlict_server;
155+
if (is_client) {
156+
aphlict_server = new JX.AphlictClientServer(http_server);
157+
} else {
158+
aphlict_server = new JX.AphlictAdminServer(http_server);
159+
}
124160

125-
var client_server = new JX.AphlictClientServer(server);
126-
var admin_server = new JX.AphlictAdminServer();
161+
aphlict_server.setLogger(debug);
162+
aphlict_server.listen(server.port, server.listen);
127163

128-
client_server.setLogger(debug);
129-
admin_server.setLogger(debug);
130-
admin_server.setClientServer(client_server);
164+
aphlict_servers.push(aphlict_server);
131165

132-
client_server.listen(config['client-port'], config['client-host']);
133-
admin_server.listen(config['admin-port'], config['admin-host']);
166+
if (is_client) {
167+
aphlict_clients.push(aphlict_server);
168+
} else {
169+
aphlict_admins.push(aphlict_server);
170+
}
171+
}
172+
173+
for (ii = 0; ii < aphlict_admins.length; ii++) {
174+
var admin_server = aphlict_admins[ii];
175+
admin_server.setClientServers(aphlict_clients);
176+
}
134177

135178
debug.log('Started Server (PID %d)', process.pid);

‎support/aphlict/server/lib/AphlictAdminServer.js

+79-42
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,19 @@ var url = require('url');
99

1010
JX.install('AphlictAdminServer', {
1111

12-
construct: function() {
13-
this.setLogger(new JX.AphlictLog());
14-
12+
construct: function(server) {
1513
this._startTime = new Date().getTime();
1614
this._messagesIn = 0;
1715
this._messagesOut = 0;
1816

19-
var handler = this._handler.bind(this);
20-
this._server = http.createServer(handler);
17+
server.on('request', JX.bind(this, this._onrequest));
18+
this._server = server;
19+
this._clientServers = [];
20+
},
21+
22+
properties: {
23+
clientServers: null,
24+
logger: null,
2125
},
2226

2327
members: {
@@ -26,15 +30,32 @@ JX.install('AphlictAdminServer', {
2630
_server: null,
2731
_startTime: null,
2832

29-
getListenerList: function(instance) {
30-
return this.getClientServer().getListenerList(instance);
33+
getListenerLists: function(instance) {
34+
var clients = this.getClientServers();
35+
36+
var lists = [];
37+
for (var ii = 0; ii < clients.length; ii++) {
38+
lists.push(clients[ii].getListenerList(instance));
39+
}
40+
return lists;
41+
},
42+
43+
log: function() {
44+
var logger = this.getLogger();
45+
if (!logger) {
46+
return;
47+
}
48+
49+
logger.log.apply(logger, arguments);
50+
51+
return this;
3152
},
3253

3354
listen: function() {
3455
return this._server.listen.apply(this._server, arguments);
3556
},
3657

37-
_handler: function(request, response) {
58+
_onrequest: function(request, response) {
3859
var self = this;
3960
var u = url.parse(request.url, true);
4061
var instance = u.query.instance || '/';
@@ -52,7 +73,7 @@ JX.install('AphlictAdminServer', {
5273
try {
5374
var msg = JSON.parse(body);
5475

55-
self.getLogger().log(
76+
self.log(
5677
'Received notification (' + instance + '): ' +
5778
JSON.stringify(msg));
5879
++self._messagesIn;
@@ -61,14 +82,14 @@ JX.install('AphlictAdminServer', {
6182
self._transmit(instance, msg);
6283
response.writeHead(200, {'Content-Type': 'text/plain'});
6384
} catch (err) {
64-
self.getLogger().log(
85+
self.log(
6586
'<%s> Internal Server Error! %s',
6687
request.socket.remoteAddress,
6788
err);
6889
response.writeHead(500, 'Internal Server Error');
6990
}
7091
} catch (err) {
71-
self.getLogger().log(
92+
self.log(
7293
'<%s> Bad Request! %s',
7394
request.socket.remoteAddress,
7495
err);
@@ -82,61 +103,77 @@ JX.install('AphlictAdminServer', {
82103
response.end();
83104
}
84105
} else if (u.pathname == '/status/') {
85-
var status = {
86-
'instance': instance,
87-
'uptime': (new Date().getTime() - this._startTime),
88-
'clients.active': this.getListenerList(instance)
89-
.getActiveListenerCount(),
90-
'clients.total': this.getListenerList(instance)
91-
.getTotalListenerCount(),
92-
'messages.in': this._messagesIn,
93-
'messages.out': this._messagesOut,
94-
'version': 7
95-
};
96-
97-
response.writeHead(200, {'Content-Type': 'application/json'});
98-
response.write(JSON.stringify(status));
99-
response.end();
106+
this._handleStatusRequest(request, response, instance);
100107
} else {
101108
response.writeHead(404, 'Not Found');
102109
response.end();
103110
}
104111
},
105112

113+
_handleStatusRequest: function(request, response, instance) {
114+
var active_count = 0;
115+
var total_count = 0;
116+
117+
var lists = this.getListenerLists(instance);
118+
for (var ii = 0; ii < lists.length; ii++) {
119+
var list = lists[ii];
120+
active_count += list.getActiveListenerCount();
121+
total_count += list.getTotalListenerCount();
122+
}
123+
124+
var server_status = {
125+
'instance': instance,
126+
'uptime': (new Date().getTime() - this._startTime),
127+
'clients.active': active_count,
128+
'clients.total': total_count,
129+
'messages.in': this._messagesIn,
130+
'messages.out': this._messagesOut,
131+
'version': 7
132+
};
133+
134+
response.writeHead(200, {'Content-Type': 'application/json'});
135+
response.write(JSON.stringify(server_status));
136+
response.end();
137+
},
138+
106139
/**
107140
* Transmits a message to all subscribed listeners.
108141
*/
109142
_transmit: function(instance, message) {
110-
var listeners = this.getListenerList(instance)
111-
.getListeners()
112-
.filter(function(client) {
113-
return client.isSubscribedToAny(message.subscribers);
114-
});
143+
var lists = this.getListenerLists(instance);
144+
145+
for (var ii = 0; ii < lists.length; ii++) {
146+
var list = lists[ii];
147+
var listeners = list.getListeners();
148+
this._transmitToListeners(list, listeners, message);
149+
}
150+
},
151+
152+
_transmitToListeners: function(list, listeners, message) {
153+
for (var ii = 0; ii < listeners.length; ii++) {
154+
var listener = listeners[ii];
115155

116-
for (var i = 0; i < listeners.length; i++) {
117-
var listener = listeners[i];
156+
if (!listener.isSubscribedToAny(message.subscribers)) {
157+
continue;
158+
}
118159

119160
try {
120161
listener.writeMessage(message);
121162

122163
++this._messagesOut;
123-
this.getLogger().log(
164+
this.log(
124165
'<%s> Wrote Message',
125166
listener.getDescription());
126167
} catch (error) {
127-
this.getListenerList(instance).removeListener(listener);
128-
this.getLogger().log(
168+
list.removeListener(listener);
169+
170+
this.log(
129171
'<%s> Write Error: %s',
130172
listener.getDescription(),
131173
error);
132174
}
133175
}
134-
},
135-
},
136-
137-
properties: {
138-
clientServer: null,
139-
logger: null,
176+
}
140177
}
141178

142179
});

‎support/aphlict/server/lib/AphlictClientServer.js

+26-6
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,16 @@ var WebSocket = require('ws');
1212
JX.install('AphlictClientServer', {
1313

1414
construct: function(server) {
15-
this.setLogger(new JX.AphlictLog());
15+
server.on('request', JX.bind(this, this._onrequest));
16+
1617
this._server = server;
1718
this._lists = {};
1819
},
1920

21+
properties: {
22+
logger: null,
23+
},
24+
2025
members: {
2126
_server: null,
2227
_lists: null,
@@ -28,6 +33,25 @@ JX.install('AphlictClientServer', {
2833
return this._lists[path];
2934
},
3035

36+
log: function() {
37+
var logger = this.getLogger();
38+
if (!logger) {
39+
return;
40+
}
41+
42+
logger.log.apply(logger, arguments);
43+
44+
return this;
45+
},
46+
47+
_onrequest: function(request, response) {
48+
// The websocket code upgrades connections before they get here, so
49+
// this only handles normal HTTP connections. We just fail them with
50+
// a 501 response.
51+
response.writeHead(501);
52+
response.end('HTTP/501 Use Websockets\n');
53+
},
54+
3155
listen: function() {
3256
var self = this;
3357
var server = this._server.listen.apply(this._server, arguments);
@@ -38,7 +62,7 @@ JX.install('AphlictClientServer', {
3862
var listener = self.getListenerList(path).addListener(ws);
3963

4064
function log() {
41-
self.getLogger().log(
65+
self.log(
4266
util.format('<%s>', listener.getDescription()) +
4367
' ' +
4468
util.format.apply(null, arguments));
@@ -97,10 +121,6 @@ JX.install('AphlictClientServer', {
97121

98122
},
99123

100-
},
101-
102-
properties: {
103-
logger: null,
104124
}
105125

106126
});

0 commit comments

Comments
 (0)
Failed to load comments.