-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
runtime.ts
150 lines (135 loc) · 5.82 KB
/
runtime.ts
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
import httpProxy from "http-proxy";
import http from "http";
import {checkAvailability} from "./checkAvailability.ts";
import {Database} from "ueberdb2";
let proxyServer: http.Server;
export const start = (settings: any) => {
console.debug(settings);
if (settings.dbType === 'dirty') console.error('DirtyDB is not recommend for production');
const backendIds = Object.keys(settings.backends);
// An object of our proxy instances
const proxies: { [key: string]: httpProxy<http.IncomingMessage, http.ServerResponse<http.IncomingMessage>> } = {};
// Making availableBackend globally available.
let availableBackends: {
up: Array<string>,
available: Array<string>,
};
(async () => {
await checkAvailability(
settings.backends,
settings.checkInterval,
settings.maxPadsPerInstance);
})();
// And now grab them every X duration
setInterval(async () => {
availableBackends = await checkAvailability(
settings.backends,
settings.checkInterval,
settings.maxPadsPerInstance);
}, settings.checkInterval);
// Creating our database connection
const db = new Database(settings.dbType, settings.dbSettings);
// Initiate the proxy routes to the backends
const initiateRoute = (backend: string, req: http.IncomingMessage, res: http.ServerResponse, socket: any, head: any) => {
if (res) {
// console.log('backend: ', backend);
if (proxies[backend]) {
proxies[backend].web(req, res, {}, (e) => {
console.error(e);
});
}
}
if (socket && head) {
if (proxies[backend]) {
proxies[backend].ws(req, socket, head, {}, (e) => {
console.error(e);
});
}
}
};
// Create dynamically assigned routes based on padIds and ensure that a route for
// unique padIds are re-used and stuck to a backend -- padId <> backend association.
const createRoute = (padId: string | null, req: any, res: any, socket: any, head: any) => {
// If the route isn't for a specific padID IE it's for a static file
// we can use any of the backends but now let's use the first :)
if (!padId) {
const newBackend = availableBackends.available[
Math.floor(Math.random() * availableBackends.available.length)];
return initiateRoute(newBackend, req, res, socket, head);
}
// pad specific backend required, do we have a backend already?
// @ts-ignore
db.get(`padId:${padId}`, (e, r) => {
if (r && r.backend) {
// console.log(`database hit: ${padId} <> ${r.backend}`);
if (!availableBackends) {
return console.log('Request made during startup.');
}
if (availableBackends.up.indexOf(r.backend) !== -1) {
initiateRoute(r.backend, req, res, socket, head);
} else {
// not available.. actually it might be up but not available..
console.log(`hit backend not available: ${padId} <> ${r.backend}`);
const newBackend = availableBackends.up[
Math.floor(Math.random() * availableBackends.up.length)];
// set and store a new backend
db.set(`padId:${padId}`, {
backend: newBackend,
});
console.log(`creating new association: ${padId} <> ${newBackend}`);
initiateRoute(newBackend, req, res, socket, head);
}
} else {
// if no backend is stored for this pad, create a new connection
console.log(availableBackends);
const newBackend = availableBackends.available[
Math.floor(Math.random() * availableBackends.available.length)];
db.set(`padId:${padId}`, {
backend: newBackend,
});
if (!availableBackends.available) console.log('no available backends!');
console.log(`database miss, initiating new association: ${padId} <> ${newBackend}`);
initiateRoute(newBackend, req, res, socket, head);
}
});
};
db.init();
// Create the backends.
for (const backendId of backendIds) {
/* eslint-disable-next-line new-cap */
proxies[backendId] = httpProxy.createProxyServer({
target: {
host: settings.backends[backendId].host,
port: settings.backends[backendId].port,
},
});
}
// Create the routes for web traffic to those backends.
proxyServer = http.createServer((req, res) => {
let padId: string | null = null;
if (req.url!.indexOf('/p/') !== -1) {
padId = req.url!.split('/p/')[1].split('?')[0].split('/')[0];
console.log(`initial request to /p/${padId}`);
}
if (padId === null) {
const searchParams = new URLSearchParams(req.url);
padId = searchParams.get('/socket.io/?padId');
}
createRoute(padId as string, req, res, null, null);
});
proxyServer.on('error', (e) => {
console.log('proxyserver error', e);
});
// Create a route for upgrade / websockets
proxyServer.on('upgrade', (req, socket, head) => {
const searchParams = new URLSearchParams(req.url);
const padId = searchParams.get('/socket.io/?padId');
createRoute(padId, req, null, socket, head);
});
console.log(`Proxy server listening on port ${settings.port}`);
proxyServer.listen(settings.port);
}
export const close = ()=> {
proxyServer.closeAllConnections();
proxyServer.close()
}