diff --git a/docs/guide/yml.md b/docs/guide/yml.md index eca6f782..215c9b6a 100644 --- a/docs/guide/yml.md +++ b/docs/guide/yml.md @@ -61,15 +61,6 @@ functions: - GET - POST - -layers: - test: - path: npm:@ali/market-layer@latest - -custom: - customDomain: - domainName: midway-fc.alibaba-inc.com - ``` diff --git a/packages/faas-cli-command-core/src/core.ts b/packages/faas-cli-command-core/src/core.ts index c2527f0b..f09b26d8 100644 --- a/packages/faas-cli-command-core/src/core.ts +++ b/packages/faas-cli-command-core/src/core.ts @@ -24,6 +24,7 @@ export class CommandHookCore implements ICommandHooksCore { private npmPlugin: string[] = []; private loadNpm: any; private preDebugTime: any; + private execId: number = Math.ceil(Math.random() * 1000); store = new Map(); @@ -432,15 +433,51 @@ export class CommandHookCore implements ICommandHooksCore { } debug(...args) { - if (!this.options.options.V && !this.options.options.verbose) { - return; + + const verbose = this.options.options.V || this.options.options.verbose; + if (!verbose) { + return; } + const now = Date.now(); if (!this.preDebugTime) { - this.preDebugTime = now; + this.preDebugTime = now; + } + const { type, path, line } = this.getStackTrace(); + let stack = ''; + if (type) { + if (typeof verbose === 'string' && type !== verbose) { + return; + } + stack = `(${type}:${path}:${line})`; } const diffTime = Number((now - this.preDebugTime) / 1000).toFixed(2); this.preDebugTime = now; - this.getLog().log('[Verbose]', `${diffTime}s`, ...args); + this.getLog().log('[Verbose]', this.execId, `+${diffTime}s`, ...args, stack); + } + + getStackTrace() { + if (!Error.captureStackTrace) { + return {}; + } + const obj: any = {}; + Error.captureStackTrace(obj, this.getStackTrace); + if (!obj.stack || !obj.stack.split) { + return {}; + } + const stackStr = obj.stack.split('\n'); + if (!stackStr || !stackStr[2]) { + return {}; + } + const matchReg = /(?:-plugin-|\/faas-cli-command-)(\w+)\/(.*?):(\d+):\d+/; + if (!matchReg.test(stackStr[2])) { + return {}; + } + const matchResult = matchReg.exec(stackStr[2]); + return { + type: matchResult[1], + path: matchResult[2], + line: matchResult[3] + } } } diff --git a/packages/faas-cli-plugin-invoke/src/invoke.ts b/packages/faas-cli-plugin-invoke/src/invoke.ts index bf528b46..fc7893f5 100644 --- a/packages/faas-cli-plugin-invoke/src/invoke.ts +++ b/packages/faas-cli-plugin-invoke/src/invoke.ts @@ -10,7 +10,7 @@ export interface InvokeOptions { sourceDir?: string; // 一体化目录结构下,函数的目录,比如 src/apis,这个影响到编译 clean?: boolean; // 清理调试目录 incremental?: boolean; // 增量编译 - verbose?: boolean; // 输出更多信息 + verbose?: boolean | string; // 输出更多信息 } export async function invoke (options: InvokeOptions) { diff --git a/packages/faas-cli-plugin-invoke/test/apigw.test.ts b/packages/faas-cli-plugin-invoke/test/apigw.test.ts index 0f7479ea..b938364f 100644 --- a/packages/faas-cli-plugin-invoke/test/apigw.test.ts +++ b/packages/faas-cli-plugin-invoke/test/apigw.test.ts @@ -30,7 +30,7 @@ describe('/test/apigw.test.ts', () => { ] }); const resultBody = JSON.parse(result.body); - assert(resultBody.headers['Content-Type'] === 'text/json' && resultBody.method === 'POST' && resultBody.path === '/test' && resultBody.body.name === 'test' && resultBody.params.id === 'id'); + assert(resultBody.headers['Content-Type'] === 'text/json' && resultBody.query.q === 'testq' && resultBody.method === 'POST' && resultBody.path === '/test' && resultBody.body.name === 'test' && resultBody.params.id === 'id'); }); it('tencent', async () => { diff --git a/packages/faas-cli-plugin-invoke/test/fixtures/http/src/index.ts b/packages/faas-cli-plugin-invoke/test/fixtures/http/src/index.ts index e6eb53d8..b3fc525f 100644 --- a/packages/faas-cli-plugin-invoke/test/fixtures/http/src/index.ts +++ b/packages/faas-cli-plugin-invoke/test/fixtures/http/src/index.ts @@ -12,6 +12,7 @@ export class HelloHttpService { headers: this.ctx.headers, method: this.ctx.method, path: this.ctx.path, + query: this.ctx.query, body: this.ctx.request.body, params: this.ctx.params } diff --git a/packages/faas-cli-plugin-invoke/test/http.test.ts b/packages/faas-cli-plugin-invoke/test/http.test.ts index d480ab60..58a177ab 100644 --- a/packages/faas-cli-plugin-invoke/test/http.test.ts +++ b/packages/faas-cli-plugin-invoke/test/http.test.ts @@ -14,6 +14,9 @@ describe('/test/http.test.ts', () => { headers: { 'Content-Type': 'text/json' }, method: 'POST', path: '/test', + query: { + name: 'q', + }, body: { name: 'test' } @@ -21,6 +24,6 @@ describe('/test/http.test.ts', () => { ] }); const resultBody = JSON.parse(result.body); - assert(resultBody.headers['Content-Type'] === 'text/json' && resultBody.method === 'POST' && resultBody.path === '/test' && resultBody.body.name === 'test'); + assert(resultBody.headers['Content-Type'] === 'text/json' && resultBody.query.name === 'q' && resultBody.method === 'POST' && resultBody.path === '/test' && resultBody.body.name === 'test'); }); }); diff --git a/packages/faas-cli-plugin-invoke/test/index.test.ts b/packages/faas-cli-plugin-invoke/test/index.test.ts index 1f9fb722..596a53ec 100644 --- a/packages/faas-cli-plugin-invoke/test/index.test.ts +++ b/packages/faas-cli-plugin-invoke/test/index.test.ts @@ -47,7 +47,7 @@ describe('/test/index.test.ts', () => { data: [{name: 'params'}], sourceDir: 'src/apis', incremental: false, - verbose: true + verbose: 'invoke' }); assert(result && result.body === 'hello http world'); await remove(join(__dirname, 'fixtures/ice-faas-ts-standard/.faas_debug_tmp')); diff --git a/packages/faas-cli-plugin-invoke/test/multi.test.ts b/packages/faas-cli-plugin-invoke/test/multi.test.ts index 285d633b..13255a97 100644 --- a/packages/faas-cli-plugin-invoke/test/multi.test.ts +++ b/packages/faas-cli-plugin-invoke/test/multi.test.ts @@ -24,7 +24,7 @@ describe('/test/multi.test.ts', () => { headers: { 'Content-Type': 'text/json'}, method: 'GET', path: '/test/xxx', - queries: {name: 123}, + query: {name: 123}, body: { name: 'test' } diff --git a/packages/faas-cli-plugin-package/src/index.ts b/packages/faas-cli-plugin-package/src/index.ts index b05defa8..24515178 100644 --- a/packages/faas-cli-plugin-package/src/index.ts +++ b/packages/faas-cli-plugin-package/src/index.ts @@ -441,6 +441,7 @@ export class PackagePlugin extends BasePlugin { if (!existsSync(pkgJson)) { writeFileSync(pkgJson, '{}'); } + const register = this.options.register ? ` --registry=${this.options.register}` : ''; exec( `${this.options.npm || 'npm'} install ${ options.npmList @@ -448,7 +449,7 @@ export class PackagePlugin extends BasePlugin { : options.production ? '--production' : '' - }`, + }${register}`, { cwd: installDirectory }, err => { if (err) { diff --git a/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/client/ice.config.js b/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/client/ice.config.js index 3982f6eb..c358f34f 100644 --- a/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/client/ice.config.js +++ b/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/client/ice.config.js @@ -19,8 +19,6 @@ module.exports = { { locales: ['zh-cn'], }, - ], - '@ali/ice-plugin-faas', - '@ali/ice-plugin-fake-page', + ] ], }; diff --git a/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/cloud/package.json b/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/cloud/package.json index 1b063632..bf59b314 100644 --- a/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/cloud/package.json +++ b/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/cloud/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "main": "src/index.ts", "dependencies": { - "@ali/midway-faas": "^2.4.14", + "@midwayjs/faas": "*", "request": "^2.88.0", "request-promise": "^4.2.5" }, diff --git a/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/cloud/src/ping.ts b/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/cloud/src/ping.ts index 591b0aa4..0040eb6a 100644 --- a/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/cloud/src/ping.ts +++ b/packages/faas-cli-plugin-package/test/fixtures/midway-faas-one/cloud/src/ping.ts @@ -4,7 +4,7 @@ import { func, inject, FaaSContext, -} from '@ali/midway-faas'; +} from '@midwayjs/faas'; @provide() @func('ping.handler') diff --git a/packages/faas-dev-pack/README.md b/packages/faas-dev-pack/README.md new file mode 100644 index 00000000..0d67d462 --- /dev/null +++ b/packages/faas-dev-pack/README.md @@ -0,0 +1,38 @@ +# Midway FaaS Dev + +本模块用于 faas 代码的本地调试,用于接入现有的前端 DevServer,提供一个统一的中间件调用。 + + +## Usage + +```ts +import { useExpressDevPack } from '@midwayjs/faas-dev-pack'; + +// dev server 代码 +app = express(); + +// 加载中间件 +app.use( + useExpressDevPack({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + }) +); +``` + +## API + +暴露出两个中间件方法,用于上层集成。 + +- `useExpressDevPack(options: DevOptions)` +- `useKoaDevPack(options: DevOptions)` + +DevOptions + +```ts +export interface DevOptions { + functionDir: string; // 本地目录,默认 process.cwd() + sourceDir?: string; // 一体化调用是,需要知道当前的函数目录结构 +} +``` + diff --git a/packages/faas-dev-pack/gateway.json b/packages/faas-dev-pack/gateway.json new file mode 100644 index 00000000..53738dd9 --- /dev/null +++ b/packages/faas-dev-pack/gateway.json @@ -0,0 +1,3 @@ +{ + "http": "@midwayjs/gateway-http" +} diff --git a/packages/faas-dev-pack/package.json b/packages/faas-dev-pack/package.json new file mode 100644 index 00000000..6b89850a --- /dev/null +++ b/packages/faas-dev-pack/package.json @@ -0,0 +1,43 @@ +{ + "name": "@midwayjs/faas-dev-pack", + "version": "0.2.76", + "main": "dist/index", + "typings": "dist/index.d.ts", + "dependencies": { + "@midwayjs/serverless-invoke": "^0.2.76", + "@midwayjs/gateway-core": "^0.2.76", + "@midwayjs/serverless-spec-builder": "^0.2.76", + "@midwayjs/gateway-http": "^0.2.76", + "body-parser": "^1.19.0", + "compose-middleware": "^5.0.1", + "koa-bodyparser": "^4.2.1", + "koa-compose": "^4.1.0" + }, + "devDependencies": { + "@midwayjs/faas": "^0.2.76", + "@types/express": "^4.17.0", + "@types/koa": "^2.0.49", + "express": "^4.17.1", + "fs-extra": "^8.0.0", + "koa": "^2.11.0", + "midway-bin": "2", + "supertest": "^4.0.2" + }, + "files": [ + "dist", + "src", + "gateway.json" + ], + "scripts": { + "build": "midway-bin build -c", + "test": "NODE_ENV=test midway-bin test --ts --full-trace", + "debug": "NODE_ENV=test midway-bin test --ts --full-trace --inspect-brk=9229", + "cov": "NODE_ENV=unittest midway-bin cov --ts", + "clean": "midway-bin clean", + "autod": "midway-bin autod" + }, + "license": "MIT", + "publishConfig": { + "registry": "http://registry.npm.alibaba-inc.com" + } +} diff --git a/packages/faas-dev-pack/src/common.ts b/packages/faas-dev-pack/src/common.ts new file mode 100644 index 00000000..de369e68 --- /dev/null +++ b/packages/faas-dev-pack/src/common.ts @@ -0,0 +1,50 @@ +import { transform } from '@midwayjs/serverless-spec-builder'; +import { join } from 'path'; +import { invoke } from '@midwayjs/serverless-invoke'; + +const matchTrigger = ( + config: any, + functionName: string, + triggerType: string +): boolean => { + // 拿到函数名,通用 mtop 的情况 + if (config.functions?.[functionName]?.events?.[0]?.[triggerType]) { + return true; + } + + return false; +}; + +export async function getGatewayFromConfig( + baseDir: string, + sourceDir: string, + req +) { + const config: any = transform(join(baseDir, 'f.yml')); + const gatewayType = config?.apiGateway?.type; + if (gatewayType) { + // mtop 自定义网关 + return gatewayType; + } else { + const functionName = req.query['functionName']; + // 拿到函数名,通用 mtop 的情况 + if (functionName && matchTrigger(config, functionName, 'mtop')) { + return 'mtop'; + } + + return 'http'; + } +} + +export function resolveModule(gatewayName: string) { + const gatewayJSON = require('../gateway.json'); + if (gatewayJSON[gatewayName]) { + return require(gatewayJSON[gatewayName]); + } else { + throw new Error(`unsupport gateway type ${gatewayName}`); + } +} + +export async function invokeFunction(options) { + return invoke(options); +} diff --git a/packages/faas-dev-pack/src/index.ts b/packages/faas-dev-pack/src/index.ts new file mode 100644 index 00000000..e8eb0e22 --- /dev/null +++ b/packages/faas-dev-pack/src/index.ts @@ -0,0 +1,64 @@ +import { getGatewayFromConfig, resolveModule, invokeFunction } from './common'; +import { + DevPackOptions, + CreateExpressGateway, + CreateKoaGateway, +} from '@midwayjs/gateway-core'; +import { NextFunction, Request, Response } from 'express'; +import { Context } from 'koa'; +import * as koaBodyParser from 'koa-bodyparser'; +import * as expressBodyParser from 'body-parser'; +import * as compose from 'koa-compose'; +import { compose as expressCompose } from 'compose-middleware'; + +export function useExpressDevPack(options: DevPackOptions) { + options.functionDir = options.functionDir || process.cwd(); + + return expressCompose([ + expressBodyParser.urlencoded({ extended: false }), + expressBodyParser.json(), + async (req: Request, res: Response, next: NextFunction) => { + const gatewayName = await getGatewayFromConfig( + options.functionDir, + options.sourceDir, + req + ); + if (!gatewayName) { + return next(); + } + const createExpressGateway: CreateExpressGateway = resolveModule( + gatewayName + ).createExpressGateway; + options.originGatewayName = gatewayName; + const gateway = createExpressGateway(options); + gateway.transform(req, res, next, invokeFunction); + }, + ]); +} + +export function useKoaDevPack(options: DevPackOptions) { + options.functionDir = options.functionDir || process.cwd(); + + return compose([ + koaBodyParser({ + enableTypes: ['form', 'json'], + }), + async (ctx: Context, next: () => Promise) => { + const gatewayName = await getGatewayFromConfig( + options.functionDir, + options.sourceDir, + ctx.request + ); + if (!gatewayName) { + await next(); + } else { + const createKoaGateway: CreateKoaGateway = resolveModule(gatewayName) + .createKoaGateway; + + options.originGatewayName = gatewayName; + const gateway = createKoaGateway(options); + await gateway.transform(ctx, next, invokeFunction); + } + }, + ]); +} diff --git a/packages/faas-dev-pack/src/interface.ts b/packages/faas-dev-pack/src/interface.ts new file mode 100644 index 00000000..b75d8824 --- /dev/null +++ b/packages/faas-dev-pack/src/interface.ts @@ -0,0 +1,7 @@ +export interface InvokeParams { + functionName: string; + data?: any; + contextData?: string; + eventType?: string; + verbose?: boolean; +} diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/README.md b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/README.md new file mode 100644 index 00000000..8e48ee62 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/README.md @@ -0,0 +1,21 @@ +# ice-typescript-starter + +## 使用 + +- 启动调试服务: `npm start` +- 构建 dist: `npm run build` + +## 目录结构 + +- react-router @4.x 默认采用 hashHistory 的单页应用 +- 入口文件: `src/index.js` +- 导航配置: `src/menuConfig.js` +- 路由配置: `src/routerConfig.js` +- 路由入口: `src/router.jsx` +- 布局文件: `src/layouts` +- 通用组件: `src/components` +- 页面文件: `src/pages` + +## 效果图 + +![screenshot](https://img.alicdn.com/tfs/TB13AFlH6TpK1RjSZKPXXa3UpXa-2860-1580.png) diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/f.yml b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/f.yml new file mode 100644 index 00000000..c5cc82f1 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/f.yml @@ -0,0 +1,17 @@ +service: fc-test + +provider: + name: aliyun + runtime: nodejs10 + +functions: + test1: + handler: index.handler + events: + - http: + path: /server/user/info + test2: + handler: test2.handler + events: + - http: + path: /* diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/ice.config.js b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/ice.config.js new file mode 100644 index 00000000..72ccfe18 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/ice.config.js @@ -0,0 +1,11 @@ +module.exports = { + entry: 'src/index.tsx', + plugins: [ + ['ice-plugin-fusion', { + themePackage: '@icedesign/theme', + }], + ['ice-plugin-moment-locales', { + locales: ['zh-cn'], + }], + ], +}; diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/package.json b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/package.json new file mode 100644 index 00000000..9a717988 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/package.json @@ -0,0 +1,38 @@ +{ + "name": "@icedesign/ts-scaffold9ade4c90-5de3-11e9-aef1-79dc0073b61f", + "version": "1.0.3", + "description": "该模板基于 TypeScript 适用于从 0 到 1 开始搭建项目,内置基础的页面,路由和菜单展示", + "dependencies": { + "@alifd/next": "^1.x", + "@icedesign/theme": "^1.x", + "@types/react": "^16.8.3", + "@types/react-dom": "^16.8.2", + "moment": "^2.23.0", + "prop-types": "^15.5.8", + "react": "^16.4.1", + "react-dom": "^16.4.1", + "react-router-dom": "^4.2.2", + "tslib": "^1.9.3", + "@midwayjs/faas": "*" + }, + "devDependencies": { + "babel-eslint": "^8.0.3", + "eslint": "^4.13.1", + "eslint-config-airbnb": "^16.1.0", + "eslint-plugin-babel": "^4.1.1", + "eslint-plugin-import": "^2.8.0", + "eslint-plugin-jsx-a11y": "^6.0.3", + "eslint-plugin-react": "^7.5.1", + "ice-scripts": "^2.0.0", + "ice-plugin-fusion": "^0.1.4", + "ice-plugin-moment-locales": "^0.1.0" + }, + "scripts": { + "start": "ice-scripts dev", + "build": "ice-scripts build", + "lint": "eslint . --ext '.js,.jsx' --fix" + }, + "engines": { + "node": ">=8.0.0" + } +} diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/public/favicon.png b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/public/favicon.png new file mode 100644 index 00000000..a2605c57 Binary files /dev/null and b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/public/favicon.png differ diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/public/index.html b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/public/index.html new file mode 100644 index 00000000..5c069296 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/public/index.html @@ -0,0 +1,13 @@ + + + + + + + ICE TypeScript Starter + + + +
+ + diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/apis/index.ts b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/apis/index.ts new file mode 100644 index 00000000..ebe12cf2 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/apis/index.ts @@ -0,0 +1,23 @@ +import { + provide, + func, + FunctionHandler, + inject, + FaaSContext, +} from '@midwayjs/faas'; + +@provide() +@func('index.handler') +export class IndexHandler implements FunctionHandler { + @inject() + ctx: FaaSContext; + + async handler() { + console.log('this.ctx.req.body', this.ctx.req.body, this.ctx.query); + const name = this.ctx.req.body['name']; + const action = this.ctx.query['action']; + this.ctx.type = 'text/html; charset=utf-8'; + this.ctx.set('x-schema', 'bbb'); + this.ctx.body = `${name},hello http world,${action}`; + } +} diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/apis/test2.ts b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/apis/test2.ts new file mode 100644 index 00000000..52b2551e --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/apis/test2.ts @@ -0,0 +1,18 @@ +import { + provide, + func, + FunctionHandler, + inject, + FaaSContext, +} from '@midwayjs/faas'; + +@provide() +@func('test2.handler') +export class Test2Handler implements FunctionHandler { + @inject() + ctx: FaaSContext; + + async handler() { + this.ctx.body = { data: 'test2' }; + } +} diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/components/Greeting/index.tsx b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/components/Greeting/index.tsx new file mode 100644 index 00000000..b9369ed6 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/components/Greeting/index.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +export interface Props { + name: string; +} + +const Greeting = ({ name }: Props) => { + return ( +
+ Hello, {name} +
+ ); +}; + +export default Greeting; diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/components/Guide/index.tsx b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/components/Guide/index.tsx new file mode 100644 index 00000000..3223e610 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/components/Guide/index.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { Button } from '@alifd/next'; + +const Guide = () => { + return ( +
+

使用指南

+ +
+ + + + + + +
+
+ ); +}; + +const styles = { + item: { + height: '34px', + lineHeight: '34px', + }, +}; + +export default Guide; diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/index.tsx b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/index.tsx new file mode 100644 index 00000000..0290d245 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/index.tsx @@ -0,0 +1,14 @@ +import ReactDOM from 'react-dom'; + +// 载入默认全局样式 normalize +import '@alifd/next/reset.scss'; + +import router from './router'; + +const ICE_CONTAINER = document.getElementById('ice-container'); + +if (!ICE_CONTAINER) { + throw new Error('当前页面不存在
节点.'); +} + +ReactDOM.render(router(), ICE_CONTAINER); diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/MainRoutes.tsx b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/MainRoutes.tsx new file mode 100644 index 00000000..632a63b6 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/MainRoutes.tsx @@ -0,0 +1,37 @@ +import React, { Component } from 'react'; +import { Redirect, Switch, Route } from 'react-router-dom'; +import routerConfig from '../../routerConfig'; +import Guide from '../../components/Guide'; + +class MainRoutes extends Component { + /** + * 渲染路由组件 + */ + renderNormalRoute = (item, index) => { + return item.component ? ( + + ) : null; + }; + + render() { + return ( + + {/* 渲染路由表 */} + {routerConfig.map(this.renderNormalRoute)} + + {/* 首页默认重定向到 /dashboard */} + + + {/* 未匹配到的路由重定向到 组件,实际情况应该重定向到 404 */} + + + ); + } +} + +export default MainRoutes; diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/index.tsx b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/index.tsx new file mode 100644 index 00000000..c1d9cf71 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/index.tsx @@ -0,0 +1,12 @@ +import React, { Component } from 'react'; +import MainRoutes from './MainRoutes'; + +export default class BasicLayout extends Component { + render() { + return ( +
+ +
+ ); + } +} diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/menuConfig.ts b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/menuConfig.ts new file mode 100644 index 00000000..e3c4c9e3 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/menuConfig.ts @@ -0,0 +1,5 @@ +// 菜单配置 + +const asideMenuConfig = []; + +export { asideMenuConfig }; diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/pages/Dashboard/index.tsx b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/pages/Dashboard/index.tsx new file mode 100644 index 00000000..68adb830 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/pages/Dashboard/index.tsx @@ -0,0 +1,14 @@ +import React, { Component } from 'react'; +import Guide from '../../components/Guide'; +import Greeting from '../../components/Greeting'; + +export default class Dashboard extends Component { + render() { + return ( +
+ + +
+ ); + } +} diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/router.tsx b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/router.tsx new file mode 100644 index 00000000..f64d2d80 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/router.tsx @@ -0,0 +1,18 @@ +/** + * 定义应用路由 + */ +import { HashRouter, Switch, Route } from 'react-router-dom'; +import React from 'react'; +import BasicLayout from './layouts/BasicLayout'; + +const router = () => { + return ( + + + + + + ); +}; + +export default router; diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/routerConfig.ts b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/routerConfig.ts new file mode 100644 index 00000000..3ce552f7 --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/src/routerConfig.ts @@ -0,0 +1,13 @@ +// 以下文件格式为描述路由的协议格式 +// 你可以调整 routerConfig 里的内容 +// 变量名 routerConfig 为 iceworks 检测关键字,请不要修改名称 +import Dashboard from './pages/Dashboard'; + +const routerConfig = [ + { + path: '/dashboard', + component: Dashboard, + }, +]; + +export default routerConfig; diff --git a/packages/faas-dev-pack/test/fixtures/ice-demo-repo/tsconfig.json b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/tsconfig.json new file mode 100644 index 00000000..39d09cdc --- /dev/null +++ b/packages/faas-dev-pack/test/fixtures/ice-demo-repo/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compileOnSave": false, + "buildOnSave": false, + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "module": "esnext", + "target": "es6", + "jsx": "react", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "lib": ["es6", "dom"], + "sourceMap": true, + "allowJs": true, + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": false, + "importHelpers": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true, + "skipLibCheck": true + }, + "include": ["src/*"], + "exclude": ["node_modules", "build", "public"] +} diff --git a/packages/faas-dev-pack/test/index.test.ts b/packages/faas-dev-pack/test/index.test.ts new file mode 100644 index 00000000..1a5f8ef7 --- /dev/null +++ b/packages/faas-dev-pack/test/index.test.ts @@ -0,0 +1,115 @@ +import * as koa from 'koa'; +import * as express from 'express'; +import { useExpressDevPack, useKoaDevPack } from '../src'; +import { join } from 'path'; +import * as request from 'supertest'; +import * as assert from 'assert'; +import { remove, pathExists } from 'fs-extra'; + +describe('/test/index.test.ts', () => { + beforeEach(async () => { + const dirs = [ + join(__dirname, './fixtures/ice-demo-repo'), + ]; + for (const dir of dirs) { + if (await pathExists(join(dir, '.faas_debug_tmp'))) { + await remove(join(dir, '.faas_debug_tmp')); + } + } + }); + it('should invoke by http api and koa', done => { + const app = new koa(); + app.use( + useKoaDevPack({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + }) + ); + request(app.callback()) + .post('/server/user/info') + .query({ + action: 'doTest', + }) + .send({ name: 'zhangting' }) + .expect('Content-type', 'text/html; charset=utf-8') + .expect(/zhangting,hello http world,doTest/) + .expect('x-schema', 'bbb') + .expect(200, done); + }); + + it('should invoke by http router /api/*', done => { + const app = new koa(); + app.use( + useKoaDevPack({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + }) + ); + request(app.callback()) + .get('/api/test2') + .expect(/test2/) + .expect(200, done); + }); + + it('should invoke by http api and express', done => { + const app = express(); + app.use( + useExpressDevPack({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + }) + ); + request(app) + .post('/server/user/info') + .query({ + action: 'doTest', + }) + .send({ name: 'zhangting' }) + .expect('Content-type', 'text/html; charset=utf-8') + .expect(/zhangting,hello http world,doTest/) + .expect('x-schema', 'bbb') + .expect(200, done); + }); + + it('should invoke by http api parallel', done => { + const app = express(); + app.use( + useExpressDevPack({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + }) + ); + Promise.all([ + request(app) + .post('/server/user/info') + .query({ + action: 'doTest', + }) + .send({ name: 'one' }) + .expect('Content-type', 'text/html; charset=utf-8') + .expect(200) + .then(response => { + return response.text; + }), + request(app) + .post('/server/user/info') + .query({ + action: 'doTest', + }) + .send({ name: 'two' }) + .expect('Content-type', 'text/html; charset=utf-8') + .expect(200) + .then(response => { + return response.text; + }), + ]) + .then(res => { + assert.deepEqual(res, [ + "one,hello http world,doTest", + "two,hello http world,doTest", + ]); + done(); + }) + .catch(done); + }); +}); diff --git a/packages/faas-dev-pack/tsconfig.json b/packages/faas-dev-pack/tsconfig.json new file mode 100644 index 00000000..0891c56e --- /dev/null +++ b/packages/faas-dev-pack/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "inlineSourceMap":true, + "noImplicitThis": true, + "noUnusedLocals": false, + "stripInternal": true, + "pretty": true, + "declaration": true, + "outDir": "dist", + "resolveJsonModule": true, + "incremental": true + }, + "exclude": [ + "dist", + "node_modules", + "test" + ] +} + diff --git a/packages/gateway-core/README.md b/packages/gateway-core/README.md new file mode 100644 index 00000000..0e51d4cb --- /dev/null +++ b/packages/gateway-core/README.md @@ -0,0 +1,3 @@ +# @midwayjs/gateway-core + +网关通用类库或依赖 diff --git a/packages/gateway-core/package.json b/packages/gateway-core/package.json new file mode 100644 index 00000000..d9e4dd95 --- /dev/null +++ b/packages/gateway-core/package.json @@ -0,0 +1,29 @@ +{ + "name": "@midwayjs/gateway-core", + "version": "0.2.76", + "main": "dist/index", + "typings": "dist/index.d.ts", + "devDependencies": { + "@midwayjs/serverless-invoke": "^0.2.76", + "@types/express": "^4.17.0", + "@types/koa": "^2.0.49", + "body-parser": "^1.19.0", + "express": "^4.17.1", + "koa": "^2.11.0", + "koa-bodyparser": "^4.2.1", + "midway-bin": "2" + }, + "files": [ + "dist", + "src" + ], + "scripts": { + "build": "midway-bin build -c", + "test": "NODE_ENV=test midway-bin test --ts --full-trace", + "debug": "NODE_ENV=test midway-bin test --ts --full-trace --inspect-brk=9229", + "cov": "NODE_ENV=unittest midway-bin cov --ts", + "clean": "midway-bin clean", + "autod": "midway-bin autod" + }, + "license": "MIT" +} diff --git a/packages/gateway-core/src/gatewaySuit.ts b/packages/gateway-core/src/gatewaySuit.ts new file mode 100644 index 00000000..2b9df7cf --- /dev/null +++ b/packages/gateway-core/src/gatewaySuit.ts @@ -0,0 +1,57 @@ +// 这个文件只是为了提供各个网关简单的测试方法,只会在单测中使用,所以 express/koa 依赖都会在 dev 中 +import * as request from 'supertest'; +import * as express from 'express'; +import * as koa from 'koa'; +import { join } from 'path'; +import * as expressBodyParser from 'body-parser'; +import * as koaBodyParser from 'koa-bodyparser'; +import { DevPackOptions } from '.'; +import { invoke } from '@midwayjs/serverless-invoke'; + +export interface GatewaySuitOptions extends Partial { + functionDir?: string; + gatewayDir?: string; + invokeCallback?: () => {}; +} + +export const createKoaSuit = (options: GatewaySuitOptions = {}) => { + if (!options.gatewayDir) { + options.gatewayDir = join(process.cwd(), 'src'); + } + const app = new koa(); + app.use( + koaBodyParser({ + enableTypes: ['form', 'json'], + }) + ); + const { createKoaGateway } = require(options.gatewayDir); + app.use(async (ctx, next) => { + await createKoaGateway(options).transform( + ctx, + next, + options.invokeCallback || invoke + ); + }); + + return request(app.callback()); +}; + +export const createExpressSuit = (options: GatewaySuitOptions = {}) => { + if (!options.gatewayDir) { + options.gatewayDir = join(process.cwd(), 'src'); + } + const app = express(); + app.use(expressBodyParser.urlencoded({ extended: false })); + app.use(expressBodyParser.json()); + const { createExpressGateway } = require(options.gatewayDir); + app.use((req, res, next) => { + createExpressGateway(options).transform( + req, + res, + next, + options.invokeCallback || invoke + ); + }); + + return request(app); +}; diff --git a/packages/gateway-core/src/index.ts b/packages/gateway-core/src/index.ts new file mode 100644 index 00000000..f51fd475 --- /dev/null +++ b/packages/gateway-core/src/index.ts @@ -0,0 +1,4 @@ +export * from './interface'; +export * from './gatewaySuit'; +export { Context, Request as KoaRequest, Response as KoaResponse } from 'koa'; +export { NextFunction, Request, Response } from 'express'; diff --git a/packages/gateway-core/src/interface.ts b/packages/gateway-core/src/interface.ts new file mode 100644 index 00000000..d60772ec --- /dev/null +++ b/packages/gateway-core/src/interface.ts @@ -0,0 +1,35 @@ +import { Context, Request as KoaRequest } from 'koa'; +import { NextFunction, Request, Response } from 'express'; +import { InvokeOptions } from '@midwayjs/serverless-invoke'; + +export { InvokeOptions } from '@midwayjs/serverless-invoke'; + +export type InvokeCallback = (result: InvokeOptions) => Promise; + +export interface ExpressGatewayAdapter { + transform( + req: Request, + res: Response, + next: NextFunction, + invoke: InvokeCallback + ); +} + +export interface KoaGatewayAdapter { + transform(ctx: Context, next: () => Promise, invoke: InvokeCallback); +} + +export type CreateExpressGateway = ( + options?: DevPackOptions +) => ExpressGatewayAdapter; +export type CreateKoaGateway = (options?: DevPackOptions) => KoaGatewayAdapter; + +export interface DevPackOptions { + functionDir: string; // 本地目录,默认 process.cwd() + sourceDir?: string; // 一体化调用时,需要知道当前的函数目录结构 + apiPath?: string | RegExp; // 只有匹配到这个才会执行函数逻辑 + ignorePattern?: string[] | ((req: Request | KoaRequest) => boolean); // 中后台下,特定的路由会忽略交给 webpackServer 去执行,比如 assets 地址 + ignoreWildcardFunctions?: string[]; // 忽略通配的函数名 + originGatewayName?: string; // 配置在 yml 里的 apiGatway 的 type + verbose?: boolean; // 展示更多信息 +} diff --git a/packages/gateway-core/test/index.ts b/packages/gateway-core/test/index.ts new file mode 100644 index 00000000..bdc437a5 --- /dev/null +++ b/packages/gateway-core/test/index.ts @@ -0,0 +1,51 @@ +import { + CreateKoaGateway, + CreateExpressGateway, + ExpressGatewayAdapter, + KoaGatewayAdapter, + InvokeCallback, + DevPackOptions, +} from '../src'; +import { NextFunction, Request, Response } from 'express'; +import { Context } from 'koa'; + +export class KoaGateway implements KoaGatewayAdapter { + options: DevPackOptions; + + constructor(options) { + this.options = options; + } + + async transform( + ctx: Context, + next: () => Promise, + invoke: InvokeCallback + ) { + ctx.body = '123'; + } +} + +export class ExpressGateway implements ExpressGatewayAdapter { + options: DevPackOptions; + + constructor(options) { + this.options = options; + } + + async transform( + req: Request, + res: Response, + next: NextFunction, + invoke: InvokeCallback + ) { + res.send('321'); + } +} + +export const createKoaGateway: CreateKoaGateway = options => { + return new KoaGateway(options); +}; + +export const createExpressGateway: CreateExpressGateway = options => { + return new ExpressGateway(options); +}; diff --git a/packages/gateway-core/test/suit.test.ts b/packages/gateway-core/test/suit.test.ts new file mode 100644 index 00000000..298c30b7 --- /dev/null +++ b/packages/gateway-core/test/suit.test.ts @@ -0,0 +1,21 @@ +import { createExpressSuit, createKoaSuit } from '../src'; + +describe('/test.suit.test.ts', () => { + it('test express', done => { + createExpressSuit({ + gatewayDir: __dirname, + }) + .get('/test') + .expect(/321/) + .expect(200, done); + }); + + it('test koa', done => { + createKoaSuit({ + gatewayDir: __dirname, + }) + .get('/test') + .expect(/123/) + .expect(200, done); + }); +}); diff --git a/packages/gateway-core/tsconfig.json b/packages/gateway-core/tsconfig.json new file mode 100644 index 00000000..af148a08 --- /dev/null +++ b/packages/gateway-core/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "inlineSourceMap":true, + "noImplicitThis": true, + "noUnusedLocals": false, + "stripInternal": true, + "pretty": true, + "declaration": true, + "outDir": "dist" + }, + "exclude": [ + "dist", + "node_modules", + "test" + ] +} diff --git a/packages/gateway-http/README.md b/packages/gateway-http/README.md new file mode 100644 index 00000000..40fb3e0a --- /dev/null +++ b/packages/gateway-http/README.md @@ -0,0 +1,3 @@ +# @midwayjs/gateway-http + +http 网关模拟 diff --git a/packages/gateway-http/package.json b/packages/gateway-http/package.json new file mode 100644 index 00000000..06574a11 --- /dev/null +++ b/packages/gateway-http/package.json @@ -0,0 +1,31 @@ +{ + "name": "@midwayjs/gateway-http", + "version": "0.2.76", + "main": "dist/index", + "typings": "dist/index.d.ts", + "dependencies": { + "@midwayjs/serverless-invoke": "^0.2.76", + "@midwayjs/gateway-core": "^0.2.76", + "@midwayjs/serverless-spec-builder": "^0.2.76", + "picomatch": "^2.2.1" + }, + "devDependencies": { + "@midwayjs/faas": "^0.2.76", + "micromatch": "^4.0.2", + "midway-bin": "2", + "minimatch": "^3.0.4" + }, + "files": [ + "dist", + "src" + ], + "scripts": { + "build": "midway-bin build -c", + "test": "NODE_ENV=test midway-bin test --ts --full-trace", + "debug": "NODE_ENV=test midway-bin test --ts --full-trace --inspect-brk=9229", + "cov": "NODE_ENV=unittest midway-bin cov --ts", + "clean": "midway-bin clean", + "autod": "midway-bin autod" + }, + "license": "MIT" +} diff --git a/packages/gateway-http/src/common.ts b/packages/gateway-http/src/common.ts new file mode 100644 index 00000000..c14606d7 --- /dev/null +++ b/packages/gateway-http/src/common.ts @@ -0,0 +1,124 @@ +import { DevPackOptions, InvokeOptions } from '@midwayjs/gateway-core'; +import { isMatch } from 'picomatch'; +import * as qs from 'querystring'; +import { getFuncList } from '@midwayjs/serverless-invoke'; +const ignoreWildcardFunctionsWhiteList = []; + +export async function parseInvokeOptionsByOriginUrl( + options: DevPackOptions, + req +): Promise> { + const ignorePattern = options.ignorePattern; + const currentUrl = req.path || req.url; + if (ignorePattern) { + if (typeof ignorePattern === 'function') { + if (ignorePattern(req)) { + return {}; + } + } else if (ignorePattern.length) { + for (const pattern of ignorePattern as string[]) { + if (new RegExp(pattern).test(currentUrl)) { + return {}; + } + } + } + } + if (currentUrl === '/favicon.ico') { + return {}; + } + const invokeOptions: Partial = {}; + invokeOptions.functionDir = options.functionDir; + invokeOptions.sourceDir = options.sourceDir; + invokeOptions.verbose = options.verbose; + const functions = await getFuncList({ + functionDir: options.functionDir, + sourceDir: options.sourceDir, + }); + const invokeHTTPData: Partial<{ + headers: object; + body: string; + method: string; + path: string; + query: object; + base64Encoded: boolean; + }> = {}; + // 获取路由 + let urlMatchList = []; + Object.keys(functions).forEach((functionName) => { + const functionItem = functions[functionName] || {}; + const event = (functionItem.events || []).find((eventItem: any) => { + return eventItem.http; + }); + if (event?.http) { + urlMatchList.push({ + functionName, + originRouter: event?.http?.path || '/*', + router: event?.http?.path?.replace(/\/\*$/, '/**') || '/**', + }); + } + }); + // https://yuque.antfin-inc.com/cse/docs/custom-domain-name + // 1. 绝对路径规则优先级最高如 /ab/cb/e + // 2. 星号只能出现最后且必须在/后面,如 /ab/cb/** + // 3. 如果绝对路径和通配都能匹配一个路径时,绝对规则优先级高 + // 4. 有多个通配能匹配一个路径时,最长的规则匹配,如 /ab/** 和 /ab/cd/** 在匹配 /ab/cd/f 时命中 /ab/cd/** + urlMatchList = urlMatchList + .map((item) => { + const router = item.router.replace(/\**$/, ''); + return { + functionName: item.functionName, + router: item.router, + originRouter: item.originRouter, + level: router.split('/').length - 1, + }; + }) + .sort((handlerA, handlerB) => { + return handlerB.level - handlerA.level; + }); + + const functionItem = urlMatchList.find((item) => { + if (isMatch(currentUrl, item.router)) { + // 如果不在白名单内,并且是需要被忽略的函数,则跳过函数处理 + if ( + !ignoreWildcardFunctionsWhiteList.includes(currentUrl) && + options.ignoreWildcardFunctions?.includes(item.functionName) + ) { + // 中后台 webpack 的特殊处理,忽略特定函数的通配逻辑 + return currentUrl.indexOf(item.originRouter) !== -1; + } + console.log( + `Info: find url "${currentUrl}" match pattern "${item.router}", functionName="${item.functionName}"` + ); + return true; + } + }); + + if (functionItem?.functionName) { + // 匹配到了函数 + invokeOptions.functionName = functionItem.functionName; + // 构造参数 + invokeHTTPData.headers = req.headers; + + if (req.body) { + const contentType = invokeHTTPData.headers['content-type'] || ''; + if (contentType.startsWith('application/x-www-form-urlencoded')) { + invokeHTTPData.body = qs.stringify(req.body); + } else if ( + contentType.startsWith('application/json') || + typeof req.body !== 'string' + ) { + invokeHTTPData.body = JSON.stringify(req.body); + } + } else { + invokeHTTPData.body = undefined; + } + invokeHTTPData.method = req.method; + invokeHTTPData.path = currentUrl; + invokeHTTPData.query = req.query; + invokeHTTPData.base64Encoded = false; + // ginkgo http 调用是数组 + invokeOptions.data = [invokeHTTPData]; + } + + return invokeOptions; +} diff --git a/packages/gateway-http/src/index.ts b/packages/gateway-http/src/index.ts new file mode 100644 index 00000000..9be1c226 --- /dev/null +++ b/packages/gateway-http/src/index.ts @@ -0,0 +1,129 @@ +import { + CreateExpressGateway, + CreateKoaGateway, + DevPackOptions, + ExpressGatewayAdapter, + InvokeCallback, + KoaGatewayAdapter, +} from '@midwayjs/gateway-core'; +import { Context } from 'koa'; +import { NextFunction, Request, Response } from 'express'; +import { parseInvokeOptionsByOriginUrl } from './common'; +import { getHeaderValue } from './utils'; + +export class KoaGateway implements KoaGatewayAdapter { + options: DevPackOptions; + + constructor(options: DevPackOptions) { + this.options = options; + } + + async transform( + ctx: Context, + next: () => Promise, + invoke: InvokeCallback + ) { + const invokeOptions = await parseInvokeOptionsByOriginUrl( + this.options, + ctx.request + ); + if (!invokeOptions.functionName) { + await next(); + } else { + try { + const result: { + headers: object; + statusCode: number; + body: string; + base64Encoded: boolean; + } = await invoke({ + functionDir: invokeOptions.functionDir, + functionName: invokeOptions.functionName, + data: invokeOptions.data, + sourceDir: invokeOptions.sourceDir, + incremental: true, + verbose: invokeOptions.verbose, + }); + ctx.status = result.statusCode; + let data; + try { + data = JSON.parse(result.body); + } catch (err) { + data = result.body; + } + ctx.body = data; + for (const key in result.headers) { + ctx.set(key, getHeaderValue(result.headers, key)); + } + } catch (err) { + ctx.body = err.stack; + ctx.status = 500; + } + } + } +} + +export class ExpressGateway implements ExpressGatewayAdapter { + options: DevPackOptions; + + constructor(options: DevPackOptions) { + this.options = options; + } + + async transform( + req: Request, + res: Response, + next: NextFunction, + invoke: InvokeCallback + ) { + const invokeOptions = await parseInvokeOptionsByOriginUrl( + this.options, + req + ); + if (!invokeOptions.functionName) { + return next(); + } else { + invoke({ + functionDir: invokeOptions.functionDir, + functionName: invokeOptions.functionName, + data: invokeOptions.data, + sourceDir: invokeOptions.sourceDir, + incremental: true, + verbose: invokeOptions.verbose, + }) + .then( + (result: { + headers: object; + statusCode: number; + body: string; + base64Encoded: boolean; + }) => { + res.statusCode = result.statusCode; + let data; + try { + data = JSON.parse(result.body); + } catch (err) { + data = result.body; + } + for (const key in result.headers) { + res.setHeader(key, getHeaderValue(result.headers, key)); + } + res.send(data); + } + ) + .catch((err) => { + next(err); + }); + } + } +} + +export const createKoaGateway: CreateKoaGateway = (options: DevPackOptions) => { + return new KoaGateway(options); +}; + +export const createExpressGateway: CreateExpressGateway = ( + options: DevPackOptions +) => { + return new ExpressGateway(options); +}; diff --git a/packages/gateway-http/src/utils.ts b/packages/gateway-http/src/utils.ts new file mode 100644 index 00000000..4ea1873a --- /dev/null +++ b/packages/gateway-http/src/utils.ts @@ -0,0 +1,5 @@ +export function getHeaderValue(headers, key) { + return Array.isArray(headers[key]) && headers[key].length === 1 + ? headers[key][0] + : headers[key]; +} diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/README.md b/packages/gateway-http/test/fixtures/ice-demo-repo/README.md new file mode 100644 index 00000000..c1d2bd33 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/README.md @@ -0,0 +1,17 @@ +# ice-typescript-starter + +## 使用 + +- 启动调试服务: `npm start` +- 构建 dist: `npm run build` + +## 目录结构 + +- react-router @4.x 默认采用 hashHistory 的单页应用 +- 入口文件: `src/index.js` +- 导航配置: `src/menuConfig.js` +- 路由配置: `src/routerConfig.js` +- 路由入口: `src/router.jsx` +- 布局文件: `src/layouts` +- 通用组件: `src/components` +- 页面文件: `src/pages` diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/f.yml b/packages/gateway-http/test/fixtures/ice-demo-repo/f.yml new file mode 100644 index 00000000..338ebe13 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/f.yml @@ -0,0 +1,31 @@ +service: fc-test + +provider: + name: tencent + runtime: nodejs10 + +functions: + test1: + handler: index.handler + events: + - http: + path: /server/user/info + test2: + handler: test2.handler + events: + - http: true + test3: + handler: test3.handler + events: + - http: + path: /api/* + test4: + handler: test4.handler + events: + - http: + path: /api/a/b/c + test5: + handler: test4.handler + events: + - http: + path: /ignore.do diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/ice.config.js b/packages/gateway-http/test/fixtures/ice-demo-repo/ice.config.js new file mode 100644 index 00000000..72ccfe18 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/ice.config.js @@ -0,0 +1,11 @@ +module.exports = { + entry: 'src/index.tsx', + plugins: [ + ['ice-plugin-fusion', { + themePackage: '@icedesign/theme', + }], + ['ice-plugin-moment-locales', { + locales: ['zh-cn'], + }], + ], +}; diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/package.json b/packages/gateway-http/test/fixtures/ice-demo-repo/package.json new file mode 100644 index 00000000..9a717988 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/package.json @@ -0,0 +1,38 @@ +{ + "name": "@icedesign/ts-scaffold9ade4c90-5de3-11e9-aef1-79dc0073b61f", + "version": "1.0.3", + "description": "该模板基于 TypeScript 适用于从 0 到 1 开始搭建项目,内置基础的页面,路由和菜单展示", + "dependencies": { + "@alifd/next": "^1.x", + "@icedesign/theme": "^1.x", + "@types/react": "^16.8.3", + "@types/react-dom": "^16.8.2", + "moment": "^2.23.0", + "prop-types": "^15.5.8", + "react": "^16.4.1", + "react-dom": "^16.4.1", + "react-router-dom": "^4.2.2", + "tslib": "^1.9.3", + "@midwayjs/faas": "*" + }, + "devDependencies": { + "babel-eslint": "^8.0.3", + "eslint": "^4.13.1", + "eslint-config-airbnb": "^16.1.0", + "eslint-plugin-babel": "^4.1.1", + "eslint-plugin-import": "^2.8.0", + "eslint-plugin-jsx-a11y": "^6.0.3", + "eslint-plugin-react": "^7.5.1", + "ice-scripts": "^2.0.0", + "ice-plugin-fusion": "^0.1.4", + "ice-plugin-moment-locales": "^0.1.0" + }, + "scripts": { + "start": "ice-scripts dev", + "build": "ice-scripts build", + "lint": "eslint . --ext '.js,.jsx' --fix" + }, + "engines": { + "node": ">=8.0.0" + } +} diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/public/favicon.png b/packages/gateway-http/test/fixtures/ice-demo-repo/public/favicon.png new file mode 100644 index 00000000..a2605c57 Binary files /dev/null and b/packages/gateway-http/test/fixtures/ice-demo-repo/public/favicon.png differ diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/public/index.html b/packages/gateway-http/test/fixtures/ice-demo-repo/public/index.html new file mode 100644 index 00000000..5c069296 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/public/index.html @@ -0,0 +1,13 @@ + + + + + + + ICE TypeScript Starter + + + +
+ + diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/index.ts b/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/index.ts new file mode 100644 index 00000000..be804810 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/index.ts @@ -0,0 +1,22 @@ +import { + provide, + func, + FunctionHandler, + inject, + FaaSContext, +} from '@midwayjs/faas'; + +@provide() +@func('index.handler') +export class IndexHandler implements FunctionHandler { + @inject() + ctx: FaaSContext; + + async handler() { + const name = this.ctx.req.body['name']; + const action = this.ctx.query['action']; + this.ctx.type = 'text/html; charset=utf-8'; + this.ctx.set('x-schema', 'bbb'); + this.ctx.body = `${name},hello http world,${action}`; + } +} diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/test2.ts b/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/test2.ts new file mode 100644 index 00000000..b921d415 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/test2.ts @@ -0,0 +1,18 @@ +import { + provide, + func, + FunctionHandler, + inject, + FaaSContext, +} from '@midwayjs/faas'; + +@provide() +@func('test2.handler') +export class Test2Handler implements FunctionHandler { + @inject() + ctx: FaaSContext; + + async handler() { + this.ctx.body = {data: 'test2'}; + } +} diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/test3.ts b/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/test3.ts new file mode 100644 index 00000000..139eccd2 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/test3.ts @@ -0,0 +1,18 @@ +import { + provide, + func, + FunctionHandler, + inject, + FaaSContext, +} from '@midwayjs/faas'; + +@provide() +@func('test3.handler') +export class Test3Handler implements FunctionHandler { + @inject() + ctx: FaaSContext; + + async handler() { + this.ctx.body = {data: 'test3'}; + } +} diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/test4.ts b/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/test4.ts new file mode 100644 index 00000000..a3c4c814 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/apis/test4.ts @@ -0,0 +1,18 @@ +import { + provide, + func, + FunctionHandler, + inject, + FaaSContext, +} from '@midwayjs/faas'; + +@provide() +@func('test4.handler') +export class Test4Handler implements FunctionHandler { + @inject() + ctx: FaaSContext; + + async handler() { + this.ctx.body = {data: 'test4'}; + } +} diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/components/Greeting/index.tsx b/packages/gateway-http/test/fixtures/ice-demo-repo/src/components/Greeting/index.tsx new file mode 100644 index 00000000..b9369ed6 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/components/Greeting/index.tsx @@ -0,0 +1,15 @@ +import React from 'react'; + +export interface Props { + name: string; +} + +const Greeting = ({ name }: Props) => { + return ( +
+ Hello, {name} +
+ ); +}; + +export default Greeting; diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/components/Guide/index.tsx b/packages/gateway-http/test/fixtures/ice-demo-repo/src/components/Guide/index.tsx new file mode 100644 index 00000000..3223e610 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/components/Guide/index.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { Button } from '@alifd/next'; + +const Guide = () => { + return ( +
+

使用指南

+
    +
  • + 1. 该模板适用于从 0 到 1 开始搭建项目,内置引导页面,路由和菜单展示。 +
  • +
  • 2. 菜单配置: menuConfig.js。
  • +
  • 3. 路由配置: routerConfig.js。
  • +
  • + 4. 通过 GUI 工具{' '} + + Iceworks + {' '} + 创建页面,会同步的更新菜单和路由配置。 +
  • +
  • + 5. 基于{' '} + + 物料 + {' '} + 生成的页面将会添加在 pages 目录。 +
  • +
  • + 6. 让前端工程变的轻松便捷, + + 下载 iceworks + {' '} + 。 +
  • +
+ +
+ ); +}; + +const styles = { + item: { + height: '34px', + lineHeight: '34px', + }, +}; + +export default Guide; diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/index.tsx b/packages/gateway-http/test/fixtures/ice-demo-repo/src/index.tsx new file mode 100644 index 00000000..0290d245 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/index.tsx @@ -0,0 +1,14 @@ +import ReactDOM from 'react-dom'; + +// 载入默认全局样式 normalize +import '@alifd/next/reset.scss'; + +import router from './router'; + +const ICE_CONTAINER = document.getElementById('ice-container'); + +if (!ICE_CONTAINER) { + throw new Error('当前页面不存在
节点.'); +} + +ReactDOM.render(router(), ICE_CONTAINER); diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/MainRoutes.tsx b/packages/gateway-http/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/MainRoutes.tsx new file mode 100644 index 00000000..632a63b6 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/MainRoutes.tsx @@ -0,0 +1,37 @@ +import React, { Component } from 'react'; +import { Redirect, Switch, Route } from 'react-router-dom'; +import routerConfig from '../../routerConfig'; +import Guide from '../../components/Guide'; + +class MainRoutes extends Component { + /** + * 渲染路由组件 + */ + renderNormalRoute = (item, index) => { + return item.component ? ( + + ) : null; + }; + + render() { + return ( + + {/* 渲染路由表 */} + {routerConfig.map(this.renderNormalRoute)} + + {/* 首页默认重定向到 /dashboard */} + + + {/* 未匹配到的路由重定向到 组件,实际情况应该重定向到 404 */} + + + ); + } +} + +export default MainRoutes; diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/index.tsx b/packages/gateway-http/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/index.tsx new file mode 100644 index 00000000..c1d9cf71 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/layouts/BasicLayout/index.tsx @@ -0,0 +1,12 @@ +import React, { Component } from 'react'; +import MainRoutes from './MainRoutes'; + +export default class BasicLayout extends Component { + render() { + return ( +
+ +
+ ); + } +} diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/menuConfig.ts b/packages/gateway-http/test/fixtures/ice-demo-repo/src/menuConfig.ts new file mode 100644 index 00000000..e3c4c9e3 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/menuConfig.ts @@ -0,0 +1,5 @@ +// 菜单配置 + +const asideMenuConfig = []; + +export { asideMenuConfig }; diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/pages/Dashboard/index.tsx b/packages/gateway-http/test/fixtures/ice-demo-repo/src/pages/Dashboard/index.tsx new file mode 100644 index 00000000..68adb830 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/pages/Dashboard/index.tsx @@ -0,0 +1,14 @@ +import React, { Component } from 'react'; +import Guide from '../../components/Guide'; +import Greeting from '../../components/Greeting'; + +export default class Dashboard extends Component { + render() { + return ( +
+ + +
+ ); + } +} diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/router.tsx b/packages/gateway-http/test/fixtures/ice-demo-repo/src/router.tsx new file mode 100644 index 00000000..f64d2d80 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/router.tsx @@ -0,0 +1,18 @@ +/** + * 定义应用路由 + */ +import { HashRouter, Switch, Route } from 'react-router-dom'; +import React from 'react'; +import BasicLayout from './layouts/BasicLayout'; + +const router = () => { + return ( + + + + + + ); +}; + +export default router; diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/src/routerConfig.ts b/packages/gateway-http/test/fixtures/ice-demo-repo/src/routerConfig.ts new file mode 100644 index 00000000..3ce552f7 --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/src/routerConfig.ts @@ -0,0 +1,13 @@ +// 以下文件格式为描述路由的协议格式 +// 你可以调整 routerConfig 里的内容 +// 变量名 routerConfig 为 iceworks 检测关键字,请不要修改名称 +import Dashboard from './pages/Dashboard'; + +const routerConfig = [ + { + path: '/dashboard', + component: Dashboard, + }, +]; + +export default routerConfig; diff --git a/packages/gateway-http/test/fixtures/ice-demo-repo/tsconfig.json b/packages/gateway-http/test/fixtures/ice-demo-repo/tsconfig.json new file mode 100644 index 00000000..39d09cdc --- /dev/null +++ b/packages/gateway-http/test/fixtures/ice-demo-repo/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compileOnSave": false, + "buildOnSave": false, + "compilerOptions": { + "baseUrl": ".", + "outDir": "build", + "module": "esnext", + "target": "es6", + "jsx": "react", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "lib": ["es6", "dom"], + "sourceMap": true, + "allowJs": true, + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": false, + "importHelpers": true, + "strictNullChecks": true, + "suppressImplicitAnyIndexErrors": true, + "noUnusedLocals": true, + "skipLibCheck": true + }, + "include": ["src/*"], + "exclude": ["node_modules", "build", "public"] +} diff --git a/packages/gateway-http/test/index.test.ts b/packages/gateway-http/test/index.test.ts new file mode 100644 index 00000000..50dab01f --- /dev/null +++ b/packages/gateway-http/test/index.test.ts @@ -0,0 +1,212 @@ +import { join } from 'path'; +import { isMatch } from 'micromatch'; +import * as minimatch from 'minimatch'; +import * as picomatch from 'picomatch'; +import * as assert from 'assert'; +import { createExpressSuit, createKoaSuit } from '@midwayjs/gateway-core'; + +describe('/test/index.test.ts', () => { + describe('test url match', () => { + it('test micromatch', () => { + assert.equal(isMatch('/server/user/info', '/server/user/info'), true); + assert.equal(isMatch('/server/user/info', '/server/user/info/1'), false); + assert.equal(isMatch('/server/user/info', '/server/user/info/**'), true); + assert.equal(isMatch('/server/user/info', '/server/user/**'), true); + assert.equal(isMatch('/bbbbbb/ccccc', '/**'), true); + assert.equal(isMatch('/api/abc', '/api/**'), true); + assert.equal(isMatch('/api/a/b/c/d', '/api/a/b/c'), false); + }); + + it('test minimatch', () => { + assert.equal(minimatch('/server/user/info', '/server/user/info'), true); + assert.equal( + minimatch('/server/user/info', '/server/user/info/1'), + false + ); + // assert.equal(minimatch('/server/user/info', '/server/user/info/**'), true); + assert.equal(minimatch('/server/user/info', '/server/user/**'), true); + assert.equal(minimatch('/bbbbbb/ccccc', '/**'), true); + assert.equal(minimatch('/api/abc', '/api/**'), true); + assert.equal(minimatch('/api/a/b/c/d', '/api/a/b/c'), false); + }); + + it('test picomatch', () => { + assert.equal( + picomatch.isMatch('/server/user/info', '/server/user/info'), + true + ); + assert.equal( + picomatch.isMatch('/server/user/info', '/server/user/info/1'), + false + ); + assert.equal( + picomatch.isMatch('/server/user/info', '/server/user/info/**'), + true + ); + assert.equal( + picomatch.isMatch('/server/user/info', '/server/user/**'), + true + ); + assert.equal(picomatch.isMatch('/bbbbbb/ccccc', '/**'), true); + assert.equal(picomatch.isMatch('/api/abc', '/api/**'), true); + assert.equal(picomatch.isMatch('/api/a/b/c/d', '/api/a/b/c'), false); + }); + }); + + describe('test express', () => { + it('test /server/user/info', done => { + createExpressSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + }) + .post('/server/user/info') + .query({ + action: 'doTest', + }) + .send({ name: 'zhangting' }) + .expect('Content-type', 'text/html; charset=utf-8') + .expect(/zhangting,hello http world,doTest/) + .expect('x-schema', 'bbb') + .expect(200, done); + }); + + it('test /* router', done => { + createExpressSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + }) + .post('/bbbbbb/ccccc') + .query({ + action: 'doTest', + }) + .expect(/test2/) + .expect(200, done); + }); + + it('test /api/* router', done => { + createExpressSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + }) + .post('/api/abc') + .query({ + action: 'doTest', + }) + .expect(/test3/) + .expect(200, done); + }); + + it('test /api/a/b/c router', done => { + createExpressSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + }) + .post('/api/a/b/c') + .query({ + action: 'doTest', + }) + .expect(/test4/) + .expect(200, done); + }); + + it('test /api/a/b/c/d router must match /api/* not /api/a/b/c', done => { + createExpressSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + }) + .post('/api/a/b/c/d') + .expect(/test3/) + .expect(200, done); + }); + }); + + it('should invoke by http api and koa', done => { + createKoaSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + }) + .post('/server/user/info') + .query({ + action: 'doTest', + }) + .send({ name: 'zhangting' }) + .expect('Content-type', 'text/html; charset=utf-8') + .expect(/zhangting,hello http world,doTest/) + .expect('x-schema', 'bbb') + .expect(200, done); + }); + + describe('test koa ignore pattern', () => { + it('should test ignore pattern', done => { + createKoaSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + ignorePattern: ['.do'], + }) + .post('/ignore.do') + .send({ name: 'zhangting' }) + .expect(404, done); + }); + + it('should test ignore pattern by function', done => { + createKoaSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + ignorePattern: req => { + return /\.do/.test(req.url); + }, + }) + .post('/ignore.do') + .send({ name: 'zhangting' }) + .expect(404, done); + }); + + it('should support ignore wildcard function', done => { + createKoaSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + ignoreWildcardFunctions: ['test2'], + }) + .post('/p') + .send({ name: 'zhangting' }) + .expect(404, done); + }); + }); + + describe('test express ignore pattern', () => { + it('should test ignore pattern', done => { + createExpressSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + ignorePattern: ['.do'], + }) + .post('/ignore.do') + .send({ name: 'zhangting' }) + .expect(404, done); + }); + + it('should test ignore pattern by function', done => { + createExpressSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + ignorePattern: req => { + return /\.do/.test(req.url); + }, + }) + .post('/ignore.do') + .send({ name: 'zhangting' }) + .expect(404, done); + }); + + it('should support ignore wildcard function', done => { + createExpressSuit({ + functionDir: join(__dirname, './fixtures/ice-demo-repo'), + sourceDir: 'src/apis', + ignoreWildcardFunctions: ['test2'], + }) + .post('/p') + .send({ name: 'zhangting' }) + .expect(404, done); + }); + }); +}); diff --git a/packages/gateway-http/tsconfig.json b/packages/gateway-http/tsconfig.json new file mode 100644 index 00000000..aa9b0585 --- /dev/null +++ b/packages/gateway-http/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "inlineSourceMap":true, + "noImplicitThis": true, + "noUnusedLocals": true, + "stripInternal": true, + "pretty": true, + "declaration": true, + "outDir": "dist" + }, + "exclude": [ + "dist", + "node_modules", + "test" + ] +} diff --git a/packages/serverless-fc-starter/src/context.ts b/packages/serverless-fc-starter/src/context.ts index adffd885..3a699179 100644 --- a/packages/serverless-fc-starter/src/context.ts +++ b/packages/serverless-fc-starter/src/context.ts @@ -54,7 +54,7 @@ export class Request { } get query() { - return this[EVENT].queries; + return this[EVENT].queries || this[EVENT].queryParameters; } get body() { diff --git a/packages/serverless-fc-trigger/src/http.ts b/packages/serverless-fc-trigger/src/http.ts index 4199f9e2..b068bd82 100644 --- a/packages/serverless-fc-trigger/src/http.ts +++ b/packages/serverless-fc-trigger/src/http.ts @@ -54,6 +54,9 @@ export class HTTPTrigger extends FCBaseTrigger { resolve([ new Proxy(req, { get: (target, key) => { + if (key === 'queries') { + key = 'query'; + } if (key in this.opts) { return this.opts[ key ]; } diff --git a/packages/serverless-scf-trigger/src/apiGateway.ts b/packages/serverless-scf-trigger/src/apiGateway.ts index 68604e61..9159af3c 100644 --- a/packages/serverless-scf-trigger/src/apiGateway.ts +++ b/packages/serverless-scf-trigger/src/apiGateway.ts @@ -50,3 +50,4 @@ export class ApiGatewayTrigger extends SCFBaseTrigger { } export const apigw = ApiGatewayTrigger; +export const http = ApiGatewayTrigger;