Repository navigation
Expand file tree
/
Copy pathhost.js
More file actions
173 lines (145 loc) · 5.56 KB
/
Copy pathhost.js
File metadata and controls
173 lines (145 loc) · 5.56 KB
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
var servers = {}
var routes = []
var host = {}
host.route = ({path, host, to}) => { routes.push({path, host, to}) }
host.server = (name, file='server.js') => {
var fork = require('child_process').fork
var port = 2000 + Math.floor(Math.random() * 7999) + ''
// Span a child process
var child = fork(
file,
{ // Options
env: {port: port},
cwd: name,
stdio: 'pipe',
execArgv: [...process.execArgv,
// This makes it kill itself when we die
'--require', __dirname + '/die-when-parent-dies.js']
}
)
var server = {process: child, output: '', port, name}
servers[port] = server
console.log('Spawned', name)//, 'to make', servers[port].stdout)
child.stdout.on('data', (x) => {
server.output += x
console.log((`> ${name}: ` + x).slice(0, -1))
})
child.stderr.on('data', (x) => {
server.output += x
console.log((`> ${name}-err: ` + x).slice(0, -1))
})
child.on('close', async (code) => {
console.log('process', name, 'exited with code', code)
delete servers[port]
// check if the process is really dead
// (because it seems like there are some ghost processes looming about sometimes)
if ((await get_running_process_set())[child.pid]) {
require('fs').appendFileSync('host-ghost-processes.log', `process = ${child.pid}\n`)
}
// Restart in 3 seconds
setTimeout( () => module.exports.server(name, file), 3000)
})
return server
}
host.listen = (port) => {
var http2 = require('http2')
var read = require('fs').readFileSync
var server = http2.createSecureServer({
key: read('private-key'),
cert: read('certificate'),
allowHTTP1: true,
settings: {
maxConcurrentStreams: 10000
}
})
var proxy = require('http2-proxy')
server.on('request', (req, res) => {
var host = req.headers.host || req.headers[':authority']
console.log('Host:', req.method, host, req.url)
for (var i=0; i<routes.length; i++) {
var route = routes[i]
//var new_req = {...req, path: '/foo'}
// console.log('old/new request is', req, new_req)
if (route_match(req, route)) {
// if (route.path) {
// console.log('Changing url for', route.path,
// 'from', req.url, 'to',
// '/' + req.url.substr(route.path.length))
// req.url = '/' + req.url.substr(route.path.length)
// }
console.log('Host: sending route to', route.to.name)
proxy.web(req, res, {
hostname: host,
port: route.to.port,
onReq: route.to.new_req,
proxyTimeout: 0 // Keep subscriptions open
}, on_web_error)
return
}
}
})
server.on('upgrade', (req, socket, head) => {
var host = req.headers.host || req.headers[':authority']
console.log('Host websocket:', req.method, host, req.url)
for (var i=0; i<routes.length; i++) {
var route = routes[i]
if (route_match(req, route)) {
console.log('forwarding websocket to', route.to.name)
proxy.ws(req, socket, head, {
hostname: host,
port: route.to.port,
onReq: route.to.new_req,
proxyTimeout: 0 // Keep subscriptions open
}, on_ws_error)
return
}
}
})
server.listen(port)
}
function route_match (req, route) {
var regexify = (str) => typeof str === 'string'
? new RegExp('^' + str.replace('*', '.*'))
: str
// console.log('Testing', route.path, route.name, route.host, req.headers.host,
// req.headers.host?.match(regexify(route.host)))
// If host is specified, then make sure it matches
if (route.host && req.headers.host
&& !req.headers.host.match(regexify(route.host)))
return false
// Now if path is specified, make sure it matches
if (route.path
&& !req.url.match(regexify(route.path)))
return false
// Otherwise, we're good.
return true
}
// When proxying a request fails, tell the client
function on_web_error (err, req, res) {
if (!err) return
console.error('proxy error', err)
// If we haven't responded yet, we can say what went wrong
if (!res.headersSent)
res.writeHead(err.statusCode || 502).end()
// Otherwise the client already has its status and headers, so it's too
// late to send an error. Hang up instead, so the client sees the stream
// die, and reconnects.
else if (!res.destroyed)
res.destroy()
}
// When proxying a websocket fails, hang up on the client
function on_ws_error (err, req, socket) {
if (!err) return
console.error('proxy error', err)
socket.destroy()
}
async function get_running_process_set() {
let ps = require('child_process').spawn('ps', ['axo', 'pid'], {stdio: 'pipe'})
let ps_stdout = []
ps.stdout.on('data', x => ps_stdout.push(x))
ps.stderr.on('data', x => ps_stdout.push(x))
await new Promise(done => ps.stdout.on('end', done))
let x = Buffer.concat(ps_stdout).toString()
return Object.fromEntries(x.match(/\d+/g).map(x => [x, true]))
}
module.exports = host