From 826a8c7a7a36d8ec03a84f3d29498bef58811146 Mon Sep 17 00:00:00 2001 From: Gao Yang Date: Thu, 9 Jan 2020 22:39:23 +0800 Subject: [PATCH] feat: single process invoke and debug (#16) * fix: use single process invoke * feat: single process invoke * fix: new invoke debug --- packages/faas-cli-plugin-fc/package.json | 1 + packages/faas-cli-plugin-fc/src/index.ts | 2 +- packages/faas-cli-plugin-invoke/src/index.ts | 35 +-- packages/faas-cli-plugin-package/package.json | 2 +- packages/serverless-fc-starter/src/index.ts | 1 + .../src/wrapper.ts | 0 .../serverless-fc-trigger/src/apiGateway.ts | 2 + packages/serverless-fc-trigger/src/cdn.ts | 2 + packages/serverless-fc-trigger/src/http.ts | 6 +- packages/serverless-fc-trigger/src/oss.ts | 2 + packages/serverless-fc-trigger/src/sls.ts | 2 + packages/serverless-fc-trigger/src/timer.ts | 1 + packages/serverless-invoke/package.json | 2 + packages/serverless-invoke/src/core.ts | 221 ++++++++++++++++++ packages/serverless-invoke/src/debug.ts | 23 +- packages/serverless-invoke/src/index.ts | 34 +-- packages/serverless-invoke/src/interface.ts | 1 + packages/serverless-invoke/src/invoke.ts | 102 ++++---- packages/serverless-invoke/src/local.ts | 196 ---------------- packages/serverless-invoke/src/main.ts | 150 ++++-------- packages/serverless-invoke/src/utils.ts | 9 +- packages/serverless-invoke/test/core.test.ts | 15 ++ .../test/fixtures/baseApp/src/index.ts | 2 +- packages/serverless-invoke/test/index.test.ts | 4 +- packages/serverless-invoke/test/invoke.js | 10 + .../serverless-midway-plugin/package.json | 5 +- .../test/invokeAliyun/src/index.ts | 2 +- packages/serverless-scf-starter/src/index.ts | 1 + .../serverless-scf-starter/src/wrapper.ts | 40 ++++ 29 files changed, 441 insertions(+), 432 deletions(-) rename packages/{faas-cli-plugin-fc => serverless-fc-starter}/src/wrapper.ts (100%) create mode 100644 packages/serverless-invoke/src/core.ts delete mode 100644 packages/serverless-invoke/src/local.ts create mode 100644 packages/serverless-invoke/test/core.test.ts create mode 100644 packages/serverless-invoke/test/invoke.js create mode 100644 packages/serverless-scf-starter/src/wrapper.ts diff --git a/packages/faas-cli-plugin-fc/package.json b/packages/faas-cli-plugin-fc/package.json index c51e0d76..b0bba8b4 100644 --- a/packages/faas-cli-plugin-fc/package.json +++ b/packages/faas-cli-plugin-fc/package.json @@ -7,6 +7,7 @@ "@alicloud/fun": "^3.1.3", "@midwayjs/fcli-command-core": "^0.2.2", "@midwayjs/serverless-spec-builder": "^0.2.2", + "@midwayjs/serverless-fc-starter": "^0.2.0", "ejs": "^3.0.1" }, "devDependencies": { diff --git a/packages/faas-cli-plugin-fc/src/index.ts b/packages/faas-cli-plugin-fc/src/index.ts index 46275649..2fda489b 100644 --- a/packages/faas-cli-plugin-fc/src/index.ts +++ b/packages/faas-cli-plugin-fc/src/index.ts @@ -6,7 +6,7 @@ import { homedir } from 'os'; import { writeFileSync, existsSync } from 'fs'; import { render } from 'ejs'; import { generateFunctionsSpecFile } from '@midwayjs/serverless-spec-builder/fc'; -import { wrapperContent } from './wrapper'; +import { wrapperContent } from '@midwayjs/serverless-fc-starter'; import { formatLayers } from './utils'; export class AliyunFCPlugin extends BasePlugin { core: ICoreInstance; diff --git a/packages/faas-cli-plugin-invoke/src/index.ts b/packages/faas-cli-plugin-invoke/src/index.ts index 8c07ccba..cb525731 100644 --- a/packages/faas-cli-plugin-invoke/src/index.ts +++ b/packages/faas-cli-plugin-invoke/src/index.ts @@ -44,47 +44,14 @@ export class InvokePlugin extends BasePlugin { async invokeFun(functionName: string) { const allFunctions = this.core.service.functions || {}; const funcConf = allFunctions[functionName]; - const layersList = [{}, this.core.service.layers || {}]; - const providerName = - this.core.service.provider && this.core.service.provider.name; - let eventResult = []; - if (funcConf) { - const events = funcConf.events; - if (Array.isArray(events)) { - let eventKey = []; - for (const evt of events) { - eventKey = eventKey.concat(Object.keys(evt)); - } - eventResult = eventKey; - } - } - - const layers = Object.assign.apply({}, layersList); - - const eventOptions = - this.getEventOptions( - providerName, - this.options.event || this.options.trigger || eventResult[0] - ) || {}; const options = { functionDir: this.core.config.servicePath, functionName, debug: this.options.debug, data: this.options.data || '{}', - handler: funcConf.handler, - layers, - ...eventOptions, + handler: funcConf.handler }; return invoke(options); } - - // 获取触发器及starter配置 - getEventOptions(providerName?: string, eventName?: string) { - return { - starter: '', - eventPath: '', - eventName: '', - }; - } } diff --git a/packages/faas-cli-plugin-package/package.json b/packages/faas-cli-plugin-package/package.json index 48a80a6d..f08cc120 100644 --- a/packages/faas-cli-plugin-package/package.json +++ b/packages/faas-cli-plugin-package/package.json @@ -8,7 +8,7 @@ "archiver": "^3.1.1", "fs-extra": "^8.1.0", "globby": "^10.0.1", - "midway-bin": "^1.15.1" + "midway-bin": "1" }, "files": [ "src", diff --git a/packages/serverless-fc-starter/src/index.ts b/packages/serverless-fc-starter/src/index.ts index 0466cb25..50e6b39e 100644 --- a/packages/serverless-fc-starter/src/index.ts +++ b/packages/serverless-fc-starter/src/index.ts @@ -3,6 +3,7 @@ import { FCRuntime } from './runtime'; export { asyncWrapper } from '@midwayjs/runtime-engine'; export * from './runtime'; +export * from './wrapper'; let bootstrap; diff --git a/packages/faas-cli-plugin-fc/src/wrapper.ts b/packages/serverless-fc-starter/src/wrapper.ts similarity index 100% rename from packages/faas-cli-plugin-fc/src/wrapper.ts rename to packages/serverless-fc-starter/src/wrapper.ts diff --git a/packages/serverless-fc-trigger/src/apiGateway.ts b/packages/serverless-fc-trigger/src/apiGateway.ts index e72ba4f7..3ac84d3f 100644 --- a/packages/serverless-fc-trigger/src/apiGateway.ts +++ b/packages/serverless-fc-trigger/src/apiGateway.ts @@ -22,3 +22,5 @@ export class ApiGatewayTrigger extends FCBaseTrigger { } } + +export const apigw = ApiGatewayTrigger; diff --git a/packages/serverless-fc-trigger/src/cdn.ts b/packages/serverless-fc-trigger/src/cdn.ts index 17b46449..5fc0e4cd 100644 --- a/packages/serverless-fc-trigger/src/cdn.ts +++ b/packages/serverless-fc-trigger/src/cdn.ts @@ -38,3 +38,5 @@ export class CDNTrigger extends FCBaseTrigger { } } + +export const cdn = CDNTrigger; diff --git a/packages/serverless-fc-trigger/src/http.ts b/packages/serverless-fc-trigger/src/http.ts index 9fa77be5..4199f9e2 100644 --- a/packages/serverless-fc-trigger/src/http.ts +++ b/packages/serverless-fc-trigger/src/http.ts @@ -1,6 +1,6 @@ import { exec } from 'child_process'; import * as express from 'express'; -import * as http from 'http'; +import * as HTTP from 'http'; import { FCBaseTrigger } from './base'; interface HTTPTriggerOpts { @@ -44,7 +44,7 @@ export class HTTPTrigger extends FCBaseTrigger { return new Promise((resolve, reject) => { if (!this.httpServer) { const app = express(); - this.httpServer = http.createServer(app); + this.httpServer = HTTP.createServer(app); app.get('*', (req, res, next) => { /** @@ -184,3 +184,5 @@ class Response { }; } } + +export const http = HTTPTrigger; diff --git a/packages/serverless-fc-trigger/src/oss.ts b/packages/serverless-fc-trigger/src/oss.ts index a5fcc531..8719b4f7 100644 --- a/packages/serverless-fc-trigger/src/oss.ts +++ b/packages/serverless-fc-trigger/src/oss.ts @@ -49,3 +49,5 @@ export class OSSTrigger extends FCBaseTrigger { } } + +export const oss = OSSTrigger; diff --git a/packages/serverless-fc-trigger/src/sls.ts b/packages/serverless-fc-trigger/src/sls.ts index 5e1b71a4..5221ec52 100644 --- a/packages/serverless-fc-trigger/src/sls.ts +++ b/packages/serverless-fc-trigger/src/sls.ts @@ -27,3 +27,5 @@ export class SLSTrigger extends FCBaseTrigger { } } + +export const sls = SLSTrigger; diff --git a/packages/serverless-fc-trigger/src/timer.ts b/packages/serverless-fc-trigger/src/timer.ts index a7c18f62..3c49b0d6 100644 --- a/packages/serverless-fc-trigger/src/timer.ts +++ b/packages/serverless-fc-trigger/src/timer.ts @@ -17,3 +17,4 @@ export class TimerTrigger extends FCBaseTrigger { } } +export const timer = TimerTrigger; diff --git a/packages/serverless-invoke/package.json b/packages/serverless-invoke/package.json index f1147e41..55a77855 100644 --- a/packages/serverless-invoke/package.json +++ b/packages/serverless-invoke/package.json @@ -6,10 +6,12 @@ "dependencies": { "@midwayjs/fcli-command-core": "^0.2.2", "@midwayjs/runtime-mock": "^0.2.0", + "@midwayjs/runtime-engine": "^0.2.0", "@midwayjs/serverless-fc-starter": "^0.2.0", "@midwayjs/serverless-fc-trigger": "^0.2.0", "@midwayjs/serverless-scf-starter": "^0.2.0", "@midwayjs/serverless-scf-trigger": "^0.2.0", + "fs-extra": "^8.1.0", "ts-node": "^8.5.2", "urllib": "^2.34.1", "websocket": "^1.0.30" diff --git a/packages/serverless-invoke/src/core.ts b/packages/serverless-invoke/src/core.ts new file mode 100644 index 00000000..a90450cf --- /dev/null +++ b/packages/serverless-invoke/src/core.ts @@ -0,0 +1,221 @@ +/* + 单进程模式的invoke + invoke -> (trigger)-> invokeCore -> entrence -> userCode[ts build] + 1. 用户调用invoke + 2. tsc编译用户代码到dist目录 + 3. 开源版: 【创建runtime、创建trigger】封装为平台invoke包,提供getInvoke方法,会传入args与入口方法,返回invoke方法 +*/ +import { FaaSStarterClass } from './utils'; +import { execSync } from 'child_process'; +import { resolve } from 'path'; +import { existsSync, writeFileSync, ensureDirSync } from 'fs-extra'; +import { loadSpec } from '@midwayjs/fcli-command-core'; +import { render } from 'ejs'; + +interface InvokeOptions { + baseDir?: string; // 目录,默认为process.cwd + functionName: string; // 函数名 + isDebug?: boolean; // 是否debug + handler?: string; // 函数的handler方法 + trigger?: string; // 触发器 + buildDir?: string; // 构建目录 +} + +export class InvokeCore { + options: InvokeOptions; + baseDir: string; + starter: any; + spec: any; + buildDir: string; + wrapperInfo: any; + + constructor(options: InvokeOptions) { + this.options = options; + this.baseDir = options.baseDir || process.cwd(); + this.buildDir = resolve(this.baseDir, options.buildDir || 'dist'); + ensureDirSync(this.buildDir); + this.spec = loadSpec(this.baseDir); + } + + async getStarter() { + if (this.starter) { + return this.starter; + } + const { functionName } = this.options; + const starter = new FaaSStarterClass({ + baseDir: this.buildDir, + functionName + }); + await starter.start(); + this.starter = starter; + return this.starter; + } + + // 获取用户代码中的函数方法 + async getUserFaasHandlerFunction() { + const handler = this.options.handler || this.getFunctionInfo().handler || ''; + const starter = await this.getStarter(); + return starter.handleInvokeWrapper(handler); + } + + getFunctionInfo(functionName?: string) { + functionName = functionName || this.options.functionName; + return this.spec && this.spec.functions && this.spec.functions[functionName] || {}; + } + + async getInvokeFunction() { + const invoke = await this.getUserFaasHandlerFunction(); + return invoke; + } + + async buildTS() { + const { baseDir } = this.options; + process.env.MIDWAY_TS_MODE = 'true'; + const tsconfig = resolve(baseDir, 'tsconfig.json'); + // 非ts + if (!existsSync(tsconfig)) { + return; + } + const distTsconfig = resolve(this.buildDir, 'tsconfig.json'); + if (!existsSync(distTsconfig)) { // midway-core 扫描判断isTsMode需要 + writeFileSync(distTsconfig, '{}'); + } + let tsc = 'tsc'; + const tscBuildDir = resolve(this.buildDir, 'src'); + try { + tsc = resolve(require.resolve('typescript'), '../../bin/tsc'); + } catch (e) { + return this.invokeError('need typescript'); + } + try { + await execSync(`cd ${baseDir};${tsc} --inlineSourceMap --outDir ${tscBuildDir} --skipLibCheck --skipDefaultLibCheck`); + } catch (e) { + this.invokeError(e); + } + } + + async invoke(...args: any) { + await this.buildTS(); + const invoke = await this.getInvokeFunction(); + this.checkDebug(); + return invoke(...args); + } + + async invokeError(err) { + console.log('[faas invoke error]'); + console.log(err); + process.exit(1); + } + + async loadHandler(WrapperContent: string) { + const wrapperInfo = await this.makeWrapper(WrapperContent); + const { fileName, handlerName } = wrapperInfo; + this.wrapperInfo = wrapperInfo; + try { + const handler = require(fileName); + return handler[handlerName]; + } catch (e) { + this.invokeError(e); + } + } + + // 写入口 + async makeWrapper(WrapperContent: string) { + const funcInfo = this.getFunctionInfo(); + const [handlerFileName, name] = funcInfo.handler.split('.'); + const funcLayers = funcInfo.layers || []; + const handlers = []; + + // 高密度部署 + if (funcInfo._isAggregation && funcInfo.functions) { + handlers.push({ + name, + handlers: funcInfo._handlers, + }); + } else { + handlers.push({ + name, + handler: funcInfo.handler, + }); + } + + const fileName = resolve(this.buildDir, `${handlerFileName}.js`); + const layers = this.getLayers( + this.spec.layers, + ...funcLayers + ); + const content = render(WrapperContent, { + handlers, + ...layers, + }); + writeFileSync(fileName, content); + return { fileName, handlerName: name }; + } + + // 安装layer + private getLayers(...layersList: any) { + const layerTypeList = this.formatLayers(...layersList); + const layerDeps = []; + const layers = []; + + if (layerTypeList && layerTypeList.npm) { + Object.keys(layerTypeList.npm).forEach((originName: string) => { + const name = 'layer_' + originName; + layerDeps.push({ name, path: layerTypeList.npm[originName] }); + layers.push(name); + }); + } + return { + layerDeps, + layers, + }; + } + + // 格式化layers + formatLayers(...multiLayers: any[]) { + const layerTypeList = { npm: {} }; + multiLayers.forEach((layer: any) => { + Object.keys(layer || {}).forEach(layerName => { + const [type, path] = layer[layerName].path.split(':'); + if (!layerTypeList[type]) { + return; + } + layerTypeList[type][layerName] = path; + }); + }); + return layerTypeList; + } + + wrapperHandler(handler) { + return handler; + } + + checkDebug() { + if (!this.options.isDebug) { + return; + } + // tslint:disable-next-line: no-eval + eval(` + debugger; + /* + + Debug 温馨提示 + + 请点击左侧文件目录中的代码文件进行调试 + + ${this.wrapperInfo ? ` + 函数的入口文件所在: + + ${this.wrapperInfo.fileName} 。 + + 其中 exports.${this.wrapperInfo.handlerName} 方法为函数入口。 + + 请断点至此函数 + 执行至此函数时,会自动生成源代码 sourceMap,方可继续调试。 + + 感谢使用 midway-faas。 + + ` : ''} + */`); + } +} diff --git a/packages/serverless-invoke/src/debug.ts b/packages/serverless-invoke/src/debug.ts index 08982f16..f6a4297d 100644 --- a/packages/serverless-invoke/src/debug.ts +++ b/packages/serverless-invoke/src/debug.ts @@ -1,5 +1,18 @@ -export const faasDebug = faasHandle => { - // 下一步将进入函数 - // faasHandler 为函数的 handle方法 - return faasHandle(); -}; +import { invoke } from './main'; +import { send, waitDebug } from './utils'; +const [parentOptions, debugPort] = process.argv.slice(2); +let options: any = {}; +try { + options = JSON.parse(parentOptions); + delete options.debug; +} catch (e) {} +(async () => { + try { + await waitDebug(debugPort); + const resultData = await invoke(options); + send('faastest', resultData); + } catch (e) { + send('faastest', 'error: ' + e.message); + } + process.exit(); +})(); diff --git a/packages/serverless-invoke/src/index.ts b/packages/serverless-invoke/src/index.ts index a7e0449d..72c98eae 100644 --- a/packages/serverless-invoke/src/index.ts +++ b/packages/serverless-invoke/src/index.ts @@ -1,35 +1,3 @@ import { invoke as InvokeFun } from './main'; -import { InvokeOptions } from './interface'; - export * from './interface'; - -export const defualtProviderEventMap = { - fc: { - starter: require.resolve('@midwayjs/serverless-fc-starter'), - eventPath: require.resolve('@midwayjs/serverless-fc-trigger'), - eventName: { - http: 'HTTPTrigger', - apiGateway: 'ApiGatewayTrigger', - }, - }, - scf: { - starter: require.resolve('@midwayjs/serverless-scf-starter'), - }, -}; - -export const invoke = (options: InvokeOptions) => { - const { provider, trigger } = options; - const providerEventMap = options.providerEventMap || defualtProviderEventMap; - const runtimeMap = providerEventMap[provider] || {}; - - const starter = runtimeMap.starter; - const eventPath = runtimeMap.eventPath; - const eventName = runtimeMap.eventName && runtimeMap.eventName[trigger]; - - return InvokeFun({ - ...options, - starter, - eventPath, - eventName, - }); -}; +export const invoke = InvokeFun; diff --git a/packages/serverless-invoke/src/interface.ts b/packages/serverless-invoke/src/interface.ts index a3c5701b..aeba2466 100644 --- a/packages/serverless-invoke/src/interface.ts +++ b/packages/serverless-invoke/src/interface.ts @@ -23,6 +23,7 @@ export interface InvokeOptions { functionDir?: string; // 函数所在目录 functionName: string; // 函数名 debug?: string; // debug 端口 + isDebug?: boolean; // 是否采用debug data?: any[]; // 函数入参 log?: boolean; // 是否进行console输出 trigger?: string; // 触发器 diff --git a/packages/serverless-invoke/src/invoke.ts b/packages/serverless-invoke/src/invoke.ts index 9909b40b..bc8abda6 100644 --- a/packages/serverless-invoke/src/invoke.ts +++ b/packages/serverless-invoke/src/invoke.ts @@ -1,54 +1,60 @@ -import { send, waitDebug, Debug_Tag } from './utils'; -import { Local } from './local'; +import { InvokeCore } from './core'; +import { createRuntime } from '@midwayjs/runtime-mock'; +import * as FCStarter from '@midwayjs/serverless-fc-starter'; +import * as FCTrigger from '@midwayjs/serverless-fc-trigger'; +import * as SCFStarter from '@midwayjs/serverless-scf-starter'; -(async () => { - const [ - functionName, - argsData, - isDebug, - starter, - eventPath, - eventName, - handler, - layers - ] = process.argv.slice(2); - - if (isDebug) { - await waitDebug(isDebug); - } +export class Invoke extends InvokeCore { + async getInvokeFunction() { + let invoke; + let runtime; + let triggerMap; + const provider = this.spec && this.spec.provider && this.spec.provider.name; + if (provider) { + let handler: any = ''; // todo + if (provider === 'fc' || provider === 'aliyun') { + handler = await this.loadHandler(FCStarter.wrapperContent); + triggerMap = FCTrigger; + } else if (provider === 'scf' || provider === 'tencent') { + handler = await this.loadHandler(SCFStarter.wrapperContent); + } + if (handler) { + runtime = createRuntime({ + handler: this.wrapperHandler(handler) + }); + } + } - let layersObj: any = null; - if (layers) { - try { - layersObj = JSON.parse(layers); - } catch (E) {} + if (runtime) { + invoke = async (...args) => { + const trigger = this.getTrigger(triggerMap, args); + await runtime.start(); + const result = await runtime.invoke(...trigger); + await runtime.close(); + return result; + }; + } + if (!invoke) { + invoke = await this.getUserFaasHandlerFunction(); + } + return invoke; } - try { - const local = new Local({ - functionName, - // trigger: trigger === 'undefined' ? undefined : trigger, - starter, - event: { - path: eventPath, - name: eventName - }, - handler, - layers: layersObj - }); - - const args = argsData ? [].concat(JSON.parse(argsData)) : []; - - if (isDebug) { - args.push(Debug_Tag); + getTrigger(triggerMap, args) { + if (!triggerMap) { + return args; } - - const resultData = await local.invoke.apply(local, args); - send('faastest', resultData); - } catch (e) { - console.log(e); - send('faastest', 'error: ' + e.message); + let triggerName = this.options.trigger; + if (!triggerName) { + const funcInfo = this.getFunctionInfo(); + if (funcInfo.events && funcInfo.events.length) { + triggerName = Object.keys(funcInfo.events[0])[0]; + } + } + const EventClass = triggerMap[triggerName]; + if (EventClass) { + return [new EventClass(...args)]; + } + return args; } - // 结束进程 - process.exit(); -})(); +} diff --git a/packages/serverless-invoke/src/local.ts b/packages/serverless-invoke/src/local.ts deleted file mode 100644 index 91df20e6..00000000 --- a/packages/serverless-invoke/src/local.ts +++ /dev/null @@ -1,196 +0,0 @@ -import { join } from 'path'; -import { IFaaSStarter } from './interface'; -import { FaaSStarterClass, Debug_Tag } from './utils'; -import { createRuntime } from '@midwayjs/runtime-mock'; -import { faasDebug } from './debug'; -import { loadSpec } from '@midwayjs/fcli-command-core'; - -const extensionsReg = { - npm: /npm:\s*(.*?)\s*$/i, - local: /local:\s*(.*?)\s*$/i, -}; - -export class Local { - ctx = {}; - opts; - baseDir: string; - starter: IFaaSStarter; - functionName: string; - isReady: boolean; - isContextReady: boolean; - handler; - choseEvent: string; - logger; - yml: any; - extensions: any; - layers: any; - - constructor(opts: { - baseDir?: string; - typescript?: boolean; - functionName: string; - logger?; - starter?: string; - event?: { - path: string; - name: string; - }; - handler?: string; - layers?: any; - runtime?: string; - }) { - this.opts = opts; - this.baseDir = opts.baseDir = opts.baseDir || process.cwd(); - this.functionName = opts.functionName; - this.logger = opts.logger || console; - this.handler = this.opts.handler; - this.layers = this.opts.layers; - } - - async ready() { - if (!this.isReady) { - this.prepare(); - const preloadModules = []; - this.starter = new FaaSStarterClass( - Object.assign( - { - preloadModules, - }, - this.opts - ) - ); - await this.starter.start(); - this.isReady = true; - } - } - - prepare() { - if (!this.handler || !this.layers) { - this.getSpecInfo(this.functionName); - } - - this.extensions = []; - if (this.layers) { - Object.keys(this.layers).forEach(extName => { - const extInfo = this.layers[extName]; - if (!extInfo || !extInfo.path) { - return; - } - let extPath = ''; - if (extensionsReg.npm.test(extInfo.path)) { - extPath = extensionsReg.npm.exec(extInfo.path)[1]; - } else if (extensionsReg.local.test(extInfo.path)) { - extPath = join( - this.baseDir, - extensionsReg.local.exec(extInfo.path)[1] - ); - } - if (extPath) { - try { - const ext = require(extPath); - this.extensions.push(ext); - } catch (e) { - console.log(`layer ${extName} load error`, e.message); - } - } - }); - } - - if (!this.handler) { - throw new Error(`We can\'t found handler info in yml`); - } - } - - async invoke(...args) { - await this.ready(); - this.createAnonymousContext(); - let isDebug = false; - if (args && args[args.length - 1] === Debug_Tag) { - isDebug = true; - args.pop(); - } - this.logger.info(`Serverless: invoke args = ${JSON.stringify(args)}`); - const wrapFun = this.starter.handleInvokeWrapper(this.handler, isDebug); - - let runtime = null; - let trigger = []; - - const innerFun = async (...args) => { - if (isDebug) { - const handler = await wrapFun(...args); - return faasDebug(handler); - } - return wrapFun(...args); - }; - - let handler = null; - let othRuntime = null; - if (this.opts.starter) { - try { - const starter = require(this.opts.starter); - handler = starter.asyncWrapper(async (...args) => { - const innerRuntime = await starter.start({}); - return innerRuntime.asyncEvent(innerFun)(...args); - }); - } catch (e) {} - } - - if (this.opts.runtime) { - try { - const runtimeClass = require(this.opts.runtime); - othRuntime = new runtimeClass(); - } catch (e) {} - } - - if (handler || othRuntime) { - runtime = createRuntime({ - handler, - runtime: othRuntime, - layers: this.extensions || [], - }); - } - if (this.opts.event) { - try { - const EventModule = require(this.opts.event.path); - const EventClass = EventModule[this.opts.event.name]; - trigger = [new EventClass(...args)]; - } catch (E) {} - } - - if (!runtime) { - console.warn(`runtime is not supported, use default invoke`); - return innerFun(...args); - } - - await runtime.start(); - const result = await runtime.invoke(...trigger); - await runtime.close(); - return result; - } - - private createAnonymousContext() { - if (!this.isContextReady) { - this.isContextReady = true; - } - } - - private getSpecInfo(functionName: string) { - const specResult = loadSpec(this.baseDir); - const layersList = [{}, specResult.layers || {}]; - let handler; - if ( - specResult && - specResult.functions && - specResult.functions[functionName] - ) { - const funData = specResult.functions[functionName]; - handler = funData.handler; - if (funData.runtimeExtensions) { - layersList.push(funData.layers); - } - } - - this.handler = handler; - this.layers = Object.assign.apply({}, layersList); - } -} diff --git a/packages/serverless-invoke/src/main.ts b/packages/serverless-invoke/src/main.ts index 2761abf9..c1ef8256 100644 --- a/packages/serverless-invoke/src/main.ts +++ b/packages/serverless-invoke/src/main.ts @@ -1,117 +1,69 @@ -import { join } from 'path'; +import { Invoke } from './invoke'; +import { InvokeOptions } from './interface'; import { fork } from 'child_process'; import { get, getWssUrl } from './utils'; -import { InvokeOptions } from './interface'; - -const getTsPath = () => { - try { - return join(require.resolve('ts-node'), '../../register'); - } catch (err) { - return join(__dirname, '../../../node_modules/ts-node/register'); +export const invoke = async (options: InvokeOptions) => { + if (!options.data || !options.data.length) { + options.data = [{}]; } -}; -const makeDebugPort = debug => { - if (debug) { - if (/^\d+$/.test(debug + '')) { - return debug + ''; + if (options.debug) { + if (typeof options.debug !== 'string' || !/^\d+^/.test(options.debug)) { + options.debug = '9229'; } - return '9229'; - } - return ''; -}; - -export const invoke = (options: InvokeOptions) => { - const { - functionName, - debug, - data, - log, - functionDir, - starter, - eventPath, - eventName, - layers, - handler, - midwayModuleName, - debugCb, - } = options; - - process.env.TS_NODE_FILES = 'true'; - process.env.TS_NODE_TYPE_CHECK = 'false'; - process.env.TS_NODE_TRANSPILE_ONLY = 'true'; - - return new Promise((resolve, reject) => { - const execArgv = ['-r', getTsPath()]; - - const debugPort = makeDebugPort(debug); - if (debugPort) { - execArgv.unshift('--inspect=' + debugPort); - } - - let arg = data; - try { - if (typeof arg === 'string') { - arg = JSON.parse(arg); - } - } catch (e) {} - + options.isDebug = true; const child = fork( - join(__dirname, './invoke'), + require.resolve('./debug'), [ - functionName, - JSON.stringify([].concat(arg || [])), - debugPort, - starter || '', - eventPath || '', - eventName || '', - handler || '', - layers ? JSON.stringify(layers) : '', + JSON.stringify(options), + options.debug ], { - cwd: functionDir || process.env.PWD, - env: { - MidwayModuleName: midwayModuleName || '', - ...process.env, - }, - silent: true, - execArgv, + cwd: options.functionDir || process.env.PWD, + execArgv: [ + '--inspect=' + options.debug + ], + silent: true } ); - - if (debugPort) { - getWssUrl(debugPort, 'devtoolsFrontendUrl', true).then(debugUrl => { - console.log('[local invoke] debug at 127.0.0.1:' + debugPort); - console.log('[local invoke] devtools at ' + debugUrl); - - if (debugCb) { - debugCb({ - port: debugPort, - info: debugUrl, - }); - } - }); - } - - get(child, 'faastest').then(data => { - resolve(data); - }); - - let err = ''; - child.stdout.on('data', buf => { - if (log || log === undefined) { - console.log('[local invoke log]', buf.toString()); + getWssUrl(options.debug, 'devtoolsFrontendUrl', true).then(debugUrl => { + console.log('[local invoke] debug at 127.0.0.1:' + options.debug); + console.log('[local invoke] devtools at ' + debugUrl); + if (options.debugCb) { + options.debugCb({ + port: options.debug, + info: debugUrl, + }); } }); - child.stderr.on('data', buf => { - err += buf.toString(); + return new Promise((resolve, reject) => { + get(child, 'faastest').then(data => { + child.kill(); + resolve(data); + }); + let err = ''; + child.stdout.on('data', buf => { + console.log('[local invoke log]', buf.toString()); + }); + child.stderr.on('data', buf => { + err += buf.toString(); + }); + child.on('close', () => { + if (err) { + reject(err); + } + }); }); + } - child.on('close', () => { - if (err) { - reject(err); - } - }); + const invokeFun = new Invoke({ + baseDir: options.functionDir, + functionName: options.functionName, + handler: options.handler, + trigger: options.trigger, + isDebug: options.isDebug }); + + return invokeFun.invoke([].concat(options.data)); }; diff --git a/packages/serverless-invoke/src/utils.ts b/packages/serverless-invoke/src/utils.ts index 47a43038..ea71078b 100644 --- a/packages/serverless-invoke/src/utils.ts +++ b/packages/serverless-invoke/src/utils.ts @@ -58,7 +58,8 @@ export function getWssUrl(port, type?: string, isThrowErr?: boolean) { } } else { const debugInfo = JSON.parse(data.toString()); - resolve(debugInfo[0][type || 'webSocketDebuggerUrl']); + const url = debugInfo[0][type || 'webSocketDebuggerUrl'] || ''; + resolve(url.replace('js_app.html?experiments=true&', 'inspector.html?')); } }); }, 300); @@ -105,12 +106,6 @@ function debugWs(addr) { send('Runtime.enable'); send('Debugger.enable', { maxScriptsCacheSize: 10000000 }); send('Debugger.setBlackboxPatterns', { patterns: ['internal'] }); - send('Debugger.setBreakpointsActive', { active: true }); - send('Debugger.setBreakpointByUrl', { - lineNumber: 3, - columnNumber: 0, - urlRegex: 'invoke/debug\.js' - }); resolve(send); }); client.connect(addr); diff --git a/packages/serverless-invoke/test/core.test.ts b/packages/serverless-invoke/test/core.test.ts new file mode 100644 index 00000000..b042b862 --- /dev/null +++ b/packages/serverless-invoke/test/core.test.ts @@ -0,0 +1,15 @@ +import { InvokeCore } from '../src/core'; +import { join } from 'path'; +import * as assert from 'assert'; +describe('/test/core.test.ts', () => { + it('single process invoke', async () => { + const invokeCore = new InvokeCore({ + functionName: 'http', + handler: 'http.handler', + baseDir: join(__dirname, 'fixtures/baseApp'), + }); + + const data = await invokeCore.invoke({}); + assert(data && /hello/.test(data)); + }); +}); diff --git a/packages/serverless-invoke/test/fixtures/baseApp/src/index.ts b/packages/serverless-invoke/test/fixtures/baseApp/src/index.ts index 0df90b61..fc6f501d 100644 --- a/packages/serverless-invoke/test/fixtures/baseApp/src/index.ts +++ b/packages/serverless-invoke/test/fixtures/baseApp/src/index.ts @@ -1,4 +1,4 @@ -import { FaaSContext, func, inject, provide } from '../../../../../faas'; +import { FaaSContext, func, inject, provide } from '@midwayjs/faas'; @provide() @func('http.handler') diff --git a/packages/serverless-invoke/test/index.test.ts b/packages/serverless-invoke/test/index.test.ts index d65d96b5..65fda912 100644 --- a/packages/serverless-invoke/test/index.test.ts +++ b/packages/serverless-invoke/test/index.test.ts @@ -4,11 +4,9 @@ import * as assert from 'assert'; describe('/test/index.test.ts', () => { it('should use origin http trigger', async () => { const result: any = await invoke({ - provider: 'fc', - trigger: 'http', functionDir: join(__dirname, 'fixtures/baseApp'), functionName: 'http', - data: [{ name: 'params' }], + data: [{ name: 'params' }] }); assert(result && result.body === 'hello http world'); }); diff --git a/packages/serverless-invoke/test/invoke.js b/packages/serverless-invoke/test/invoke.js new file mode 100644 index 00000000..eb4e8379 --- /dev/null +++ b/packages/serverless-invoke/test/invoke.js @@ -0,0 +1,10 @@ +const { invoke } = require('../'); +const { join } = require('path'); +invoke({ + functionDir: join(__dirname, 'fixtures/baseApp'), + functionName: 'http', + data: [{ name: 'params' }], + debug: '9229' +}).then(result => { + console.log('result', result); +}); \ No newline at end of file diff --git a/packages/serverless-midway-plugin/package.json b/packages/serverless-midway-plugin/package.json index 57af5233..002cf024 100644 --- a/packages/serverless-midway-plugin/package.json +++ b/packages/serverless-midway-plugin/package.json @@ -42,5 +42,8 @@ "serverless-tencent-scf": "^0.1.21", "tar": "^5.0.5" }, - "gitHead": "b67e2753cbdcc91813067ba2a1bb1ce7e85a3dff" + "gitHead": "b67e2753cbdcc91813067ba2a1bb1ce7e85a3dff", + "devDependencies": { + "@midwayjs/serverless-fc-starter": "^0.2.0" + } } diff --git a/packages/serverless-midway-plugin/test/invokeAliyun/src/index.ts b/packages/serverless-midway-plugin/test/invokeAliyun/src/index.ts index ddaab886..f2dcb2a6 100644 --- a/packages/serverless-midway-plugin/test/invokeAliyun/src/index.ts +++ b/packages/serverless-midway-plugin/test/invokeAliyun/src/index.ts @@ -1,4 +1,4 @@ -import { FaaSContext, func, inject, provide } from '../../../../faas'; +import { FaaSContext, func, inject, provide } from '@midwayjs/faas'; @provide() @func('index.handler') diff --git a/packages/serverless-scf-starter/src/index.ts b/packages/serverless-scf-starter/src/index.ts index 65f0171e..849adb08 100644 --- a/packages/serverless-scf-starter/src/index.ts +++ b/packages/serverless-scf-starter/src/index.ts @@ -3,6 +3,7 @@ import { SCFRuntime } from './runtime'; export * from './runtime'; export * from './interface'; +export * from './wrapper'; export { asyncWrapper } from '@midwayjs/runtime-engine'; let bootstrap; diff --git a/packages/serverless-scf-starter/src/wrapper.ts b/packages/serverless-scf-starter/src/wrapper.ts new file mode 100644 index 00000000..2d5c09ec --- /dev/null +++ b/packages/serverless-scf-starter/src/wrapper.ts @@ -0,0 +1,40 @@ +export const wrapperContent = `const { FaaSStarter } = require('@midwayjs/faas'); +const { asyncWrapper, start } = require('@midwayjs/serverless-scf-starter'); +<% layerDeps.forEach(function(layer){ %>const <%=layer.name%> = require('<%=layer.path%>'); +<% }); %> + +let starter; +let runtime; +let inited = false; + +const initializeMethod = async (config = {}) => { + runtime = await start({ + layers: [<%= layers.join(", ") %>] + }); + starter = new FaaSStarter({ config, baseDir: __dirname }); + await starter.start(); + inited = true; +}; + +exports.initializer = asyncWrapper(async ({config} = {}) => { + await initializeMethod(config); +}); + +<% handlers.forEach(function(handlerData){ %> +exports.<%=handlerData.name%> = asyncWrapper(async (...args) => { + if (!inited) { + await initializeMethod(); + } + <% if (handlerData.handler) { %> + return runtime.asyncEvent(starter.handleInvokeWrapper('<%=handlerData.handler%>'))(...args); + <% } else { %> + return runtime.asyncEvent(async (ctx) => { + <% handlerData.handlers.forEach(function(multiHandler){ %> if (ctx && ctx.path === '<%=multiHandler.path%>') { + return starter.handleInvokeWrapper('<%=multiHandler.handler%>')(ctx); + } else <% }); %>{ + return 'unhandler path'; + } + })(...args); + <% } %> +}); +<% }); %>`;