From fcf81f8f4bb8c4b3c8e8849c68846bdb1df10e59 Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Fri, 15 May 2020 06:36:43 +0300 Subject: [PATCH 1/3] Optimize API execution --- lib/application.js | 2 +- lib/server.js | 39 +++++++++++++++++++-------------------- 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/lib/application.js b/lib/application.js index 7850209..9df5dcf 100644 --- a/lib/application.js +++ b/lib/application.js @@ -63,7 +63,7 @@ class Application extends events.EventEmitter { runScript(methodName, sandbox = this.sandbox) { const script = this.api.get(methodName); - if (!script) throw new Error('Not found'); + if (!script) return null; return script.runInContext(sandbox, SCRIPT_OPTIONS); } diff --git a/lib/server.js b/lib/server.js index 4de6f3f..25f5567 100644 --- a/lib/server.js +++ b/lib/server.js @@ -81,44 +81,43 @@ class Client { res.end(result); } - async execute(method, args) { - const { application } = this; + async rpc(method, args) { + const { application, res, connection } = this; const { semaphore } = application.server; - await semaphore.enter(); + try { + await semaphore.enter(); + } catch { + this.error(504); + return; + } try { const session = await application.auth.restore(this); const sandbox = session ? session.sandbox : undefined; const context = session ? session.context : {}; const exp = application.runScript(method, sandbox); + if (!exp) { + this.error(404); + return; + } const proc = exp(context); if (!session && proc.access !== 'public') { - semaphore.leave(); - throw new Error(`Forbidden: /api/${method}`); + this.error(403, new Error(`Forbidden: /api/${method}`)); + return; } const result = await proc.method(args); if (!session && proc.access === 'public') { const session = application.auth.start(this, result.userId); result.token = session.token; } - return JSON.stringify(result); + const data = JSON.stringify(result); + if (connection) connection.send(data); + else res.end(data); + } catch (err) { + this.error(500, err); } finally { semaphore.leave(); } } - - async rpc(method, args) { - const { res, connection } = this; - try { - const result = await this.execute(method, args); - if (connection) connection.send(result); - else res.end(result); - } catch (err) { - if (err.message === 'Not found') this.error(404); - else if (err.message === 'Semaphore timeout') this.error(504); - else if (err.message.startsWith('Forbidden:')) this.error(403, err); - else this.error(500, err); - } - } } const listener = application => (req, res) => { From 8056cfe7a3f41f49741c8fd5105be408aab1500d Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Fri, 15 May 2020 22:17:12 +0300 Subject: [PATCH 2/3] Pass API execution context to sandbox Closes: https://github.com/HowProgrammingWorks/NodejsStarterKit/issues/114 --- lib/application.js | 8 +++++--- lib/auth.js | 2 +- lib/server.js | 7 ++----- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/lib/application.js b/lib/application.js index 9df5dcf..3034b46 100644 --- a/lib/application.js +++ b/lib/application.js @@ -34,7 +34,8 @@ class Application extends events.EventEmitter { createSandbox() { const introspection = async () => [...this.api.keys()]; - const application = { security, api: { introspection } }; + const context = Object.freeze({}); + const application = { security, api: { introspection }, context }; for (const name of this.namespaces) application[name] = this[name]; const sandbox = { console: this.logger, application, Buffer, api }; sandbox.global = sandbox; @@ -51,7 +52,7 @@ class Application extends events.EventEmitter { const data = await fsp.readFile(fileName, 'utf8'); const code = data.startsWith('({') ? data : `({ access: 'logged', method: ${data.trim().slice(0, -1)} });`; - const src = `'use strict';\ncontext => ${code}`; + const src = `'use strict';\n${code}`; const options = { filename: fileName, lineOffset: -1 }; try { return new vm.Script(src, options); @@ -61,7 +62,8 @@ class Application extends events.EventEmitter { } } - runScript(methodName, sandbox = this.sandbox) { + runScript(methodName, session) { + const sandbox = session ? session.sandbox : this.sandbox; const script = this.api.get(methodName); if (!script) return null; return script.runInContext(sandbox, SCRIPT_OPTIONS); diff --git a/lib/auth.js b/lib/auth.js index 2128aae..f8feab3 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -90,7 +90,7 @@ module.exports = application => { this.token = token; this.sandbox = sandbox; this.data = contextData; - this.context = new Proxy(contextData, contextHandler); + sandbox.context = new Proxy(contextData, contextHandler); } } diff --git a/lib/server.js b/lib/server.js index 25f5567..47b8b7a 100644 --- a/lib/server.js +++ b/lib/server.js @@ -92,14 +92,11 @@ class Client { } try { const session = await application.auth.restore(this); - const sandbox = session ? session.sandbox : undefined; - const context = session ? session.context : {}; - const exp = application.runScript(method, sandbox); - if (!exp) { + const proc = application.runScript(method, session); + if (!proc) { this.error(404); return; } - const proc = exp(context); if (!session && proc.access !== 'public') { this.error(403, new Error(`Forbidden: /api/${method}`)); return; From 57e7845300e2bf1fa01070df07902173dd69a961 Mon Sep 17 00:00:00 2001 From: Timur Shemsedinov Date: Fri, 15 May 2020 23:22:40 +0300 Subject: [PATCH 3/3] Optimize args and refs --- lib/application.js | 2 +- lib/auth.js | 3 +-- lib/server.js | 8 ++++---- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/application.js b/lib/application.js index 3034b46..91c894c 100644 --- a/lib/application.js +++ b/lib/application.js @@ -63,7 +63,7 @@ class Application extends events.EventEmitter { } runScript(methodName, session) { - const sandbox = session ? session.sandbox : this.sandbox; + const { sandbox } = (session || this); const script = this.api.get(methodName); if (!script) return null; return script.runInContext(sandbox, SCRIPT_OPTIONS); diff --git a/lib/auth.js b/lib/auth.js index f8feab3..69e1516 100644 --- a/lib/auth.js +++ b/lib/auth.js @@ -57,8 +57,7 @@ module.exports = application => { const fillPool = () => { const need = SANDBOX_POOL - pool.length; for (let i = 0; i < need; i++) { - const sandbox = application.createSandbox(); - pool.push(sandbox); + pool.push(application.createSandbox()); } }; diff --git a/lib/server.js b/lib/server.js index 47b8b7a..9052425 100644 --- a/lib/server.js +++ b/lib/server.js @@ -55,13 +55,13 @@ class Client { } static() { - const { url } = this.req; + const { req: { url }, res, application } = this; const filePath = url === '/' ? '/index.html' : url; const fileExt = path.extname(filePath).substring(1); const mimeType = MIME_TYPES[fileExt] || MIME_TYPES.html; - this.res.writeHead(200, { 'Content-Type': mimeType }); - const data = this.application.cache.get(filePath); - if (data) this.res.end(data); + res.writeHead(200, { 'Content-Type': mimeType }); + const data = application.cache.get(filePath); + if (data) res.end(data); else this.error(404); }