Skip to content

Commit

Permalink
Added WebSocket support
Browse files Browse the repository at this point in the history
  • Loading branch information
DimaCrafter committed Oct 21, 2018
1 parent c36d01c commit 828db17
Show file tree
Hide file tree
Showing 6 changed files with 777 additions and 56 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [connect-mongo](https://github.com/kcbanner/connect-mongo)
* [Express](https://github.com/expressjs/express)
* [express-session](https://github.com/expressjs/session)
* [express-ws](https://github.com/HenningM/express-ws)
* [Mongoose](https://github.com/Automattic/mongoose)
* [mongoose-auto-increment](https://github.com/codetunnel/mongoose-auto-increment)

Expand Down Expand Up @@ -47,4 +48,5 @@
| `session.ttl` | `36` | Cookie TTL in hours |
| | | |
| `devMode` | `false` | If `true` controllers and models refreshing on every request |
| `port` | `8081` | API listing port |
| `port` | `8081` | API listing port |
| `ws_timeout` | `60` | WebSocket request waiting timeout in seconds |
86 changes: 86 additions & 0 deletions dispatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
const ROOT = process.cwd();
const config = require(ROOT + '/config.json');

const {getHTTPUtils, getWSUtils} = require('./utils');
const path = require('path');
const fs = require('fs');
let loadedControllers = {};

async function getController(controller) {
controller = controller.charAt(0).toUpperCase() + controller.slice(1);
return new Promise((resolve, reject) => {
if (controller in loadedControllers) {
resolve(loadedControllers[controller]);
} else {
const controllerPath = path.normalize(`${ROOT}/controllers/${controller}.js`);
fs.access(controllerPath, fs.constants.F_OK | fs.constants.W_OK, (err) => {
if(err && err.code == 'ENOENT') reject([`API ${controller} controller not found`, 404]);
else if(err) reject([`Can't access ${controller} controller`, 403]);
else {
let controller = require(controllerPath);
controller = new controller();
// Clear cache and don't save controller cache in devMode
config.devMode
? delete require.cache[controllerPath]
: (loadedControllers[controller.constructor.name] = controller);
resolve(controller);
}
});
}
});
}

async function load(controller, action, utils) {
try { controller = await getController(controller); }
catch(err) { throw err; }

controller.onLoad && controller.onLoad();
if (action in controller) return controller[action].apply(utils);
else throw [`API ${controller.constructor.name}.${action} action not found`, 404];
}

const dispatch = {
async http(req, res) {
// Getting controller and action from path
let [controller, action] = req.path.split('/').slice(1);
const utils = getHTTPUtils(req, res, this.db);
try {
await load(controller, action, utils);
} catch(err) {
utils.send(...err);
return;
}
},

async ws(req, ws) {
const utils = getWSUtils(ws, req, this.db);
// Waiting first message with dispatch information
const timeout = setTimeout(() => {
utils.send('connection_timeout', false);
utils.close();
ws.off('message', onData);
}, (config.ws_timeout || 60) * 1000);

const onData = async req => {
clearTimeout(timeout);
try {
req = JSON.parse(req);
} catch {
utils.send('wrong_request', 400);
return;
}

try {
utils.data = req.data;
await load(req.controller, req.action, utils);
} catch(err) {
if(err instanceof Array) { utils.end(...err); }
else { utils.end(err, 500); }
return;
}
};
ws.once('message', onData);
}
};

module.exports = dispatch;
47 changes: 10 additions & 37 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
const express = require('express');
const app = express();
const getUtils = require('./utils');
app.set('json spaces', 4);

const ROOT = process.cwd();
const fs = require('fs');
const path = require('path');
const {getHTTPUtils} = require('./utils');

const config = require(ROOT + '/config.json');
if(!config.db) { console.log('[DB] Config must have connection details'); process.exit(); }
Expand Down Expand Up @@ -50,53 +49,27 @@ const DB = require('./DB');
req.setEncoding('utf8');
req.on('data', chunk => req.body += chunk);
req.on('end', () => {
if(req.body.trim() !== '') {
if(req.body !== '') {
try {
req.body = JSON.parse(req.body);
} catch {
getUtils(req, res).send('Wrong request', 400);
getHTTPUtils(req, res).send('Wrong request', 400);
return;
}
}
next();
});
});

let loadedControllers = {};
app.all('*', (req, res) => {
const utils = getUtils(req, res, MainDB);
function dispatch(controller, action) {
controller.onLoad && controller.onLoad();
if (action in controller) controller[action].apply(utils);
else utils.send(`API ${controller.constructor.name}.${action} action not found`, 404);
}

let [controller, action] = req.path.split('/').slice(1);
// Convert controller's name to PascalCase
controller = controller.charAt(0).toUpperCase() + controller.slice(1);
if (controller in loadedControllers) {
// Dispatching cached controller
dispatch(loadedControllers[controller], action);
} else {
const controllerPath = path.normalize(`${ROOT}/controllers/${controller}.js`);
fs.access(controllerPath, fs.constants.F_OK | fs.constants.W_OK, (err) => {
if(err && err.code == 'ENOENT') utils.send(`API ${controller} controller not found`, 404);
else if(err) utils.send(`Can't access ${controller} controller`, 403);
else {
let controller = require(controllerPath);
controller = new controller();
// Clear and don't save controller cache in devMode
config.devMode
? delete require.cache[controllerPath]
: (loadedControllers[controller.constructor.name] = controller);
dispatch(controller, action);
}
});
}
});
// Dispatching requests
const dispatch = require('./dispatch');
dispatch.db = MainDB;
require('express-ws')(app);
app.ws('/ws', (ws, req) => dispatch.ws(req, ws));
app.all('*', (req, res) => dispatch.http(req, res));

config.port = config.port || 8081;
app.listen(config.port, function () {
app.listen(config.port, () => {
console.log('[Core] Started at port ' + config.port);
});
})();
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
{
"name": "dc-api-core",
"version": "0.1.5-1",
"version": "0.1.6",
"author": "DimaCrafter",
"homepage": "https://github.com/DimaCrafter/dc-api-core",
"bugs": "https://github.com/DimaCrafter/dc-api-core/issues",
"repository": "github.com:DimaCrafter/dc-api-core",
"dependencies": {
"connect-mongo": "^2.0.1",
"express": "^4.16.3",
"express": "^4.16.4",
"express-session": "^1.15.6",
"mongoose": "^5.3.1",
"express-ws": "^4.0.0",
"mongoose": "^5.3.4",
"mongoose-auto-increment": "^5.0.1"
}
}
53 changes: 38 additions & 15 deletions utils.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,40 @@
module.exports = (req, res, db) => {
return {
send(msg, code = 200) {
res.set('Access-Control-Allow-Origin', req.get('origin'));
res.set('Access-Control-Allow-Credentials', 'true');
res.set('Access-Control-Allow-Headers', '*');
res.status(code).json({
success: code == 200,
code,
msg
});
},
const base = (utils, db, req) => {
return Object.assign(utils, {
db,
data: req.body,
session: req.session
};
session: req.session,
ROOT: process.cwd()
});
};

module.exports = {
getHTTPUtils(req, res, db) {
return base({
send(msg, code = 200) {
res.set('Access-Control-Allow-Origin', req.get('origin'));
res.set('Access-Control-Allow-Credentials', 'true');
res.set('Access-Control-Allow-Headers', '*');
res.status(code).json({
success: code === 200,
code,
msg
});
},
data: req.body
}, db, req);
},

getWSUtils(ws, req, db) {
return base({
send(msg, code = 0) {
ws.send(JSON.stringify({
success: code === 0,
code,
msg
}));
},
end(...args) { this.send(...args); this.close(); },
close() { ws.close(); },
data: {}
}, db, req);
}
};
Loading

0 comments on commit 828db17

Please sign in to comment.