X-Git-Url: https://git.kengrimes.com/?p=henge%2Fkiak.git;a=blobdiff_plain;f=router.js;fp=router.js;h=411c4886c3323ff3f28e9768ec3bbe79479905c8;hp=d8e9a8b03c1117004b50141147b65b5aef03c12b;hb=5ae8bb9adc37111641414da912600ea97d834874;hpb=6e147610b0a69225f15e09dcbdac29adb4ed66fc diff --git a/router.js b/router.js index d8e9a8b..411c488 100644 --- a/router.js +++ b/router.js @@ -15,6 +15,11 @@ exports = { */ validRoutes: /[a-zA-Z][a-zA-Z0-9\-_]*/, + /** A map of routes + * @prop {Object.Map} routes - all the routes! + */ + routes: {}, + /** Parameters set on bootup (startHttpServer) * @prop {string[2]} skelPage - html document split in twain for JS injection * @prop {string} clientJS - jerverscripps to inject in skelPage for clients @@ -28,7 +33,7 @@ exports = { httpdRoot: undefined, bindJail: undefined, - /** @func + /** @func * @summary Start main HTTP server * @desc starts up an HTTP or HTTPS server used for routing * @arg {Object} conf - object containing configuration properties @@ -46,7 +51,8 @@ exports = { if ('httpd' in this) throw new Error('httpd already running') if (conf.tls == undefined) - this.httpd = require('http').createServer(this.httpdListener) + this.httpd = require('http').createServer((req, res) => + this.httpdListener(req, res)) else if (!('keyFile' in conf.tls) || !('certFile' in conf.tls)) throw new Error('HTTPS requires a valid key and cert') else @@ -58,14 +64,16 @@ exports = { } }) this.httpd = - require('https').createServer(this.httpsOpts, this.httpdListener) + require('https').createServer(this.httpsOpts, (request,response) => + this.httpdListener(request,response)) + .listen(conf.port) }) - this.httpd.listen(conf.port) - this.httpdRoot = - conf.httpdRoot ? require('path').normalize(conf.httpdRoot) : undefined - while (this.httpdRoot[this.httpdRoot.length - 1] == require('path').sep) - this.httpdRoot = this.httpdRoot.slice(0,-1) - this.syncReads(conf.skelFile, conf.clientJS, conf.hostJS) + if (conf.httpdRoot) { + this.httpdRoot = require('path').normalize(conf.httpdRoot) + while (this.httpdRoot[this.httpdRoot.length - 1] == require('path').sep) + this.httpdRoot = this.httpdRoot.slice(0,-1) + } + this.syncReads([conf.skelFile, conf.clientJS, conf.hostJS]) .then((results) => { this.skelPage = results[conf.skelFile].split('') this.clientJS = results[conf.clientJS] @@ -74,8 +82,8 @@ exports = { .catch((err) => { console.log(err) }) - console.log(`HTTP${(conf.tls == undefined) ? 'S' : ''} ` + - `Server Started on port ${conf.port}${this.httpdRoot ? + console.log(`HTTP${(conf.tls == undefined) ? '' : 'S'} ` + + `Server Started on port ${conf.port}${this.httpdRoot ? `, serving files from ${this.httpdRoot}`:''}`) }, @@ -97,7 +105,7 @@ exports = { && this.bindJail != path) throw new Error(`${routeName}:${path} jailed to ${this.bindJail}`) if (require('fs').existsSync(path)) { - this.route[routeName] = { + this.routes[routeName] = { bind: { path: path, dir: require('fs').lstatSync(path).isDirectory() @@ -108,7 +116,7 @@ exports = { throw new Error(`${path} not found, ${routeName} not bound`) }, - /** @func + /** @func * @summary Router * @desc listens for http client requests and services routes/files * @arg {http.ClientRequest} request @@ -118,7 +126,8 @@ exports = { dlog(`Received request ${request.method} ${request.url}`) let htArgv = request.url.slice(1).split('?') const routeName = htArgv[0].split('/')[0] - const route = this.routes[routeName] + let route = this.routes[routeName] + console.log(`route is ${route}`) /* If the route exists, check if we are a returning host or a new client */ if (route) { if (route.bind) { @@ -128,16 +137,27 @@ exports = { this.serveBind(response, route.bind, htArgv) } //TODO: auth better than this (ip spoofing is easy) - else if (route.host == (request.headers['x-forwarded-for'] || + // but this will require a more involved host-creation process + // that isn't just "give you a route if it's available" on visit + /* else if (route.origin == (request.headers['x-forwarded-for'] || request.connection.remoteAddress)) this.serveHost(response, route, htArgv) - else + else */ + else { this.serveClient(request, response, route) + } } /* If it's a valid routename that doesn't exist, make this client a host */ else if (this.validRoutes.test(routeName)) { - route = this.createRoute(routeName, this.httpsOpts) - this.serveHost(response, route, htArgv) + this.routes[routeName] = true + require('get-port')() + .then((port) => { + this.createHost(routeName, htArgv, port, request, response) + }) + .catch((err) => { + delete this.routes[routeName] + console.log(err) + }) } /* Try servicing files if we have a root directory for it */ else if (this.httpdRoot) { @@ -194,7 +214,7 @@ exports = { this.serveFile(response, bind.path) }, - /** @func + /** @func * @summary Serve a route to an http client * @desc routes may be bound to the filesystem, or to an outgoing host * @arg {http.ClientRequest} request - request from the client @@ -217,9 +237,9 @@ exports = { case 'client-sdp-offer': let data = '' if (pubKey) { + let data = request.headers['x-strapp-offer'] route.pendingResponses.addResponse(pubKey, response) - request.on('data', (chunk) => data += chunk) - request.on('end', () => route.socket.send(`${pubKey} ${type} ${data}`)) + route.socket.send(`${pubKey} ${type} ${data}`) } else { response.writeHead(401) @@ -232,66 +252,64 @@ exports = { } }, - /** @func - * @summary Serve a route to an authorized http host - * @desc services host application to the client, establishing a socket - * @arg {http.ServerResponse} response - response object to use - * @arg {Object} route - the route that belongs to this host - * @arg {string[]} argv - vector of arguments sent to the host - */ - serveHost: function (response, route, argv) { - response.writeHead(200, { 'Content-Type': 'text/html' }) - response.write(`${this.skelPage[0]}` + - `\tconst _strapp_port = ${route.port}\n` + - `\tconst _strapp_protocol = ` + - `${this.httpsOpts ? 'wss' : 'ws'}'\n` + - `${this.hostJS}\n${this.skelPage[1]}`) - response.end() - }, - - /** @func - * @summary Create a new route + /** @func + * @summary Create a new route for a host * @desc makes a new route for the given route name * @arg {string} routeName - name of the new route - * @arg {string} host - Origin address from the request that made this + * @arg {string[]} argv - Origin address from the request that made this * route (for security verification on the socket) - * @arg {Object} [httpsOpts] - key and cert for tls - * @returns {Object} a route object containing host, socket, and servers + * @arg {number|string} port - the port to listen on for websocket + * @arg {http.ClientRequest} request - host's request + * @arg {http.ServerResponse} response - responder */ - createRoute: function (routeName, host, httpsOpts) { - dlog(`Creating ${httpsOpts ? 'TLS ' : ''}route ${routeName} from ${host}`) - if (routeName in this.routes) - throw new Error(`route ${routeName} already exists`) - const httpd = httpsOpts - ? require('https').createServer(httpsOpts) + createHost: function (routeName, argv, port, request, response) { + const origin = (request.headers['x-forwarded-for'] || + request.connection.remoteAddress) + dlog(`New ${this.httpsOpts?'TLS ':''}route ${routeName}:${port}=>${origin}`) + const httpd = this.httpsOpts + ? require('https').createServer(this.httpsOpts) : require('http').createServer() const route = { pendingResponses: new Map([]), - host: host, + origin: origin, httpd: httpd, name: routeName, - port: undefined, + port: port, wsd: undefined, socket: undefined } - require('get-port')().then((port) => { - route.port = port - route.httpd.listen(port) - route.wsd = new require('ws').Server({ - server:route.httpd, - verifyClient: (info) => - info.origin == host && (info.secure || !httpsOpts) + route.httpd.listen(port) + route.wsd = new (require('ws').Server)({ server: httpd }) + .on('connection', (socket) => { + route.socket = socket + socket.on('message', (msg) => + this.hostMessage(msg,route)) }) - route.wsd.on('connection', (socket) => - socket.on('message', (msg) => - this.hostMessage(msg,route))) - }) - route.pendingResponses.addResponse = function (key, response) { + route.pendingResponses.addResponse = function (key, response_p) { let responses = this.get(key) || [] - this.set(key, responses.push(response)) + responses.push(response_p) + this.set(key, responses) } this.routes[routeName] = route - return route + this.serveHost(response, route, argv) + }, + + /** @func + * @summary Serve a route to an authorized http host + * @desc services host application to the client, establishing a socket + * @arg {http.ServerResponse} response - response object to use + * @arg {Object} route - the route that belongs to this host + * @arg {string[]} argv - vector of arguments sent to the host + */ + serveHost: function (response, route, argv) { + dlog(`Serving host ${route.origin}`) + response.writeHead(200, { 'Content-Type': 'text/html' }) + response.write(`${this.skelPage[0]}` + + `\tconst _strapp_port = ${route.port}\n` + + `\tconst _strapp_protocol = ` + + `'${this.httpsOpts ? 'wss' : 'ws'}'\n` + + `${this.hostJS}\n${this.skelPage[1]}`) + response.end() }, /** @func @@ -328,7 +346,7 @@ exports = { switch (command) { case '^': if (argv.length < 2) { - dlog(`Malformed '${command}' command from ${route.host}`) + dlog(`Malformed '${command}' command from ${route.origin}`) route.socket.send(`! "Insufficient arguments" 0 ${message}`) break } @@ -356,16 +374,16 @@ exports = { case '!': if (argv.length === 3) argv[0] += `\nIn message: ${argv[2]}` - console.log(`Error[${route.host}|${argv[1]}]:${argv[0]}`) + console.log(`Error[${route.origin}|${argv[1]}]:${argv[0]}`) break default: route.socket.send(`! "Unknown command '${command}'" 0 ${message}`) - dlog(`Host ${route.host} send unknown command: ${message}`) + dlog(`Host ${route.origin} send unknown command: ${message}`) break } }, - /** @func + /** @func * @summary Serve a file to an http client after a request * @desc reads files from the system to be distributed to clients, and * buffers recently accessed files @@ -387,8 +405,8 @@ exports = { response.end() }) }, - - /** @func + + /** @func * @summary Synchronize Reading Multiple Files * @desc reads an array of files into an object, whose keys are the * input filenames, and values are the data read @@ -396,7 +414,7 @@ exports = { * @arg {Object} [readOpts] - options to pass to fs.readFile() */ syncReads: (files, readOpts) => new Promise((resolve,reject) => { - dlog(`syncReads: ${files}`) + dlog(`syncing reads from ${files}`) let count = 0 let results = {} const read_cb = (fileName) => (err, data) => { @@ -412,5 +430,6 @@ exports = { files.forEach((file) => require('fs').readFile(file, readOpts, read_cb(file))) }) - } + +module.exports = exports