From b4f8ad2fc8fce8925a05b6f1df22129f492db7c2 Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Thu, 16 Apr 2020 14:41:04 +0300 Subject: [PATCH] Alternative server implementation --- lib/application.js | 4 +- lib/server.js | 156 ++++++++++++++++++++++----------------------- 2 files changed, 78 insertions(+), 82 deletions(-) diff --git a/lib/application.js b/lib/application.js index ec8315e..b874fe7 100644 --- a/lib/application.js +++ b/lib/application.js @@ -9,7 +9,7 @@ const path = require('path'); const Config = require('./config.js'); const Logger = require('./logger.js'); const Database = require('./db.js'); -const Server = require('./server.js'); +const serve = require('./server.js'); const Sessions = require('./sessions.js'); const APP_PATH = process.cwd(); @@ -40,8 +40,8 @@ class Application extends EventEmitter { this.config.on('loaded', () => { const { sections } = this.config; this.db = new Database(sections.database, this); - this.server = new Server(sections.server, this); this.sessions = new Sessions(this); + this.server = serve(this); this.emit('started'); }); this.cacheDirectory(STATIC_PATH); diff --git a/lib/server.js b/lib/server.js index 8630210..5432703 100644 --- a/lib/server.js +++ b/lib/server.js @@ -37,122 +37,118 @@ const receiveArgs = async req => new Promise(resolve => { }); }); -class Server { - constructor(config, application) { - this.config = config; - this.application = application; - const { ports, host, transport } = config; - const { threadId } = application.worker; - const port = ports[threadId - 1]; - const listener = this.handler.bind(this); - const lib = TRANSPORT[transport]; - this.instance = lib.createServer({ ...application.cert }, listener); - if (transport.startsWith('ws')) { - this.ws = new WebSocket.Server({ server: this.instance }); - this.ws.on('connection', connection => { - connection.on('message', message => { - this.apiws(connection, message); - }); - }); - } else { - this.instance.on('connection', connection => { - const timeout = setTimeout(() => { - const res = connections.get(connection); - application.server.error(res, 504, 'Gateway Timeout'); - }, LONG_RESPONSE); - connection.on('close', () => { - clearTimeout(timeout); - connections.delete(connection); - }); - }); - } - this.instance.listen(port, host); - } - - async close() { - this.instance.close(err => { - if (err) this.application.logger.error(err.stack); - }); - await timeout(SHUTDOWN_TIMEOUT); - this.closeConnections(); +const closeConnections = () => { + for (const [connection, res] of connections.entries()) { + connections.delete(connection); + res.end('Server stopped'); + connection.destroy(); } +}; - closeConnections() { - for (const [connection, res] of connections.entries()) { - connections.delete(connection); - res.end('Server stopped'); - connection.destroy(); - } - } +const error = (res, status, message) => { + res.statusCode = status; + res.end(`
HTTP ${status}: ${message}
`); +}; - error(res, status, message) { - res.statusCode = status; - res.end(`
HTTP ${status}: ${message}
`); - } +module.exports = application => { + const { config, logger, cache, sessions } = application; + const { ports, host, transport } = config.sections.server; + const { threadId } = application.worker; + const port = ports[threadId - 1]; + const lib = TRANSPORT[transport]; - handler(req, res) { - connections.set(res.connection, res); - const { method, url } = req; - this.application.logger.log(`${method}\t${url}`); - if (url.startsWith('/api/')) { - if (method === 'POST') this.api(req, res); - else this.error(res, 403, 'Forbidden'); - } else { - this.static(req, res); - } - } - - async api(req, res) { + const api = async (req, res) => { const { url } = req; const methodName = url.substring(METHOD_OFFSET); - const session = this.application.sessions.restore(req); + const session = sessions.restore(req); if (!session && methodName !== 'signIn') { - this.application.logger.error(`Forbidden ${url}`); - this.error(res, 403, 'Forbidden'); + logger.error(`Forbidden ${url}`); + error(res, 403, 'Forbidden'); return; } const args = await receiveArgs(req); const sandbox = session ? session.sandbox : undefined; try { - const method = this.application.runScript(methodName, sandbox); + const method = application.runScript(methodName, sandbox); const result = await method({})(args); if (methodName === 'signIn') { - const session = this.application.sessions.start(req); + const session = sessions.start(req); res.setHeader('Set-Cookie', session.cookie); } res.end(JSON.stringify(result)); } catch (err) { - this.application.logger.error(err.stack); + logger.error(err.stack); if (err.message === 'Not found') { - this.error(res, 404, 'File is not found'); + error(res, 404, 'File is not found'); } else { - this.error(res, 500, 'Server error'); + error(res, 500, 'Server error'); } } - } + }; - async apiws(connection, message) { + const apiws = async (connection, message) => { const { method, args } = JSON.parse(message); try { - const fn = this.application.runScript(method); + const fn = application.runScript(method); const result = await fn({})(args); connection.send(JSON.stringify(result)); } catch (err) { - this.application.logger.error(err.stack); + logger.error(err.stack); connection.send(JSON.stringify({ result: 'error' })); } - } + }; - static(req, res) { + const staticFile = (req, res) => { const { url } = req; const filePath = url === '/' ? '/index.html' : url; const fileExt = path.extname(filePath).substring(1); const mimeType = MIME_TYPES[fileExt] || MIME_TYPES.html; res.writeHead(200, { 'Content-Type': mimeType }); - const data = this.application.cache.get(filePath); + const data = cache.get(filePath); if (data) res.end(data); - else this.error(res, 404, 'File is not found'); + else error(res, 404, 'File is not found'); + }; + + const handler = (req, res) => { + connections.set(res.connection, res); + const { method, url } = req; + logger.log(`${method}\t${url}`); + if (url.startsWith('/api/')) { + if (method === 'POST') api(req, res); + else error(res, 403, 'Forbidden'); + } else { + staticFile(req, res); + } + }; + + const instance = lib.createServer({ ...application.cert }, handler); + instance.listen(port, host); + + if (transport.startsWith('ws')) { + const ws = new WebSocket.Server({ server: instance }); + ws.on('connection', connection => { + connection.on('message', apiws); + }); + } else { + instance.on('connection', connection => { + const timeout = setTimeout(() => { + const res = connections.get(connection); + error(res, 504, 'Gateway Timeout'); + }, LONG_RESPONSE); + connection.on('close', () => { + clearTimeout(timeout); + connections.delete(connection); + }); + }); } -} -module.exports = Server; + const close = async () => { + instance.close(err => { + if (err) logger.error(err.stack); + }); + await timeout(SHUTDOWN_TIMEOUT); + closeConnections(); + }; + + return { close }; +};