From d575e21e4210903fbd5faf65eed38f7a4652f611 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Tue, 17 Apr 2018 12:23:26 +0530 Subject: [PATCH 01/42] Add mongo and redis in package.json --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index ff7bc97..45c3469 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,10 @@ "cookie-signature": "^1.1.0", "formidable": "^1.2.1", "mime": "^2.2.0", + "mongodb": "^3.0.6", "node-fetch": "^2.0.0", "querystring": "^0.2.0", + "redis": "^2.8.0", "turbo-http": "^0.3.0", "uid-safe": "^2.1.5" }, From ae712241e67a6a511f49ea73745defb245365003 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Tue, 17 Apr 2018 12:24:33 +0530 Subject: [PATCH 02/42] Modify store database config --- lib/handlers/session.mjs | 30 ++++++++++-------------------- lib/stores/config.mjs | 33 +++++++++++++++++++++++++++++++++ lib/stores/mongo.mjs | 29 +++++++++++++++++------------ 3 files changed, 60 insertions(+), 32 deletions(-) create mode 100644 lib/stores/config.mjs diff --git a/lib/handlers/session.mjs b/lib/handlers/session.mjs index 06fbb42..5eb2d95 100644 --- a/lib/handlers/session.mjs +++ b/lib/handlers/session.mjs @@ -1,22 +1,12 @@ import uid from 'uid-safe' import signature from 'cookie-signature' -import MemoryStore from '../stores/memory' -import RedisStore from '../stores/redis' -import MongoStore from '../stores/mongo' +import { DataBase, dbOptions } from './../../lib/stores/config' -let options = { - // host: '127.0.0.2', - // port: 5432, - // expire: 120, // 2 min - // database: RedisStore - database: MongoStore -} -let DB = options.database || MemoryStore -let sessionStoreDB = new DB() -let sessionStore = sessionStoreDB.init(options) +let sessionStoreDB = new DataBase() +let sessionStore = sessionStoreDB.init(dbOptions) class Session { - async init (req, res) { + async init(req, res) { let data const SECRET = 'session' if (req.cookies && req.cookies.sess_id) { @@ -34,33 +24,33 @@ class Session { this.sess_id = data.id this.data = data res.setCookie('sess_id', signature.sign(this.sess_id, SECRET), { - maxAge: options.expire || 60 * 60 * 24 * 7 // 1 week + maxAge: dbOptions.expire }) } - async set (key, value) { + async set(key, value) { let data = sessionStore.get(this.sess_id) if (data instanceof Promise) { data = await data } if (!data) { - data = {sess_id: this.sess_id} + data = { sess_id: this.sess_id } } data[key] = value await sessionStore.set(this.sess_id, data) } - get (key) { + get(key) { let result = sessionStore.get(this.sess_id) return result } - delete () { + delete() { sessionStore.delete(this.sess_id) } } -export default async function () { +export default async function() { if (sessionStore instanceof Promise) { sessionStore = await sessionStore } diff --git a/lib/stores/config.mjs b/lib/stores/config.mjs new file mode 100644 index 0000000..51076bb --- /dev/null +++ b/lib/stores/config.mjs @@ -0,0 +1,33 @@ +import MemoryStore from './memory' +import RedisStore from './redis' +import MongoStore from './mongo' + +// Available storage types: In-Memory, Redis, Mongo + +let dbConfig = { + memory: { + host: '', + port: 0, + expire: 60 * 60 * 24 * 7, // 1 week + database: MemoryStore + }, + redis: { + host: '127.0.0.2', + port: 5432, + expire: 120, // 2 min + database: RedisStore + }, + mongo: { + host: '127.0.0.1', + port: 27017, + expire: 120, + database: MongoStore + } +} + +// Set prefered DB by setting dbConfig['mongo' | 'redis' | memory] + +const DataBase = dbConfig['mongo'].database +const dbOptions = dbConfig['mongo'] + +export { DataBase, dbOptions } diff --git a/lib/stores/mongo.mjs b/lib/stores/mongo.mjs index 38cb07f..2f1ebbd 100644 --- a/lib/stores/mongo.mjs +++ b/lib/stores/mongo.mjs @@ -1,30 +1,35 @@ import mongo from 'mongodb' export default class mongoStore { - async init (options) { - let url = options.url || 'mongodb://localhost:27017/' + async init(options) { + let url = `mongodb://${options.host}:${options.port}/` this.client = mongo.MongoClient let db = await this.client.connect(url) - console.log('mongo db connected...') + console.log('Connected to mongo...') this.store = db.db('session-data') return this } - set (hash, value) { + set(hash, value) { let data = {} data['sess_id'] = hash data['data'] = value let obj = {} obj['sess_id'] = hash - this.store.collection('sessions').replaceOne(obj, { $set: data }, { upsert: true }, (err, res) => { - if (err) { - console.log(err) - } - }) + this.store + .collection('sessions') + .replaceOne(obj, { $set: data }, { upsert: true }, (err, res) => { + if (err) { + console.log(err) + } + }) } - async get (key) { - let result = await this.store.collection('sessions').find({ sess_id: key }).toArray() + async get(key) { + let result = await this.store + .collection('sessions') + .find({ sess_id: key }) + .toArray() if (result.length >= 1) { result = result[0] if (result) { @@ -34,7 +39,7 @@ export default class mongoStore { return null } - delete (key) { + delete(key) { this.store.collection('sessions').remove({ sess_id: key }) } } From b99826836d9167760959f4e2ed97d1a879fede24 Mon Sep 17 00:00:00 2001 From: Amal Shehu Date: Tue, 17 Apr 2018 12:33:29 +0530 Subject: [PATCH 03/42] Fix storage database config (#1) * redisStore with set and get only * update with master * linting error fixed * fix lint issues * implemented delete in redisStore * some code clean up * bind(this) in session constructor and remove some logs * removed logs * replaced conrtructor with init which is an async function * fix all issues with set, get and delete + DB option as use preference * options and expire time for db entries * support to redis config options such as port and host * update with upstream with new folder structure * update with upstream with new folder structure * await when data from store is a Promise * delete older file to sync with new folder structure * fixed merge conflicts due to changes done to implement signed cookies * restored changes fir redisStore that was discarded to resolve conflict * Mongo Store all operations working * ensure mongodb connection is established before session init * made other store consistant with MongoStore init() signature * code clean up * reverting the test file * reverting package-lock.json * Add mongo and redis in package.json * Modify store database config --- lib/handlers/session.mjs | 38 +++++++++++++++++++++++---------- lib/stores/config.mjs | 33 +++++++++++++++++++++++++++++ lib/stores/memory.mjs | 3 ++- lib/stores/mongo.mjs | 45 ++++++++++++++++++++++++++++++++++++++++ lib/stores/redis.mjs | 36 ++++++++++++++++++++++++++++++++ package-lock.json | 2 +- package.json | 2 ++ tests/test.mjs | 2 +- 8 files changed, 147 insertions(+), 14 deletions(-) create mode 100644 lib/stores/config.mjs create mode 100644 lib/stores/mongo.mjs create mode 100644 lib/stores/redis.mjs diff --git a/lib/handlers/session.mjs b/lib/handlers/session.mjs index 5c97c9e..5eb2d95 100644 --- a/lib/handlers/session.mjs +++ b/lib/handlers/session.mjs @@ -1,16 +1,21 @@ import uid from 'uid-safe' import signature from 'cookie-signature' -import MemoryStore from '../stores/memory' +import { DataBase, dbOptions } from './../../lib/stores/config' -const sessionStore = new MemoryStore() +let sessionStoreDB = new DataBase() +let sessionStore = sessionStoreDB.init(dbOptions) class Session { - constructor (req, res) { + async init(req, res) { let data const SECRET = 'session' if (req.cookies && req.cookies.sess_id) { const unsigned = signature.unsign(req.cookies.sess_id, SECRET) + this.sess_id = unsigned data = sessionStore.get(unsigned) + if (data instanceof Promise) { + data = await data + } } if (!data) { data = { id: uid.sync(64) } @@ -19,26 +24,37 @@ class Session { this.sess_id = data.id this.data = data res.setCookie('sess_id', signature.sign(this.sess_id, SECRET), { - maxAge: 60 * 60 * 24 * 7 // 1 week + maxAge: dbOptions.expire }) } - set (key, value) { + async set(key, value) { let data = sessionStore.get(this.sess_id) + if (data instanceof Promise) { + data = await data + } + if (!data) { + data = { sess_id: this.sess_id } + } data[key] = value - sessionStore.set(this.sess_id, data) + await sessionStore.set(this.sess_id, data) } - get (key) { - return sessionStore.get(this.sess_id)[key] + get(key) { + let result = sessionStore.get(this.sess_id) + return result } - delete () { + delete() { sessionStore.delete(this.sess_id) } } -export default function () { - this.session = new Session(this, this.res) +export default async function() { + if (sessionStore instanceof Promise) { + sessionStore = await sessionStore + } + this.session = new Session() + await this.session.init(this, this.res) return null } diff --git a/lib/stores/config.mjs b/lib/stores/config.mjs new file mode 100644 index 0000000..51076bb --- /dev/null +++ b/lib/stores/config.mjs @@ -0,0 +1,33 @@ +import MemoryStore from './memory' +import RedisStore from './redis' +import MongoStore from './mongo' + +// Available storage types: In-Memory, Redis, Mongo + +let dbConfig = { + memory: { + host: '', + port: 0, + expire: 60 * 60 * 24 * 7, // 1 week + database: MemoryStore + }, + redis: { + host: '127.0.0.2', + port: 5432, + expire: 120, // 2 min + database: RedisStore + }, + mongo: { + host: '127.0.0.1', + port: 27017, + expire: 120, + database: MongoStore + } +} + +// Set prefered DB by setting dbConfig['mongo' | 'redis' | memory] + +const DataBase = dbConfig['mongo'].database +const dbOptions = dbConfig['mongo'] + +export { DataBase, dbOptions } diff --git a/lib/stores/memory.mjs b/lib/stores/memory.mjs index 91a8c4f..f02d57f 100644 --- a/lib/stores/memory.mjs +++ b/lib/stores/memory.mjs @@ -1,6 +1,7 @@ export default class MemoryStore { - constructor () { + init () { this.store = {} + return this } set (key, value) { this.store[key] = value diff --git a/lib/stores/mongo.mjs b/lib/stores/mongo.mjs new file mode 100644 index 0000000..2f1ebbd --- /dev/null +++ b/lib/stores/mongo.mjs @@ -0,0 +1,45 @@ +import mongo from 'mongodb' + +export default class mongoStore { + async init(options) { + let url = `mongodb://${options.host}:${options.port}/` + this.client = mongo.MongoClient + let db = await this.client.connect(url) + console.log('Connected to mongo...') + this.store = db.db('session-data') + return this + } + + set(hash, value) { + let data = {} + data['sess_id'] = hash + data['data'] = value + let obj = {} + obj['sess_id'] = hash + this.store + .collection('sessions') + .replaceOne(obj, { $set: data }, { upsert: true }, (err, res) => { + if (err) { + console.log(err) + } + }) + } + + async get(key) { + let result = await this.store + .collection('sessions') + .find({ sess_id: key }) + .toArray() + if (result.length >= 1) { + result = result[0] + if (result) { + return result['data'] + } + } + return null + } + + delete(key) { + this.store.collection('sessions').remove({ sess_id: key }) + } +} diff --git a/lib/stores/redis.mjs b/lib/stores/redis.mjs new file mode 100644 index 0000000..31345cd --- /dev/null +++ b/lib/stores/redis.mjs @@ -0,0 +1,36 @@ +import redis from 'redis' +import util from 'util' + +export default class redisStore { + init (options) { + this.client = redis.createClient(options) + this.hgetall = util.promisify(this.client.hgetall).bind(this.client) + this.hmset = util.promisify(this.client.hmset).bind(this.client) + this.del = util.promisify(this.client.del).bind(this.client) + this.ttl = options.expire || 60 * 60 * 24 * 7 // default 1 week + this.client.on('connect', function () { + console.log('connected to redis...') + }) + this.client.on('error', function (error) { + console.error('Error: RedisStore reported an error: ' + error) + }) + return this + } + + set (hash, value) { + this.hmset(hash, value) + if (this.ttl) { + let timeout = new Date().setSeconds(this.ttl) + this.client.expireat(hash, parseInt(timeout / 1000)) + } + } + + get (key) { + let result = this.hgetall(key) + return result + } + + delete (key) { + this.del(key) + } +} diff --git a/package-lock.json b/package-lock.json index 9166737..2322e95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -411,4 +411,4 @@ "dev": true } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index ff7bc97..45c3469 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,10 @@ "cookie-signature": "^1.1.0", "formidable": "^1.2.1", "mime": "^2.2.0", + "mongodb": "^3.0.6", "node-fetch": "^2.0.0", "querystring": "^0.2.0", + "redis": "^2.8.0", "turbo-http": "^0.3.0", "uid-safe": "^2.1.5" }, diff --git a/tests/test.mjs b/tests/test.mjs index ebdf980..991c6d0 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -158,4 +158,4 @@ test('responds to requests', async (t) => { // Shutdown App Server app.close() -}) +}) \ No newline at end of file From d56fec3af58725c07db013f4b96f4f2e0e35158e Mon Sep 17 00:00:00 2001 From: amalshehu Date: Tue, 17 Apr 2018 12:46:12 +0530 Subject: [PATCH 04/42] Remove test file --- index.mjs | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 index.mjs diff --git a/index.mjs b/index.mjs deleted file mode 100644 index acf131f..0000000 --- a/index.mjs +++ /dev/null @@ -1,9 +0,0 @@ -import App from './lib/server' - -const app = new App() -const router = app.getRouter() -router.get('/user/123', function() { - this.res.send('TEST TURBO SERVER') -}) - -app.listen(8080) From c650c4add2bc348e18d15f38b863b3f6c548c44c Mon Sep 17 00:00:00 2001 From: amalshehu Date: Tue, 17 Apr 2018 18:22:36 +0530 Subject: [PATCH 05/42] Modify session database config --- lib/stores/config.mjs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/stores/config.mjs b/lib/stores/config.mjs index 51076bb..4c3434e 100644 --- a/lib/stores/config.mjs +++ b/lib/stores/config.mjs @@ -8,26 +8,27 @@ let dbConfig = { memory: { host: '', port: 0, - expire: 60 * 60 * 24 * 7, // 1 week + // expire: 60 * 60 * 24 * 7, // 1 week + expire: 3, database: MemoryStore }, redis: { - host: '127.0.0.2', - port: 5432, + host: '127.0.0.1', + port: 6379, expire: 120, // 2 min database: RedisStore }, mongo: { host: '127.0.0.1', port: 27017, - expire: 120, + expire: 3, database: MongoStore } } // Set prefered DB by setting dbConfig['mongo' | 'redis' | memory] -const DataBase = dbConfig['mongo'].database -const dbOptions = dbConfig['mongo'] +const DataBase = dbConfig['memory'].database +const dbOptions = dbConfig['memory'] export { DataBase, dbOptions } From 4834c4b98cdf888d9558f8ba8fa531b11231a01b Mon Sep 17 00:00:00 2001 From: amalshehu Date: Tue, 17 Apr 2018 18:23:10 +0530 Subject: [PATCH 06/42] Fix expiry timeout for MemoryStore --- lib/stores/memory.mjs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/stores/memory.mjs b/lib/stores/memory.mjs index f02d57f..58b9856 100644 --- a/lib/stores/memory.mjs +++ b/lib/stores/memory.mjs @@ -1,15 +1,20 @@ export default class MemoryStore { - init () { + init(options) { this.store = {} + this.expiryTime = options.expire return this } - set (key, value) { + set(key, value) { this.store[key] = value + + setTimeout(() => { + this.delete(key) + }, this.expiryTime * 1000) } - get (key) { + get(key) { return this.store[key] } - delete (key) { + delete(key) { delete this.store[key] } } From f16b2e71cf1db5834e297ee23b78923994867a13 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Tue, 17 Apr 2018 18:24:18 +0530 Subject: [PATCH 07/42] Set expiry time for MongoStore --- lib/stores/mongo.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/stores/mongo.mjs b/lib/stores/mongo.mjs index 2f1ebbd..f545404 100644 --- a/lib/stores/mongo.mjs +++ b/lib/stores/mongo.mjs @@ -7,6 +7,9 @@ export default class mongoStore { let db = await this.client.connect(url) console.log('Connected to mongo...') this.store = db.db('session-data') + this.store + .collection('sessions') + .createIndex({ createdAt: 1 }, { expireAfterSeconds: options.expire }) return this } @@ -16,6 +19,7 @@ export default class mongoStore { data['data'] = value let obj = {} obj['sess_id'] = hash + obj['createdAt'] = new Date() this.store .collection('sessions') .replaceOne(obj, { $set: data }, { upsert: true }, (err, res) => { From cb807b413e306a7542246707cc2cdd2c2bfb04ee Mon Sep 17 00:00:00 2001 From: Amal Shehu Date: Tue, 17 Apr 2018 18:31:58 +0530 Subject: [PATCH 08/42] Fix expiry timeout for MongoStore and MemoryStore (#2) * Add mongo and redis in package.json * Modify store database config * Modify session database config * Fix expiry timeout for MemoryStore * Set expiry time for MongoStore --- lib/stores/config.mjs | 13 +++++++------ lib/stores/memory.mjs | 13 +++++++++---- lib/stores/mongo.mjs | 4 ++++ 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/lib/stores/config.mjs b/lib/stores/config.mjs index 51076bb..2f3a3a6 100644 --- a/lib/stores/config.mjs +++ b/lib/stores/config.mjs @@ -8,26 +8,27 @@ let dbConfig = { memory: { host: '', port: 0, - expire: 60 * 60 * 24 * 7, // 1 week + // expire: 60 * 60 * 24 * 7, // 1 week + expire: 3, database: MemoryStore }, redis: { - host: '127.0.0.2', - port: 5432, + host: '127.0.0.1', + port: 6379, expire: 120, // 2 min database: RedisStore }, mongo: { host: '127.0.0.1', port: 27017, + expire: 120, database: MongoStore } } // Set prefered DB by setting dbConfig['mongo' | 'redis' | memory] - -const DataBase = dbConfig['mongo'].database -const dbOptions = dbConfig['mongo'] +const DataBase = dbConfig['memory'].database +const dbOptions = dbConfig['memory'] export { DataBase, dbOptions } diff --git a/lib/stores/memory.mjs b/lib/stores/memory.mjs index f02d57f..58b9856 100644 --- a/lib/stores/memory.mjs +++ b/lib/stores/memory.mjs @@ -1,15 +1,20 @@ export default class MemoryStore { - init () { + init(options) { this.store = {} + this.expiryTime = options.expire return this } - set (key, value) { + set(key, value) { this.store[key] = value + + setTimeout(() => { + this.delete(key) + }, this.expiryTime * 1000) } - get (key) { + get(key) { return this.store[key] } - delete (key) { + delete(key) { delete this.store[key] } } diff --git a/lib/stores/mongo.mjs b/lib/stores/mongo.mjs index 2f1ebbd..f545404 100644 --- a/lib/stores/mongo.mjs +++ b/lib/stores/mongo.mjs @@ -7,6 +7,9 @@ export default class mongoStore { let db = await this.client.connect(url) console.log('Connected to mongo...') this.store = db.db('session-data') + this.store + .collection('sessions') + .createIndex({ createdAt: 1 }, { expireAfterSeconds: options.expire }) return this } @@ -16,6 +19,7 @@ export default class mongoStore { data['data'] = value let obj = {} obj['sess_id'] = hash + obj['createdAt'] = new Date() this.store .collection('sessions') .replaceOne(obj, { $set: data }, { upsert: true }, (err, res) => { From bed0ce586234e67a0ab07e4426572057965ffbc2 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 18 Apr 2018 14:21:29 +0530 Subject: [PATCH 09/42] Modify getHandler method --- lib/router.mjs | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/lib/router.mjs b/lib/router.mjs index a8eafd7..064681f 100644 --- a/lib/router.mjs +++ b/lib/router.mjs @@ -1,20 +1,29 @@ - export default class Router { - constructor (path = '/') { - this.routes = {GET: {}, POST: {}} + constructor(path = '/') { + this.routes = { GET: {}, POST: {} } this.path = path this.isRouter = true } - get (path, fn) { + get(path, fn) { this.routes.GET[path] = fn } - post (path, fn) { + post(path, fn) { this.routes.POST[path] = fn } - getHandler (method, path) { - return this.routes[method][path] + getHandler(method, path) { + let stockPath + let reqPath = path.split('/').filter(i => i.length > 1) + const xPath = Object.keys(this.routes[method]).filter(p => { + stockPath = p.split('/').filter(i => i.length > 1) + if (reqPath[0] === stockPath[0] && reqPath.length === stockPath.length) { + return p + } + }) + return undefined != xPath + ? this.routes[method][xPath] + : this.routes[method][path] } } From ead51b498f6d5e4c67e17f520d8ee4abebf3600b Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 18 Apr 2018 14:22:15 +0530 Subject: [PATCH 10/42] Add tests for url with params --- tests/test.mjs | 285 ++++++++++++++++++++++++++----------------------- 1 file changed, 154 insertions(+), 131 deletions(-) diff --git a/tests/test.mjs b/tests/test.mjs index 991c6d0..969cac7 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -3,159 +3,182 @@ import test from 'tape' import App from '../lib/server' import FormData from 'form-data' import signature from 'cookie-signature' - // Start App Server const app = new App() const router = app.getRouter() -router.post('/', function () { +router.post('/', function() { this.res.send(this.body) }) -router.get('/session', function () { - this.res.send({sess_id: this.session.sess_id}) +router.get('/session', function() { + this.res.send({ sess_id: this.session.sess_id }) }) -router.get('/redirect', function () { +router.get('/redirect', function() { this.res.redirect('/wonderland') }) -router.post('/urlencoded', function () { +router.post('/urlencoded', function() { this.res.send(this.body) }) -router.post('/multipartform', function () { +router.post('/multipartform', function() { this.res.send(this.body) }) -router.get('/download', function () { +router.get('/download', function() { const file = './public/index.html' const filename = 'app.html' this.res.download(file, filename) }) +router.get('/user/:id', function() { + this.res.send('Hi') +}) + app.listen() // process.env.PORT || 5000 -test('responds to requests', async (t) => { - t.plan(24) - let res, data, cookie, error - - try { - res = await fetch('http://127.0.0.1:5000/aa') - data = await res.text() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 404) - t.equal(data, 'Not Found') - - // Test GET '/'. Should return index.html in public folder - - try { - res = await fetch('http://127.0.0.1:5000') - data = await res.text() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.equal(data, '

hello world

\n') - - // Test POST '/' with {hello: 'world'} - - try { - res = await fetch('http://127.0.0.1:5000', { - method: 'POST', - headers: {'Content-Type': 'application/json'}, - body: JSON.stringify({hello: 'world'}) - }) - data = await res.json() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.deepEqual(data, {hello: 'world'}) - - // Test Session - - try { - res = await fetch('http://127.0.0.1:5000/session') - data = await res.json() - cookie = res.headers.get('set-cookie') - const [name, value] = cookie.split(';')[0].split('=') - const val = signature.unsign(decodeURIComponent(value), 'session') - cookie = {[name]: val} - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.deepEqual(data, cookie) - - // Test res.redirect - - try { - res = await fetch('http://127.0.0.1:5000/redirect', { - redirect: 'manual', - follow: 0 - }) - data = res.headers.get('Location') - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 302) - t.equal(data, 'http://127.0.0.1:5000/wonderland') - - // Test urlencoded - - try { - res = await fetch('http://127.0.0.1:5000/urlencoded', { - method: 'POST', - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, - body: 'input1=hello&input2=world&input3=are+you%3F' - }) - data = await res.json() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.deepEqual(data, {'input1': 'hello', 'input2': 'world', 'input3': 'are you?'}) - - // Test multipart form-data - - try { - let form = new FormData() - form.append('input1', 'hello') - form.append('input2', 'world') - form.append('input3', 'are you?') - res = await fetch('http://127.0.0.1:5000/multipartform', { - method: 'POST', - body: form - }) - data = await res.json() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.deepEqual(data.fields, {'input1': 'hello', 'input2': 'world', 'input3': 'are you?'}) - - // Test res.download - - try { - res = await fetch('http://127.0.0.1:5000/download') - data = res.headers.get('Content-Disposition') - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.equal(data, 'attachment;filename="app.html"') - - // Shutdown App Server - app.close() -}) \ No newline at end of file +// test('responds to requests', async t => { +// t.plan(24) +// let res, data, cookie, error + +// try { +// res = await fetch('http://127.0.0.1:5000/aa') +// data = await res.text() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 404) +// t.equal(data, 'Not Found') + +// // Test GET '/'. Should return index.html in public folder + +// try { +// res = await fetch('http://127.0.0.1:5000') +// data = await res.text() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.equal(data, '

hello world

\n') + +// // Test POST '/' with {hello: 'world'} + +// try { +// res = await fetch('http://127.0.0.1:5000', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ hello: 'world' }) +// }) +// data = await res.json() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data, { hello: 'world' }) + +// // Test GET '/users/:id' + +// try { +// res = await fetch('http://127.0.0.1:5000/users/101', { +// method: 'GET', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({}) +// }) +// data = await res.json() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data, { name: 'Batman', id: '101' }) + +// // Test Session + +// try { +// res = await fetch('http://127.0.0.1:5000/session') +// data = await res.json() +// cookie = res.headers.get('set-cookie') +// const [name, value] = cookie.split(';')[0].split('=') +// const val = signature.unsign(decodeURIComponent(value), 'session') +// cookie = { [name]: val } +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data, cookie) + +// // Test res.redirect + +// try { +// res = await fetch('http://127.0.0.1:5000/redirect', { +// redirect: 'manual', +// follow: 0 +// }) +// data = res.headers.get('Location') +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 302) +// t.equal(data, 'http://127.0.0.1:5000/wonderland') + +// // Test urlencoded + +// try { +// res = await fetch('http://127.0.0.1:5000/urlencoded', { +// method: 'POST', +// headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, +// body: 'input1=hello&input2=world&input3=are+you%3F' +// }) +// data = await res.json() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data, { input1: 'hello', input2: 'world', input3: 'are you?' }) + +// // Test multipart form-data + +// try { +// let form = new FormData() +// form.append('input1', 'hello') +// form.append('input2', 'world') +// form.append('input3', 'are you?') +// res = await fetch('http://127.0.0.1:5000/multipartform', { +// method: 'POST', +// body: form +// }) +// data = await res.json() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data.fields, { +// input1: 'hello', +// input2: 'world', +// input3: 'are you?' +// }) + +// // Test res.download + +// try { +// res = await fetch('http://127.0.0.1:5000/download') +// data = res.headers.get('Content-Disposition') +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.equal(data, 'attachment;filename="app.html"') + +// // Shutdown App Server +// app.close() +// }) From 310ae011089a4f1b4ba248536cb432a1b502a01e Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 18 Apr 2018 14:38:19 +0530 Subject: [PATCH 11/42] Reformat code --- lib/router.mjs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/router.mjs b/lib/router.mjs index 064681f..00f02f7 100644 --- a/lib/router.mjs +++ b/lib/router.mjs @@ -14,16 +14,20 @@ export default class Router { } getHandler(method, path) { - let stockPath - let reqPath = path.split('/').filter(i => i.length > 1) - const xPath = Object.keys(this.routes[method]).filter(p => { - stockPath = p.split('/').filter(i => i.length > 1) - if (reqPath[0] === stockPath[0] && reqPath.length === stockPath.length) { - return p - } - }) + const xPath = this.checkParams(method, path) return undefined != xPath ? this.routes[method][xPath] : this.routes[method][path] } + + checkParams(method, path) { + let stockPath + let reqPath = path.slice(1).split('/') + return Object.keys(this.routes[method]).filter(part => { + stockPath = part.slice(1).split('/') + return reqPath[0] === stockPath[0] && reqPath.length === stockPath.length + ? part + : undefined + }) + } } From 5b1224f9e1ddf4230a60070dd159a997c3ca8717 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 10:13:28 +0530 Subject: [PATCH 12/42] Modify getHandler --- lib/router.mjs | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/router.mjs b/lib/router.mjs index 00f02f7..7c1f49a 100644 --- a/lib/router.mjs +++ b/lib/router.mjs @@ -13,21 +13,34 @@ export default class Router { this.routes.POST[path] = fn } - getHandler(method, path) { - const xPath = this.checkParams(method, path) + getHandler(method, path, context) { + const extractedParams = this.checkParams(method, path) + let xPath = extractedParams[0] + context.params = extractedParams[1] return undefined != xPath - ? this.routes[method][xPath] + ? this.routes[method][xPath].bind(context) : this.routes[method][path] } checkParams(method, path) { + const params = {} + let matchedRoute let stockPath let reqPath = path.slice(1).split('/') - return Object.keys(this.routes[method]).filter(part => { + // get all route names from defined routes + Object.keys(this.routes[method]).filter(part => { stockPath = part.slice(1).split('/') - return reqPath[0] === stockPath[0] && reqPath.length === stockPath.length - ? part - : undefined + const condition = + reqPath[0] === stockPath[0] && reqPath.length === stockPath.length + if (condition) { + for (let i = 1; i < stockPath.length; i++) { + if (stockPath[i].startsWith(':')) { + params[stockPath[i].slice(1)] = reqPath[i] + } + } + matchedRoute = part + } }) + return [matchedRoute, params] } } From 9979d872ae782212c3a5d6240af20b3ff704a32a Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 10:13:40 +0530 Subject: [PATCH 13/42] Fix typos --- lib/server.mjs | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/lib/server.mjs b/lib/server.mjs index 04d7672..59318b7 100644 --- a/lib/server.mjs +++ b/lib/server.mjs @@ -8,16 +8,11 @@ import finalHandler from './handlers/final' import Router from './router' import sessionHandler from './handlers/session' -const HANDLERS = [ - staticHandler, - cookieParser, - sessionHandler, - bodyParser -] +const HANDLERS = [staticHandler, cookieParser, sessionHandler, bodyParser] export default class App { - constructor () { - this.server = turbo.createServer(function (req, res) { + constructor() { + this.server = turbo.createServer(function(req, res) { Object.assign(req, Request) Object.assign(res, Response) req.res = res @@ -27,36 +22,37 @@ export default class App { this.addRouter(new Router()) } - addRouter (router) { + addRouter(router) { if (!this.router) { this.router = router - HANDLERS.push(function () { - const handler = router.getHandler(this.method, this.url) + HANDLERS.push(function() { + const handler = router.getHandler(this.method, this.url, this) if (handler) { return this.callHandlers(handler) } return null - }, - finalHandler) + }, finalHandler) } } - getRouter () { + getRouter() { return this.router } - listen (port) { + listen(port) { this.PORT = process.env.PORT || port || 5000 this.server.listen(this.PORT) console.log('Server running on PORT ' + this.PORT) } - close () { + close() { this.server.close(_ => { console.log('Shutting down App!') process.exit() }) } - static get Router () { return Router } + static get Router() { + return Router + } } From 5ab44c4806595097d761e522888f0fd15c235127 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 11:16:06 +0530 Subject: [PATCH 14/42] Relocate param logic --- lib/router.mjs | 31 ++----------------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/lib/router.mjs b/lib/router.mjs index 7c1f49a..79ecd54 100644 --- a/lib/router.mjs +++ b/lib/router.mjs @@ -13,34 +13,7 @@ export default class Router { this.routes.POST[path] = fn } - getHandler(method, path, context) { - const extractedParams = this.checkParams(method, path) - let xPath = extractedParams[0] - context.params = extractedParams[1] - return undefined != xPath - ? this.routes[method][xPath].bind(context) - : this.routes[method][path] - } - - checkParams(method, path) { - const params = {} - let matchedRoute - let stockPath - let reqPath = path.slice(1).split('/') - // get all route names from defined routes - Object.keys(this.routes[method]).filter(part => { - stockPath = part.slice(1).split('/') - const condition = - reqPath[0] === stockPath[0] && reqPath.length === stockPath.length - if (condition) { - for (let i = 1; i < stockPath.length; i++) { - if (stockPath[i].startsWith(':')) { - params[stockPath[i].slice(1)] = reqPath[i] - } - } - matchedRoute = part - } - }) - return [matchedRoute, params] + getHandler(method, path) { + return this.routes[method][path] } } From 2fae8d8f9757cef2ffeb9f5785453304e1be08b4 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 11:16:24 +0530 Subject: [PATCH 15/42] Add param parser --- lib/handlers/params.mjs | 28 ++++++++++++++++++++++++++++ lib/server.mjs | 11 +++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 lib/handlers/params.mjs diff --git a/lib/handlers/params.mjs b/lib/handlers/params.mjs new file mode 100644 index 0000000..3a413b7 --- /dev/null +++ b/lib/handlers/params.mjs @@ -0,0 +1,28 @@ +export default function() { + let reqPath = this.url.slice(1).split('/') + this.param = isFinite(reqPath[1]) ? Number(reqPath[1]) : reqPath[1] + this.url = `/${reqPath[0]}` + return null +} + +// const checkParams = (method, path) => { +// const params = {} +// let matchedRoute +// let stockPath +// let reqPath = path.slice(1).split('/') +// // get all route names from defined routes +// Object.keys(this.routes[method]).filter(part => { +// stockPath = part.slice(1).split('/') +// const condition = +// reqPath[0] === stockPath[0] && reqPath.length === stockPath.length +// if (condition) { +// for (let i = 1; i < stockPath.length; i++) { +// if (stockPath[i].startsWith(':')) { +// params[stockPath[i].slice(1)] = reqPath[i] +// } +// } +// matchedRoute = part +// } +// }) +// return [matchedRoute, params] +// } diff --git a/lib/server.mjs b/lib/server.mjs index 59318b7..f9b0537 100644 --- a/lib/server.mjs +++ b/lib/server.mjs @@ -2,13 +2,20 @@ import turbo from 'turbo-http' import Request from './request' import Response from './response' import cookieParser from './handlers/cookie' +import paramParser from './handlers/params' import bodyParser from './handlers/body' import staticHandler from './handlers/static' import finalHandler from './handlers/final' import Router from './router' import sessionHandler from './handlers/session' -const HANDLERS = [staticHandler, cookieParser, sessionHandler, bodyParser] +const HANDLERS = [ + staticHandler, + cookieParser, + sessionHandler, + bodyParser, + paramParser +] export default class App { constructor() { @@ -26,7 +33,7 @@ export default class App { if (!this.router) { this.router = router HANDLERS.push(function() { - const handler = router.getHandler(this.method, this.url, this) + const handler = router.getHandler(this.method, this.url) if (handler) { return this.callHandlers(handler) } From bf6266f463b8af6fcd0dfd50f38fa9dc7ac4913d Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 11:17:08 +0530 Subject: [PATCH 16/42] Update routes with param tests --- tests/test.mjs | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/tests/test.mjs b/tests/test.mjs index 969cac7..4097392 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -33,8 +33,8 @@ router.get('/download', function() { this.res.download(file, filename) }) -router.get('/user/:id', function() { - this.res.send('Hi') +router.get('/user', function() { + this.res.send(`Hi User!. Your id is ${this.param}`) }) app.listen() // process.env.PORT || 5000 @@ -81,22 +81,6 @@ app.listen() // process.env.PORT || 5000 // t.equal(res.status, 200) // t.deepEqual(data, { hello: 'world' }) -// // Test GET '/users/:id' - -// try { -// res = await fetch('http://127.0.0.1:5000/users/101', { -// method: 'GET', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({}) -// }) -// data = await res.json() -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 200) -// t.deepEqual(data, { name: 'Batman', id: '101' }) - // // Test Session // try { From e9acca04676679c12b9cc3980d1609d5c08ac4ee Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 12:03:23 +0530 Subject: [PATCH 17/42] Fix routes with params --- lib/handlers/params.mjs | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/lib/handlers/params.mjs b/lib/handlers/params.mjs index 3a413b7..3d0ff26 100644 --- a/lib/handlers/params.mjs +++ b/lib/handlers/params.mjs @@ -1,28 +1,8 @@ export default function() { - let reqPath = this.url.slice(1).split('/') - this.param = isFinite(reqPath[1]) ? Number(reqPath[1]) : reqPath[1] - this.url = `/${reqPath[0]}` + let urlSlices = this.url.slice(1).split('/') + this.param = isFinite(urlSlices[1]) ? Number(urlSlices[1]) : urlSlices[1] + var paramIdx = urlSlices.indexOf(urlSlices[1]) + if (paramIdx !== -1) urlSlices.splice(paramIdx, 1) + this.url = `/${urlSlices.join('/')}` return null } - -// const checkParams = (method, path) => { -// const params = {} -// let matchedRoute -// let stockPath -// let reqPath = path.slice(1).split('/') -// // get all route names from defined routes -// Object.keys(this.routes[method]).filter(part => { -// stockPath = part.slice(1).split('/') -// const condition = -// reqPath[0] === stockPath[0] && reqPath.length === stockPath.length -// if (condition) { -// for (let i = 1; i < stockPath.length; i++) { -// if (stockPath[i].startsWith(':')) { -// params[stockPath[i].slice(1)] = reqPath[i] -// } -// } -// matchedRoute = part -// } -// }) -// return [matchedRoute, params] -// } From a208508784d55309cf68588250f07df6512b0bef Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 12:03:39 +0530 Subject: [PATCH 18/42] Add more tests --- tests/test.mjs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test.mjs b/tests/test.mjs index 4097392..97abda1 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -37,6 +37,14 @@ router.get('/user', function() { this.res.send(`Hi User!. Your id is ${this.param}`) }) +router.get('/user/edit', function() { + this.res.send(`Your id is ${this.param}. This is an edit route.`) +}) + +router.get('/user/delete', function() { + this.res.send(`Your id is ${this.param}. Delete user logic goes here.`) +}) + app.listen() // process.env.PORT || 5000 // test('responds to requests', async t => { From 6b9dc38cb9f10979bf251ae685a893f5f510eb13 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 15:08:35 +0530 Subject: [PATCH 19/42] Add temporary session variable to config --- lib/config.mjs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/config.mjs b/lib/config.mjs index 9bcf84a..d4557c8 100644 --- a/lib/config.mjs +++ b/lib/config.mjs @@ -4,7 +4,8 @@ import fs from 'fs' const ROOT = process.cwd() const configPath = path.join(ROOT, 'turbo-serv.json') -const configDefault = {static: {dir: 'public'}} +const configDefault = { static: { dir: 'public' } } +const sessionDefault = { store: 'memory' } let config try { @@ -13,6 +14,7 @@ try { } catch (e) { console.log('Parser Error in config file') config = configDefault + config.session = sessionDefault } config.ROOT = ROOT From 4906f8b5406e0f02e02c379d778673562327cda0 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 15:09:16 +0530 Subject: [PATCH 20/42] Temporary config for session store --- lib/stores/store.mjs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 lib/stores/store.mjs diff --git a/lib/stores/store.mjs b/lib/stores/store.mjs new file mode 100644 index 0000000..c51bf67 --- /dev/null +++ b/lib/stores/store.mjs @@ -0,0 +1,36 @@ +import MemoryStore from './memory' +import RedisStore from './redis' +import MongoStore from './mongo' + +import config from './../config' + +// Available storage types: In-Memory, Redis, Mongo + +let dbConfig = { + memory: { + host: '', + port: 0, + // expire: 60 * 60 * 24 * 7, // 1 week + expire: 3, + database: MemoryStore + }, + redis: { + host: '127.0.0.1', + port: 6379, + expire: 120, // 2 min + database: RedisStore + }, + mongo: { + host: '127.0.0.1', + port: 27017, + + expire: 120, + database: MongoStore + } +} + +// Set prefered DB by setting dbConfig['mongo' | 'redis' | memory] +const DataBase = dbConfig[config.session.store].database +const dbOptions = dbConfig[config.session.store] + +export { DataBase, dbOptions } From afe1dae2f55a4068b8581d002301ced0f2055c7f Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 15:09:50 +0530 Subject: [PATCH 21/42] Rename file stores/config -> stores/store --- lib/handlers/session.mjs | 2 +- lib/stores/config.mjs | 34 ---------------------------------- 2 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 lib/stores/config.mjs diff --git a/lib/handlers/session.mjs b/lib/handlers/session.mjs index 5eb2d95..5f638b5 100644 --- a/lib/handlers/session.mjs +++ b/lib/handlers/session.mjs @@ -1,6 +1,6 @@ import uid from 'uid-safe' import signature from 'cookie-signature' -import { DataBase, dbOptions } from './../../lib/stores/config' +import { DataBase, dbOptions } from './../../lib/stores/store' let sessionStoreDB = new DataBase() let sessionStore = sessionStoreDB.init(dbOptions) diff --git a/lib/stores/config.mjs b/lib/stores/config.mjs deleted file mode 100644 index 2f3a3a6..0000000 --- a/lib/stores/config.mjs +++ /dev/null @@ -1,34 +0,0 @@ -import MemoryStore from './memory' -import RedisStore from './redis' -import MongoStore from './mongo' - -// Available storage types: In-Memory, Redis, Mongo - -let dbConfig = { - memory: { - host: '', - port: 0, - // expire: 60 * 60 * 24 * 7, // 1 week - expire: 3, - database: MemoryStore - }, - redis: { - host: '127.0.0.1', - port: 6379, - expire: 120, // 2 min - database: RedisStore - }, - mongo: { - host: '127.0.0.1', - port: 27017, - - expire: 120, - database: MongoStore - } -} - -// Set prefered DB by setting dbConfig['mongo' | 'redis' | memory] -const DataBase = dbConfig['memory'].database -const dbOptions = dbConfig['memory'] - -export { DataBase, dbOptions } From c738034b39a226c788f4448544fbe6409c3b2e55 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 16:51:13 +0530 Subject: [PATCH 22/42] Set session store default values --- lib/config.mjs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/config.mjs b/lib/config.mjs index d4557c8..230c9e9 100644 --- a/lib/config.mjs +++ b/lib/config.mjs @@ -5,7 +5,14 @@ const ROOT = process.cwd() const configPath = path.join(ROOT, 'turbo-serv.json') const configDefault = { static: { dir: 'public' } } -const sessionDefault = { store: 'memory' } +const sessionDefault = { + store: 'memory', + options: { + host: '0.0.0.0', + port: 0, + expire: 120 + } +} let config try { From 25def341237370277c4e8d400933ca86370d4346 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 16:51:30 +0530 Subject: [PATCH 23/42] Modify store config --- lib/stores/store.mjs | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/lib/stores/store.mjs b/lib/stores/store.mjs index c51bf67..7dfe02d 100644 --- a/lib/stores/store.mjs +++ b/lib/stores/store.mjs @@ -6,31 +6,14 @@ import config from './../config' // Available storage types: In-Memory, Redis, Mongo -let dbConfig = { - memory: { - host: '', - port: 0, - // expire: 60 * 60 * 24 * 7, // 1 week - expire: 3, - database: MemoryStore - }, - redis: { - host: '127.0.0.1', - port: 6379, - expire: 120, // 2 min - database: RedisStore - }, - mongo: { - host: '127.0.0.1', - port: 27017, - - expire: 120, - database: MongoStore - } +let storeConfig = { + memory: MemoryStore, + redis: RedisStore, + mongo: MongoStore } - // Set prefered DB by setting dbConfig['mongo' | 'redis' | memory] -const DataBase = dbConfig[config.session.store].database -const dbOptions = dbConfig[config.session.store] + +const DataBase = storeConfig[config.session.store] +const dbOptions = config.session.options export { DataBase, dbOptions } From 85948a00f7e83790b3c078aaf4304c66c9105b66 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 17:51:28 +0530 Subject: [PATCH 24/42] Add tests for url with param. --- tests/test.mjs | 53 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/tests/test.mjs b/tests/test.mjs index 170a7cd..07e9d0e 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -34,21 +34,23 @@ router.get('/download', function() { }) router.get('/user', function() { - this.res.send(`Hi User!. Your id is ${this.param}`) + this.res.send({ user: `Hi User!. Your id is ${this.param}` }) }) router.get('/user/edit', function() { - this.res.send(`Your id is ${this.param}. This is an edit route.`) + this.res.send({ user: `Your id is ${this.param}. This is an edit route.` }) }) router.get('/user/delete', function() { - this.res.send(`Your id is ${this.param}. Delete user logic goes here.`) + this.res.send({ + user: `Your id is ${this.param}. Delete user logic goes here.` + }) }) app.listen() // process.env.PORT || 5000 test('responds to requests', async t => { - t.plan(24) + t.plan(33) let res, data, cookie, error try { @@ -171,6 +173,49 @@ test('responds to requests', async t => { t.equal(res.status, 200) t.equal(data, 'attachment;filename="app.html"') + // Test user with param + + try { + res = await fetch('http://127.0.0.1:5000/user/507f160ea', { + method: 'GET' + }) + data = await res.json() + } catch (e) { + error = e + } + t.false(error) + t.equal(res.status, 200) + t.deepEqual(data, { user: 'Hi User!. Your id is 507f160ea' }) + + // Test user with param and edit route. + + try { + res = await fetch('http://127.0.0.1:5000/user/507f160ea/edit', { + method: 'GET' + }) + data = await res.json() + } catch (e) { + error = e + } + t.false(error) + t.equal(res.status, 200) + t.deepEqual(data, { user: 'Your id is 507f160ea. This is an edit route.' }) + + // Test user with param and delete route. + + try { + res = await fetch('http://127.0.0.1:5000/user/507f160ea/delete', { + method: 'GET' + }) + data = await res.json() + } catch (e) { + error = e + } + t.false(error) + t.equal(res.status, 200) + t.deepEqual(data, { + user: 'Your id is 507f160ea. Delete user logic goes here.' + }) // Shutdown App Server app.close() }) From d02108fb508dfa2bed4c6ec05cedb2728787ce4e Mon Sep 17 00:00:00 2001 From: amalshehu Date: Thu, 19 Apr 2018 18:34:31 +0530 Subject: [PATCH 25/42] Fix mongo indexing issue. --- lib/stores/mongo.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/stores/mongo.mjs b/lib/stores/mongo.mjs index f545404..7f949fe 100644 --- a/lib/stores/mongo.mjs +++ b/lib/stores/mongo.mjs @@ -9,7 +9,7 @@ export default class mongoStore { this.store = db.db('session-data') this.store .collection('sessions') - .createIndex({ createdAt: 1 }, { expireAfterSeconds: options.expire }) + .createIndex({ createdAt: -1 }, { expireAfterSeconds: options.expire }) return this } From 49c7cdade4e77490ba28c83155a366535d5daceb Mon Sep 17 00:00:00 2001 From: amalshehu Date: Mon, 23 Apr 2018 12:13:30 +0530 Subject: [PATCH 26/42] Remove paramHandler --- lib/server.mjs | 18 +-- tests/test.mjs | 402 +++++++++++++++++++++++++------------------------ 2 files changed, 211 insertions(+), 209 deletions(-) diff --git a/lib/server.mjs b/lib/server.mjs index f9b0537..8e03e60 100644 --- a/lib/server.mjs +++ b/lib/server.mjs @@ -2,20 +2,13 @@ import turbo from 'turbo-http' import Request from './request' import Response from './response' import cookieParser from './handlers/cookie' -import paramParser from './handlers/params' import bodyParser from './handlers/body' import staticHandler from './handlers/static' import finalHandler from './handlers/final' import Router from './router' import sessionHandler from './handlers/session' -const HANDLERS = [ - staticHandler, - cookieParser, - sessionHandler, - bodyParser, - paramParser -] +const HANDLERS = [staticHandler, cookieParser, sessionHandler, bodyParser] export default class App { constructor() { @@ -26,12 +19,14 @@ export default class App { req._handlers = HANDLERS res.req = req }) + this.routers = {} this.addRouter(new Router()) } addRouter(router) { - if (!this.router) { + if (!this.routers[router.path]) { this.router = router + this.routers[router.path] = router HANDLERS.push(function() { const handler = router.getHandler(this.method, this.url) if (handler) { @@ -42,8 +37,9 @@ export default class App { } } - getRouter() { - return this.router + getRouter(path = '/') { + this.addRouter(new Router(path)) + return this.routers[path] } listen(port) { diff --git a/tests/test.mjs b/tests/test.mjs index 07e9d0e..ea6c44a 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -3,219 +3,225 @@ import test from 'tape' import App from '../lib/server' import FormData from 'form-data' import signature from 'cookie-signature' +import Router from './../lib/router' // Start App Server const app = new App() const router = app.getRouter() +const settingsRouter = app.getRouter('/settings') + router.post('/', function() { this.res.send(this.body) }) - -router.get('/session', function() { - this.res.send({ sess_id: this.session.sess_id }) +settingsRouter.get('/create', function() { + this.res.send('settings/create route') }) -router.get('/redirect', function() { - this.res.redirect('/wonderland') -}) +// router.get('/session', function() { +// this.res.send({ sess_id: this.session.sess_id }) +// }) -router.post('/urlencoded', function() { - this.res.send(this.body) -}) +// router.get('/redirect', function() { +// this.res.redirect('/wonderland') +// }) -router.post('/multipartform', function() { - this.res.send(this.body) -}) +// router.post('/urlencoded', function() { +// this.res.send(this.body) +// }) -router.get('/download', function() { - const file = './public/index.html' - const filename = 'app.html' - this.res.download(file, filename) -}) +// router.post('/multipartform', function() { +// this.res.send(this.body) +// }) -router.get('/user', function() { - this.res.send({ user: `Hi User!. Your id is ${this.param}` }) -}) +// router.get('/download', function() { +// const file = './public/index.html' +// const filename = 'app.html' +// this.res.download(file, filename) +// }) -router.get('/user/edit', function() { - this.res.send({ user: `Your id is ${this.param}. This is an edit route.` }) -}) +// router.get('/user', function() { +// this.res.send({ user: `Hi User!. Your id is ${this.param}` }) +// }) -router.get('/user/delete', function() { - this.res.send({ - user: `Your id is ${this.param}. Delete user logic goes here.` - }) -}) +// router.get('/user/edit', function() { +// this.res.send({ user: `Your id is ${this.param}. This is an edit route.` }) +// }) + +// router.get('/user/delete', function() { +// this.res.send({ +// user: `Your id is ${this.param}. Delete user logic goes here.` +// }) +// }) app.listen() // process.env.PORT || 5000 -test('responds to requests', async t => { - t.plan(33) - let res, data, cookie, error - - try { - res = await fetch('http://127.0.0.1:5000/aa') - data = await res.text() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 404) - t.equal(data, 'Not Found') - - // Test GET '/'. Should return index.html in public folder - - try { - res = await fetch('http://127.0.0.1:5000') - data = await res.text() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.equal(data, '

hello world

\n') - - // Test POST '/' with {hello: 'world'} - - try { - res = await fetch('http://127.0.0.1:5000', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ hello: 'world' }) - }) - data = await res.json() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.deepEqual(data, { hello: 'world' }) - - // Test Session - - try { - res = await fetch('http://127.0.0.1:5000/session') - data = await res.json() - cookie = res.headers.get('set-cookie') - const [name, value] = cookie.split(';')[0].split('=') - const val = signature.unsign(decodeURIComponent(value), 'session') - cookie = { [name]: val } - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.deepEqual(data, cookie) - - // Test res.redirect - - try { - res = await fetch('http://127.0.0.1:5000/redirect', { - redirect: 'manual', - follow: 0 - }) - data = res.headers.get('Location') - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 302) - t.equal(data, 'http://127.0.0.1:5000/wonderland') - - // Test urlencoded - - try { - res = await fetch('http://127.0.0.1:5000/urlencoded', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, - body: 'input1=hello&input2=world&input3=are+you%3F' - }) - data = await res.json() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.deepEqual(data, { input1: 'hello', input2: 'world', input3: 'are you?' }) - - // Test multipart form-data - - try { - let form = new FormData() - form.append('input1', 'hello') - form.append('input2', 'world') - form.append('input3', 'are you?') - res = await fetch('http://127.0.0.1:5000/multipartform', { - method: 'POST', - body: form - }) - data = await res.json() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.deepEqual(data.fields, { - input1: 'hello', - input2: 'world', - input3: 'are you?' - }) - - // Test res.download - - try { - res = await fetch('http://127.0.0.1:5000/download') - data = res.headers.get('Content-Disposition') - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.equal(data, 'attachment;filename="app.html"') - - // Test user with param - - try { - res = await fetch('http://127.0.0.1:5000/user/507f160ea', { - method: 'GET' - }) - data = await res.json() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.deepEqual(data, { user: 'Hi User!. Your id is 507f160ea' }) - - // Test user with param and edit route. - - try { - res = await fetch('http://127.0.0.1:5000/user/507f160ea/edit', { - method: 'GET' - }) - data = await res.json() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.deepEqual(data, { user: 'Your id is 507f160ea. This is an edit route.' }) - - // Test user with param and delete route. - - try { - res = await fetch('http://127.0.0.1:5000/user/507f160ea/delete', { - method: 'GET' - }) - data = await res.json() - } catch (e) { - error = e - } - t.false(error) - t.equal(res.status, 200) - t.deepEqual(data, { - user: 'Your id is 507f160ea. Delete user logic goes here.' - }) - // Shutdown App Server - app.close() -}) +// test('responds to requests', async t => { +// t.plan(33) +// let res, data, cookie, error + +// try { +// res = await fetch('http://127.0.0.1:5000/aa') +// data = await res.text() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 404) +// t.equal(data, 'Not Found') + +// // Test GET '/'. Should return index.html in public folder + +// try { +// res = await fetch('http://127.0.0.1:5000') +// data = await res.text() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.equal(data, '

hello world

\n') + +// // Test POST '/' with {hello: 'world'} + +// try { +// res = await fetch('http://127.0.0.1:5000', { +// method: 'POST', +// headers: { 'Content-Type': 'application/json' }, +// body: JSON.stringify({ hello: 'world' }) +// }) +// data = await res.json() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data, { hello: 'world' }) + +// // Test Session + +// try { +// res = await fetch('http://127.0.0.1:5000/session') +// data = await res.json() +// cookie = res.headers.get('set-cookie') +// const [name, value] = cookie.split(';')[0].split('=') +// const val = signature.unsign(decodeURIComponent(value), 'session') +// cookie = { [name]: val } +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data, cookie) + +// // Test res.redirect + +// try { +// res = await fetch('http://127.0.0.1:5000/redirect', { +// redirect: 'manual', +// follow: 0 +// }) +// data = res.headers.get('Location') +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 302) +// t.equal(data, 'http://127.0.0.1:5000/wonderland') + +// // Test urlencoded + +// try { +// res = await fetch('http://127.0.0.1:5000/urlencoded', { +// method: 'POST', +// headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, +// body: 'input1=hello&input2=world&input3=are+you%3F' +// }) +// data = await res.json() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data, { input1: 'hello', input2: 'world', input3: 'are you?' }) + +// // Test multipart form-data + +// try { +// let form = new FormData() +// form.append('input1', 'hello') +// form.append('input2', 'world') +// form.append('input3', 'are you?') +// res = await fetch('http://127.0.0.1:5000/multipartform', { +// method: 'POST', +// body: form +// }) +// data = await res.json() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data.fields, { +// input1: 'hello', +// input2: 'world', +// input3: 'are you?' +// }) + +// // Test res.download + +// try { +// res = await fetch('http://127.0.0.1:5000/download') +// data = res.headers.get('Content-Disposition') +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.equal(data, 'attachment;filename="app.html"') + +// // Test user with param + +// try { +// res = await fetch('http://127.0.0.1:5000/user/507f160ea', { +// method: 'GET' +// }) +// data = await res.json() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data, { user: 'Hi User!. Your id is 507f160ea' }) + +// // Test user with param and edit route. + +// try { +// res = await fetch('http://127.0.0.1:5000/user/507f160ea/edit', { +// method: 'GET' +// }) +// data = await res.json() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data, { user: 'Your id is 507f160ea. This is an edit route.' }) + +// // Test user with param and delete route. + +// try { +// res = await fetch('http://127.0.0.1:5000/user/507f160ea/delete', { +// method: 'GET' +// }) +// data = await res.json() +// } catch (e) { +// error = e +// } +// t.false(error) +// t.equal(res.status, 200) +// t.deepEqual(data, { +// user: 'Your id is 507f160ea. Delete user logic goes here.' +// }) +// // Shutdown App Server +// app.close() +// }) From 123d47d7030556a1f45d8def1323b007f6f39133 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Mon, 23 Apr 2018 14:09:45 +0530 Subject: [PATCH 27/42] Create settingsRouter stub --- lib/server.mjs | 8 ++++---- tests/test.mjs | 11 +++++------ 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/server.mjs b/lib/server.mjs index 8e03e60..4c7e532 100644 --- a/lib/server.mjs +++ b/lib/server.mjs @@ -37,9 +37,9 @@ export default class App { } } - getRouter(path = '/') { - this.addRouter(new Router(path)) - return this.routers[path] + getRouter() { + // this.addRouter(new Router(path)) + return this.routes } listen(port) { @@ -55,7 +55,7 @@ export default class App { }) } - static get Router() { + static Routers() { return Router } } diff --git a/tests/test.mjs b/tests/test.mjs index ea6c44a..8891d36 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -3,16 +3,15 @@ import test from 'tape' import App from '../lib/server' import FormData from 'form-data' import signature from 'cookie-signature' -import Router from './../lib/router' // Start App Server const app = new App() -const router = app.getRouter() +// const router = app.getRouter() -const settingsRouter = app.getRouter('/settings') +const settingsRouter = new (App.Routers())('/settings') -router.post('/', function() { - this.res.send(this.body) -}) +// router.post('/', function() { +// this.res.send(this.body) +// }) settingsRouter.get('/create', function() { this.res.send('settings/create route') }) From 3c2d78b055fb1d0a55e45154cbdcb2a25f108f88 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Tue, 24 Apr 2018 09:51:22 +0530 Subject: [PATCH 28/42] Created nested routers --- tests/test.mjs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/test.mjs b/tests/test.mjs index 8891d36..a1a0f4e 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -5,13 +5,19 @@ import FormData from 'form-data' import signature from 'cookie-signature' // Start App Server const app = new App() -// const router = app.getRouter() +const router = app.getRouter() +const settingsRouter = app.getRouter('/settings') -const settingsRouter = new (App.Routers())('/settings') +// const settingsRouter = new (App.Routers())('/settings') + +router.post('/', function() { + this.res.send(this.body) +}) + +router.get('/session', function() { + this.res.send({ sess_id: this.session.sess_id }) +}) -// router.post('/', function() { -// this.res.send(this.body) -// }) settingsRouter.get('/create', function() { this.res.send('settings/create route') }) From 7a871a47cfe70462731681c9e17a9e24b6de8796 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Tue, 24 Apr 2018 09:51:45 +0530 Subject: [PATCH 29/42] Modify getRouter method --- lib/server.mjs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/server.mjs b/lib/server.mjs index 4c7e532..1b9946d 100644 --- a/lib/server.mjs +++ b/lib/server.mjs @@ -22,10 +22,9 @@ export default class App { this.routers = {} this.addRouter(new Router()) } - addRouter(router) { if (!this.routers[router.path]) { - this.router = router + // this.router = router this.routers[router.path] = router HANDLERS.push(function() { const handler = router.getHandler(this.method, this.url) @@ -37,9 +36,11 @@ export default class App { } } - getRouter() { - // this.addRouter(new Router(path)) - return this.routes + getRouter(path = '/') { + if (!this.routers[path]) { + let router = this.addRouter(new Router(path)) + } + return this.routers[path] } listen(port) { From cec42f76ef692f8942059c0fb34716b2803094c9 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Tue, 24 Apr 2018 12:16:59 +0530 Subject: [PATCH 30/42] Code cleanup --- lib/server.mjs | 21 ++++++++++----------- tests/test.mjs | 3 ++- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/server.mjs b/lib/server.mjs index 1b9946d..7a8b00e 100644 --- a/lib/server.mjs +++ b/lib/server.mjs @@ -20,12 +20,11 @@ export default class App { res.req = req }) this.routers = {} - this.addRouter(new Router()) + this.addRoute(new Router()) } - addRouter(router) { - if (!this.routers[router.path]) { - // this.router = router - this.routers[router.path] = router + addRoute(router) { + if (!this.router) { + this.router = router HANDLERS.push(function() { const handler = router.getHandler(this.method, this.url) if (handler) { @@ -36,11 +35,11 @@ export default class App { } } - getRouter(path = '/') { - if (!this.routers[path]) { - let router = this.addRouter(new Router(path)) - } - return this.routers[path] + getRouter() { + // if (!this.routers[path]) { + // let router = this.addRoute(new Router(path)) + // } + return this.router } listen(port) { @@ -56,7 +55,7 @@ export default class App { }) } - static Routers() { + static get Router() { return Router } } diff --git a/tests/test.mjs b/tests/test.mjs index a1a0f4e..84ae056 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -6,9 +6,10 @@ import signature from 'cookie-signature' // Start App Server const app = new App() const router = app.getRouter() -const settingsRouter = app.getRouter('/settings') +// const settingsRouter = app.getRouter('/settings') // const settingsRouter = new (App.Routers())('/settings') +const settingsRouter = new App.Router('/settings') router.post('/', function() { this.res.send(this.body) From 9ea881e10b1ac29f433f9b8528f2bb2f38f6f117 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 08:48:24 +0530 Subject: [PATCH 31/42] Implemented option to add nested router --- lib/router.mjs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/router.mjs b/lib/router.mjs index 79ecd54..17196f3 100644 --- a/lib/router.mjs +++ b/lib/router.mjs @@ -3,6 +3,7 @@ export default class Router { this.routes = { GET: {}, POST: {} } this.path = path this.isRouter = true + this.routers = [] } get(path, fn) { @@ -13,7 +14,22 @@ export default class Router { this.routes.POST[path] = fn } + addRouter(router) { + this.routers.push(router) + } + getHandler(method, path) { - return this.routes[method][path] + const handler = this.routes[method][path] + return handler ? handler : this.findRouter(method, path) + } + + findRouter(method, path) { + for (const router of this.routers) { + if (path.startsWith(router.path)) { + path = path.slice(router.path.length) + const handler = router.routes[method] + return path == '' ? handler['/'] : handler[path] + } + } } } From 161af67c4f1c0634648e2d1e936b0eb99c00b933 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 08:48:38 +0530 Subject: [PATCH 32/42] Fix typos --- lib/server.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/server.mjs b/lib/server.mjs index 7a8b00e..c000fd1 100644 --- a/lib/server.mjs +++ b/lib/server.mjs @@ -19,7 +19,6 @@ export default class App { req._handlers = HANDLERS res.req = req }) - this.routers = {} this.addRoute(new Router()) } addRoute(router) { From a1f2530f1e3aa42562fc79c9de44733ea6978164 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 08:48:50 +0530 Subject: [PATCH 33/42] Add tests --- tests/test.mjs | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/test.mjs b/tests/test.mjs index 84ae056..aaadbda 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -6,10 +6,12 @@ import signature from 'cookie-signature' // Start App Server const app = new App() const router = app.getRouter() -// const settingsRouter = app.getRouter('/settings') -// const settingsRouter = new (App.Routers())('/settings') const settingsRouter = new App.Router('/settings') +const adminRouter = new App.Router('/admin') + +router.addRouter(settingsRouter) +router.addRouter(adminRouter) router.post('/', function() { this.res.send(this.body) @@ -19,10 +21,30 @@ router.get('/session', function() { this.res.send({ sess_id: this.session.sess_id }) }) +settingsRouter.get('/', function() { + this.res.send(`From settings router root path`) +}) + settingsRouter.get('/create', function() { this.res.send('settings/create route') }) +settingsRouter.post('/config', function() { + this.res.send('settings/config route') +}) + +adminRouter.post('/createUser', function() { + this.res.send('admin/createUser route') +}) + +const layoutRouter = new App.Router('/layout') + +layoutRouter.post('/modify', function() { + this.res.send('settings/create route') +}) + +settingsRouter.addRouter(layoutRouter) + // router.get('/session', function() { // this.res.send({ sess_id: this.session.sess_id }) // }) From 8d3a49ac16da6f2d866dbc58fe16d4df97b0d6e6 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 11:11:23 +0530 Subject: [PATCH 34/42] Add recursive route handler. --- lib/router.mjs | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/router.mjs b/lib/router.mjs index 17196f3..49b8c61 100644 --- a/lib/router.mjs +++ b/lib/router.mjs @@ -20,13 +20,34 @@ export default class Router { getHandler(method, path) { const handler = this.routes[method][path] - return handler ? handler : this.findRouter(method, path) + return handler ? handler : this.findRouter(method, path, this.routers) } - findRouter(method, path) { - for (const router of this.routers) { + findRouter(method, path, routers, idx = 0) { + // for (const router of this.routers) { + // if (path.startsWith(router.path)) { + // path = path.slice(router.path.length) + // const handler = router.routes[method] + // return path == '' ? handler['/'] : handler[path] + // } + // } + const splitPath = path.slice(1).split('/') + if (!Array.isArray(routers)) routers = [routers] + for (const router of routers) { if (path.startsWith(router.path)) { path = path.slice(router.path.length) + const splitPath = path + .slice(1) + .split('/') + .map(p => `/${p}`) + if (router.routers[idx].path === splitPath.shift()) { + path = path.slice(router.routers[idx].path.length) + return this.findRouter(method, path, router.routers[idx], idx++) + } else if (router.routes[method]) { + const handler = router.routes[method] + return path == '' ? handler['/'] : handler[path] + } + } else { const handler = router.routes[method] return path == '' ? handler['/'] : handler[path] } From b254eb4b44dda70cb05c5794688cefc9d85ac741 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 11:11:36 +0530 Subject: [PATCH 35/42] Modify tests --- tests/test.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test.mjs b/tests/test.mjs index aaadbda..8fefa4a 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -40,7 +40,7 @@ adminRouter.post('/createUser', function() { const layoutRouter = new App.Router('/layout') layoutRouter.post('/modify', function() { - this.res.send('settings/create route') + this.res.send('settings/layout/modify route') }) settingsRouter.addRouter(layoutRouter) From b09da71143c829115954e690667b77424b7ed897 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 12:47:06 +0530 Subject: [PATCH 36/42] Add another nested route --- tests/test.mjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test.mjs b/tests/test.mjs index 8fefa4a..d1add0d 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -43,6 +43,10 @@ layoutRouter.post('/modify', function() { this.res.send('settings/layout/modify route') }) +layoutRouter.get('/currentStyle', function() { + this.res.send('settings/layout/currentStyle route') +}) + settingsRouter.addRouter(layoutRouter) // router.get('/session', function() { From 6c05a0c90ac447fd77ef7fb20d81a5ee96b73c1e Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 12:47:28 +0530 Subject: [PATCH 37/42] Modify findRouter method --- lib/router.mjs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/router.mjs b/lib/router.mjs index 49b8c61..18bb080 100644 --- a/lib/router.mjs +++ b/lib/router.mjs @@ -20,18 +20,13 @@ export default class Router { getHandler(method, path) { const handler = this.routes[method][path] - return handler ? handler : this.findRouter(method, path, this.routers) + return typeof handler == 'function' + ? handler + : this.findRouter(method, path, this.routers) } findRouter(method, path, routers, idx = 0) { - // for (const router of this.routers) { - // if (path.startsWith(router.path)) { - // path = path.slice(router.path.length) - // const handler = router.routes[method] - // return path == '' ? handler['/'] : handler[path] - // } - // } - const splitPath = path.slice(1).split('/') + // const splitPath = path.slice(1).split('/') if (!Array.isArray(routers)) routers = [routers] for (const router of routers) { if (path.startsWith(router.path)) { @@ -40,17 +35,28 @@ export default class Router { .slice(1) .split('/') .map(p => `/${p}`) - if (router.routers[idx].path === splitPath.shift()) { + if ( + router.routers.length > 0 && + router.routers[idx].path === splitPath.shift() + ) { path = path.slice(router.routers[idx].path.length) return this.findRouter(method, path, router.routers[idx], idx++) } else if (router.routes[method]) { const handler = router.routes[method] return path == '' ? handler['/'] : handler[path] } - } else { - const handler = router.routes[method] + } + const handler = router.routes[method] + if (handler[path]) { return path == '' ? handler['/'] : handler[path] } } } } +// for (const router of this.routers) { +// if (path.startsWith(router.path)) { +// path = path.slice(router.path.length) +// const handler = router.routes[method] +// return path == '' ? handler['/'] : handler[path] +// } +// } From f51ae1f1444d1a756f02c5adca52e99798238ed9 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 15:01:08 +0530 Subject: [PATCH 38/42] Add tokenize url method --- lib/router.mjs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/router.mjs b/lib/router.mjs index 18bb080..6dc0493 100644 --- a/lib/router.mjs +++ b/lib/router.mjs @@ -26,15 +26,11 @@ export default class Router { } findRouter(method, path, routers, idx = 0) { - // const splitPath = path.slice(1).split('/') if (!Array.isArray(routers)) routers = [routers] for (const router of routers) { if (path.startsWith(router.path)) { path = path.slice(router.path.length) - const splitPath = path - .slice(1) - .split('/') - .map(p => `/${p}`) + const splitPath = this.tokenizeUrl(path) if ( router.routers.length > 0 && router.routers[idx].path === splitPath.shift() @@ -52,6 +48,12 @@ export default class Router { } } } + tokenizeUrl = path => { + return path + .slice(1) + .split('/') + .map(p => `/${p}`) + } } // for (const router of this.routers) { // if (path.startsWith(router.path)) { From 7310f3f1bcb29d57981fa426ee223c4a1e161466 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 18:45:08 +0530 Subject: [PATCH 39/42] Fix Routes chaining issue --- lib/router.mjs | 44 ++++++++++++++++---------------------------- 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/lib/router.mjs b/lib/router.mjs index 6dc0493..98d4481 100644 --- a/lib/router.mjs +++ b/lib/router.mjs @@ -25,40 +25,28 @@ export default class Router { : this.findRouter(method, path, this.routers) } - findRouter(method, path, routers, idx = 0) { + findRouter(method, path, routers) { if (!Array.isArray(routers)) routers = [routers] for (const router of routers) { if (path.startsWith(router.path)) { path = path.slice(router.path.length) - const splitPath = this.tokenizeUrl(path) - if ( - router.routers.length > 0 && - router.routers[idx].path === splitPath.shift() - ) { - path = path.slice(router.routers[idx].path.length) - return this.findRouter(method, path, router.routers[idx], idx++) - } else if (router.routes[method]) { - const handler = router.routes[method] - return path == '' ? handler['/'] : handler[path] - } - } - const handler = router.routes[method] - if (handler[path]) { - return path == '' ? handler['/'] : handler[path] + const deepRouter = this.hasDeepRouter( + router, + path + .slice(1) + .split('/') + .map(token => `/${token}`) + .shift() + ) + return deepRouter + ? this.findRouter(method, path, deepRouter) + : router.routes[method][path] } } } - tokenizeUrl = path => { - return path - .slice(1) - .split('/') - .map(p => `/${p}`) + + hasDeepRouter(router, pathToken) { + const route = router.routers.filter(route => route.path === pathToken) + return route ? route[0] : null } } -// for (const router of this.routers) { -// if (path.startsWith(router.path)) { -// path = path.slice(router.path.length) -// const handler = router.routes[method] -// return path == '' ? handler['/'] : handler[path] -// } -// } From fd0d8f8b0b6c618bc0a2797200f88213518d0748 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 18:45:27 +0530 Subject: [PATCH 40/42] Add tests --- tests/test.mjs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/tests/test.mjs b/tests/test.mjs index d1add0d..910fd66 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -9,9 +9,14 @@ const router = app.getRouter() const settingsRouter = new App.Router('/settings') const adminRouter = new App.Router('/admin') +const layoutRouter = new App.Router('/layout') +const sideBarRouter = new App.Router('/sidebar') router.addRouter(settingsRouter) router.addRouter(adminRouter) +adminRouter.addRouter(settingsRouter) +settingsRouter.addRouter(layoutRouter) +layoutRouter.addRouter(sideBarRouter) router.post('/', function() { this.res.send(this.body) @@ -37,8 +42,6 @@ adminRouter.post('/createUser', function() { this.res.send('admin/createUser route') }) -const layoutRouter = new App.Router('/layout') - layoutRouter.post('/modify', function() { this.res.send('settings/layout/modify route') }) @@ -46,8 +49,13 @@ layoutRouter.post('/modify', function() { layoutRouter.get('/currentStyle', function() { this.res.send('settings/layout/currentStyle route') }) +sideBarRouter.get('/status', function() { + this.res.send('settings/layout/sidebar/status route') +}) -settingsRouter.addRouter(layoutRouter) +sideBarRouter.post('/toggle', function() { + this.res.send('settings/layout/sidebar/toggle route') +}) // router.get('/session', function() { // this.res.send({ sess_id: this.session.sess_id }) From 4fe0e12855399c0f3962526b77f43ea14e4c82b6 Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 18:54:30 +0530 Subject: [PATCH 41/42] Update tests --- tests/test.mjs | 353 +++++++++++++++++++++---------------------------- 1 file changed, 148 insertions(+), 205 deletions(-) diff --git a/tests/test.mjs b/tests/test.mjs index 910fd66..b109db5 100644 --- a/tests/test.mjs +++ b/tests/test.mjs @@ -57,211 +57,154 @@ sideBarRouter.post('/toggle', function() { this.res.send('settings/layout/sidebar/toggle route') }) -// router.get('/session', function() { -// this.res.send({ sess_id: this.session.sess_id }) -// }) - -// router.get('/redirect', function() { -// this.res.redirect('/wonderland') -// }) - -// router.post('/urlencoded', function() { -// this.res.send(this.body) -// }) - -// router.post('/multipartform', function() { -// this.res.send(this.body) -// }) - -// router.get('/download', function() { -// const file = './public/index.html' -// const filename = 'app.html' -// this.res.download(file, filename) -// }) - -// router.get('/user', function() { -// this.res.send({ user: `Hi User!. Your id is ${this.param}` }) -// }) - -// router.get('/user/edit', function() { -// this.res.send({ user: `Your id is ${this.param}. This is an edit route.` }) -// }) - -// router.get('/user/delete', function() { -// this.res.send({ -// user: `Your id is ${this.param}. Delete user logic goes here.` -// }) -// }) +router.get('/session', function() { + this.res.send({ sess_id: this.session.sess_id }) +}) + +router.get('/redirect', function() { + this.res.redirect('/wonderland') +}) + +router.post('/urlencoded', function() { + this.res.send(this.body) +}) + +router.post('/multipartform', function() { + this.res.send(this.body) +}) + +router.get('/download', function() { + const file = './public/index.html' + const filename = 'app.html' + this.res.download(file, filename) +}) app.listen() // process.env.PORT || 5000 -// test('responds to requests', async t => { -// t.plan(33) -// let res, data, cookie, error - -// try { -// res = await fetch('http://127.0.0.1:5000/aa') -// data = await res.text() -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 404) -// t.equal(data, 'Not Found') - -// // Test GET '/'. Should return index.html in public folder - -// try { -// res = await fetch('http://127.0.0.1:5000') -// data = await res.text() -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 200) -// t.equal(data, '

hello world

\n') - -// // Test POST '/' with {hello: 'world'} - -// try { -// res = await fetch('http://127.0.0.1:5000', { -// method: 'POST', -// headers: { 'Content-Type': 'application/json' }, -// body: JSON.stringify({ hello: 'world' }) -// }) -// data = await res.json() -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 200) -// t.deepEqual(data, { hello: 'world' }) - -// // Test Session - -// try { -// res = await fetch('http://127.0.0.1:5000/session') -// data = await res.json() -// cookie = res.headers.get('set-cookie') -// const [name, value] = cookie.split(';')[0].split('=') -// const val = signature.unsign(decodeURIComponent(value), 'session') -// cookie = { [name]: val } -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 200) -// t.deepEqual(data, cookie) - -// // Test res.redirect - -// try { -// res = await fetch('http://127.0.0.1:5000/redirect', { -// redirect: 'manual', -// follow: 0 -// }) -// data = res.headers.get('Location') -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 302) -// t.equal(data, 'http://127.0.0.1:5000/wonderland') - -// // Test urlencoded - -// try { -// res = await fetch('http://127.0.0.1:5000/urlencoded', { -// method: 'POST', -// headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, -// body: 'input1=hello&input2=world&input3=are+you%3F' -// }) -// data = await res.json() -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 200) -// t.deepEqual(data, { input1: 'hello', input2: 'world', input3: 'are you?' }) - -// // Test multipart form-data - -// try { -// let form = new FormData() -// form.append('input1', 'hello') -// form.append('input2', 'world') -// form.append('input3', 'are you?') -// res = await fetch('http://127.0.0.1:5000/multipartform', { -// method: 'POST', -// body: form -// }) -// data = await res.json() -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 200) -// t.deepEqual(data.fields, { -// input1: 'hello', -// input2: 'world', -// input3: 'are you?' -// }) - -// // Test res.download - -// try { -// res = await fetch('http://127.0.0.1:5000/download') -// data = res.headers.get('Content-Disposition') -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 200) -// t.equal(data, 'attachment;filename="app.html"') - -// // Test user with param - -// try { -// res = await fetch('http://127.0.0.1:5000/user/507f160ea', { -// method: 'GET' -// }) -// data = await res.json() -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 200) -// t.deepEqual(data, { user: 'Hi User!. Your id is 507f160ea' }) - -// // Test user with param and edit route. - -// try { -// res = await fetch('http://127.0.0.1:5000/user/507f160ea/edit', { -// method: 'GET' -// }) -// data = await res.json() -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 200) -// t.deepEqual(data, { user: 'Your id is 507f160ea. This is an edit route.' }) - -// // Test user with param and delete route. - -// try { -// res = await fetch('http://127.0.0.1:5000/user/507f160ea/delete', { -// method: 'GET' -// }) -// data = await res.json() -// } catch (e) { -// error = e -// } -// t.false(error) -// t.equal(res.status, 200) -// t.deepEqual(data, { -// user: 'Your id is 507f160ea. Delete user logic goes here.' -// }) -// // Shutdown App Server -// app.close() -// }) +test('responds to requests', async t => { + t.plan(24) + let res, data, cookie, error + + try { + res = await fetch('http://127.0.0.1:5000/aa') + data = await res.text() + } catch (e) { + error = e + } + t.false(error) + t.equal(res.status, 404) + t.equal(data, 'Not Found') + + // Test GET '/'. Should return index.html in public folder + + try { + res = await fetch('http://127.0.0.1:5000') + data = await res.text() + } catch (e) { + error = e + } + t.false(error) + t.equal(res.status, 200) + t.equal(data, '

hello world

\n') + + // Test POST '/' with {hello: 'world'} + + try { + res = await fetch('http://127.0.0.1:5000', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ hello: 'world' }) + }) + data = await res.json() + } catch (e) { + error = e + } + t.false(error) + t.equal(res.status, 200) + t.deepEqual(data, { hello: 'world' }) + + // Test Session + + try { + res = await fetch('http://127.0.0.1:5000/session') + data = await res.json() + cookie = res.headers.get('set-cookie') + const [name, value] = cookie.split(';')[0].split('=') + const val = signature.unsign(decodeURIComponent(value), 'session') + cookie = { [name]: val } + } catch (e) { + error = e + } + t.false(error) + t.equal(res.status, 200) + t.deepEqual(data, cookie) + + // Test res.redirect + + try { + res = await fetch('http://127.0.0.1:5000/redirect', { + redirect: 'manual', + follow: 0 + }) + data = res.headers.get('Location') + } catch (e) { + error = e + } + t.false(error) + t.equal(res.status, 302) + t.equal(data, 'http://127.0.0.1:5000/wonderland') + + // Test urlencoded + + try { + res = await fetch('http://127.0.0.1:5000/urlencoded', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: 'input1=hello&input2=world&input3=are+you%3F' + }) + data = await res.json() + } catch (e) { + error = e + } + t.false(error) + t.equal(res.status, 200) + t.deepEqual(data, { input1: 'hello', input2: 'world', input3: 'are you?' }) + + // Test multipart form-data + + try { + let form = new FormData() + form.append('input1', 'hello') + form.append('input2', 'world') + form.append('input3', 'are you?') + res = await fetch('http://127.0.0.1:5000/multipartform', { + method: 'POST', + body: form + }) + data = await res.json() + } catch (e) { + error = e + } + t.false(error) + t.equal(res.status, 200) + t.deepEqual(data.fields, { + input1: 'hello', + input2: 'world', + input3: 'are you?' + }) + + // Test res.download + + try { + res = await fetch('http://127.0.0.1:5000/download') + data = res.headers.get('Content-Disposition') + } catch (e) { + error = e + } + t.false(error) + t.equal(res.status, 200) + t.equal(data, 'attachment;filename="app.html"') + + // Shutdown App Server + app.close() +}) From d44802d9db10afd49b6bb16bc0e2d5d67beb374d Mon Sep 17 00:00:00 2001 From: amalshehu Date: Wed, 25 Apr 2018 18:56:40 +0530 Subject: [PATCH 42/42] Fix typos --- lib/handlers/params.mjs | 8 -------- lib/server.mjs | 3 --- 2 files changed, 11 deletions(-) delete mode 100644 lib/handlers/params.mjs diff --git a/lib/handlers/params.mjs b/lib/handlers/params.mjs deleted file mode 100644 index 3d0ff26..0000000 --- a/lib/handlers/params.mjs +++ /dev/null @@ -1,8 +0,0 @@ -export default function() { - let urlSlices = this.url.slice(1).split('/') - this.param = isFinite(urlSlices[1]) ? Number(urlSlices[1]) : urlSlices[1] - var paramIdx = urlSlices.indexOf(urlSlices[1]) - if (paramIdx !== -1) urlSlices.splice(paramIdx, 1) - this.url = `/${urlSlices.join('/')}` - return null -} diff --git a/lib/server.mjs b/lib/server.mjs index c000fd1..5b9406c 100644 --- a/lib/server.mjs +++ b/lib/server.mjs @@ -35,9 +35,6 @@ export default class App { } getRouter() { - // if (!this.routers[path]) { - // let router = this.addRoute(new Router(path)) - // } return this.router }