forked from narfai/mewcluster
-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.js
186 lines (150 loc) · 6.31 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
'use strict';
/*Copyright 2016 François Cadeillan
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.*/
//Have to handle clustering (both Master and Worker)
var ro_cluster = require('cluster');
//Have to handle balancing (both Master and Worker)
var ro_balancer = require('./class/Balancer');
var rh_config_from_app = require('../app/cluster.json');
var rf_merge = require('merge');
const APP_PATH = "../app";
if(ro_cluster.isMaster) {
//This code should be executed only once on a Master process
//Check Docker ENV
if (typeof process.env.REDIS_PORT_6379_TCP_ADDR === 'undefined') {
throw new Error('Invalid redis env');
}
if (typeof process.env.REDIS_PORT_6379_TCP_PORT === 'undefined') {
throw new Error('Invalid redis env');
}
//TODO implement optionnal docker env for overriding application config
var h_default_config = {
port:8080,
engine:ro_balancer.ENGINE.SINGLE,
timeout:5000,
engine_conf:{
max:2,
respawn:true
}
};
//Proxy listen for TCP requests
ro_balancer.listen(rf_merge(h_default_config, rh_config_from_app.balancer));
} else {
//This code should be executed from one to many times on a Worker process
//Have to handle HTTP
const ro_http = require('http');
//Have to handle absctract sockets
const rf_socket_io = require('socket.io');
//Needed to get per-user session through redis
const rf_socket_io_redis = require('socket.io-redis');
//Have to parse some urls
const ro_url = require('url');
//promise!
const ro_q = require('q');
//Get app notifier factory
var rf_get_notifier = require('./class/Notifier');
var oc_notifier = rf_get_notifier('server', function(OUTPUTS){
return {
info:[OUTPUTS.FILE,OUTPUTS.STDOUT],
warning:[OUTPUTS.FILE,OUTPUTS.STDOUT],
debug:[OUTPUTS.FILE,OUTPUTS.STDOUT]
};
});
if(typeof rh_config_from_app.server !== 'undefined' && typeof rh_config_from_app.server.reload !== 'undefined' && rh_config_from_app.reload){
oc_notifier.info('#' + process.pid + 'unload ' + APP_PATH + '/app' + ' from require cache ');
//Reload application
delete require.cache[require.resolve(APP_PATH+'/app')];
}
//Our application
const App = require(APP_PATH+'/app');
//Need to handle http contents
const HttpContent = require('./class/HttpContent');
//Get App Emitter
var o_app_emitter = ro_balancer.get_app_emitter();
//Triggered instead killing process
process.on('uncaughtException', function (o_error) {
//Let Master handle Worker error (it may restart it if needed)
o_app_emitter.send_panic(o_error);
});
//TODO Grab SIGTERM for calling on_close on loaded Apps THEN exiting
//Create HTTP server
var o_internal_server = ro_http.createServer(function (o_req, o_res) {
var o_defer = ro_q.defer();
var s_path = ro_url.parse(o_req.url).pathname;
oc_notifier.debug('#' + process.pid + 'server path : '+ s_path +' request url : ' + o_req.url);
o_app_emitter.send_heartbeat();
var data = {
method: '',
value: ''
};
oc_notifier.debug('#' + process.pid + ' get ' + o_req.method + 'request');
switch (o_req.method){
case 'POST':
data.method = 'POST';
o_req.on('data', function (d) {
data.value += d;
});
o_req.on('end', function () {
o_defer.resolve(data);
});
break;
case 'GET':
default:
data.method = 'GET';
o_defer.resolve(data);
break;
}
o_defer.promise.then(function(h_request_data){
console.log(h_request_data);
//Static http request handling by application
//App have to send it bootload html + js code before having two-way communications
App.get_static(s_path, {pid: process.pid, requestData:h_request_data}, HttpContent) //Have to return a promise
.then(function (o_content) { //Split HttpContent object
return [o_content.render(APP_PATH + rh_config_from_app.httpcontent.render_path), o_content.get_code(), o_content.get_type()];
})
.spread(function (s_content, i_code, s_type) { //Send HttpContent to client
o_res.writeHead(i_code, {//TODO Allow headers to be dynamic by adding headers hash to HttpContent.headers
'Content-Type': s_type,
'Content-Length': s_content.length
});
o_res.write(s_content);
o_res.end();
}).catch(function(o_error) { //On any error or reject, send 500 error
o_res.writeHead(500);
o_res.write(o_error.toString());
o_res.end();
});
}).done();
}).listen(0, 'localhost');
//Bind abstract socket to http server
var o_io = rf_socket_io(o_internal_server);
//Bind redis socket per-user session
o_io.adapter(rf_socket_io_redis({
host: process.env.REDIS_PORT_6379_TCP_ADDR,
port: process.env.REDIS_PORT_6379_TCP_PORT,
upgrade: true,
}));
o_io.on('error', function(err){
o_app_emitter.send_panic(o_error);
});
try {
//Instanciate our application. Have to be before "bind_internal" allow app listeners to be triggered before server one's in event queue
new App({
emitter :o_app_emitter,
notifier_factory: rf_get_notifier,
io:o_io
});
} catch(o_error){
o_app_emitter.send_panic(o_error);
}
//Allow balancer to relay TCP connexions to our internal server
ro_balancer.bind_internal(o_internal_server, o_app_emitter);
}