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 };
+};