Skip to content

Commit

Permalink
feat: support fun router (#867)
Browse files Browse the repository at this point in the history
Co-authored-by: echosoar <82163514@qq.com>
  • Loading branch information
czy88840616 and echosoar authored Feb 27, 2021
1 parent 542b701 commit 01e673f
Show file tree
Hide file tree
Showing 51 changed files with 1,356 additions and 78 deletions.
8 changes: 8 additions & 0 deletions packages-serverless/serverless-app/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const path = require('path');

module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testPathIgnorePatterns: ['<rootDir>/test/fixtures'],
coveragePathIgnorePatterns: ['<rootDir>/test/'],
};
49 changes: 49 additions & 0 deletions packages-serverless/serverless-app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"name": "@midwayjs/serverless-app",
"version": "2.6.8",
"main": "dist/index",
"typings": "dist/index.d.ts",
"dependencies": {
"@midwayjs/bootstrap": "^2.8.0",
"@midwayjs/core": "^2.8.0",
"@midwayjs/locate": "^1.4.1",
"@midwayjs/gateway-common-http": "^1.2.41",
"@midwayjs/serverless-spec-builder": "^1.2.41",
"body-parser": "^1.19.0",
"express": "^4.17.1"
},
"devDependencies": {
"@midwayjs/cli": "^1.2.36",
"@midwayjs/faas": "^2.8.0",
"@midwayjs/decorator": "^2.8.0",
"@midwayjs/faas-middleware-upload": "^0.0.5",
"supertest": "^4.0.2",
"typescript": "^4.1.0",
"@midwayjs/serverless-fc-starter": "^2.7.0",
"@midwayjs/serverless-fc-trigger": "^2.7.0",
"@midwayjs/serverless-scf-starter": "^2.7.0",
"@midwayjs/serverless-scf-trigger": "^2.7.0"
},
"engines": {
"node": ">= 10"
},
"files": [
"plugin.json",
"dist",
"src"
],
"scripts": {
"build": "midway-bin build -c",
"test": "midway-bin test --ts",
"cov": "midway-bin cov --ts"
},
"repository": {
"type": "git",
"url": "git@github.com:midwayjs/midway.git"
},
"publishConfig": {
"access": "public"
},
"license": "MIT",
"gitHead": "58706de896b8e3b50605bb29f40ff29abe43924d"
}
303 changes: 303 additions & 0 deletions packages-serverless/serverless-app/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
import {
IMidwayApplication,
IMidwayBootstrapOptions,
IMidwayContainer,
IMidwayContext,
IMidwayFramework,
MidwayFrameworkType,
} from '@midwayjs/core';

import { Server } from 'net';
import { start2 } from './start';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import { getSpecFile, loadSpec } from '@midwayjs/serverless-spec-builder';
import { createExpressGateway } from '@midwayjs/gateway-common-http';
import { findNpmModule, output404 } from './utils';
import { Locator } from '@midwayjs/locate';
import { StarterMap, TriggerMap } from './platform';

import { IServerlessApp, IServerlessAppOptions } from './interface';

export * from './interface';
export class Framework
implements IMidwayFramework<IServerlessApp, IServerlessAppOptions> {
app: IServerlessApp;
configurationOptions: IServerlessAppOptions;
private innerApp: IMidwayApplication;
private innerFramework: IMidwayFramework<any, any>;
private runtime: any;
private server: Server;
private bootstrapOptions;
private spec;
configure(options: IServerlessAppOptions) {
this.configurationOptions = options;
return this;
}
async stop() {
if (this.server?.close) {
this.server.close();
}
}

getApplicationContext(): IMidwayContainer {
return this.innerApp.getApplicationContext();
}

getConfiguration(key?: string): any {
return this.innerApp.getConfig(key);
}

getCurrentEnvironment(): string {
return this.innerApp.getEnv();
}
getAppDir(): string {
return this.innerApp.getAppDir();
}

getLogger(name?: string): any {
return this.innerApp.getLogger(name);
}
getBaseDir(): string {
return this.innerApp.getBaseDir();
}
getCoreLogger() {
return (this.innerApp as any).coreLogger;
}
createLogger(name: string, options?: any): any {
return this.innerApp.createLogger(name, options);
}
getProjectName(): string {
return this.innerApp.getProjectName();
}
public getDefaultContextLoggerClass() {
return this.innerFramework.getDefaultContextLoggerClass();
}

async applicationInitialize(options: IMidwayBootstrapOptions) {}

public getFrameworkName() {
return 'midway:serverless:app';
}

public getFrameworkType(): MidwayFrameworkType {
return MidwayFrameworkType.FAAS;
}
public getApplication(): IServerlessApp {
return this.app;
}

public getServer() {
return this.server;
}

private getStarterName() {
const starter = this.spec?.provider?.starterModule;
if (starter) {
return require.resolve(starter);
}
const platform = this.getPlatform();
const starterModList = StarterMap[platform];
if (!starterModList || !starterModList.length) {
throw new Error(`Current provider '${platform}' not support(no starter)`);
}
for (const mod of starterModList) {
try {
return require.resolve(mod);
} catch {
// continue
}
}
throw new Error(
`Platform starter '${
starterModList[starterModList.length - 1]
}' not found`
);
}

private getTriggerMap() {
const trigger = this.spec?.provider?.triggerModule;
if (trigger) {
return require(trigger);
}
const platform = this.getPlatform();
const triggerModList = TriggerMap[platform];
if (!triggerModList || !triggerModList.length) {
throw new Error(`Current provider '${platform}' not support(no trigger)`);
}
for (const mod of triggerModList) {
try {
return require(mod);
} catch {
// continue
}
}
throw new Error(
`Platform trigger '${
triggerModList[triggerModList.length - 1]
}' not found`
);
}

private async getServerlessInstance<T>(cls: any): Promise<T> {
// 如何传initializeContext
const context: IMidwayContext = await new Promise(resolve => {
this.runtime.asyncEvent(async ctx => {
resolve((this.innerFramework as any).getContext(ctx));
})({}, this.configurationOptions.initContext || {});
});

return context.requestContext.getAsync(cls);
}

private getPlatform() {
const provider = this.spec?.provider?.name;
if (provider) {
if (provider === 'fc' || provider === 'aliyun') {
return 'aliyun';
} else if (provider === 'scf' || provider === 'tencent') {
return 'tencent';
}
}
return provider;
}

async initialize(options: Partial<IMidwayBootstrapOptions>) {
process.env.MIDWAY_SERVER_ENV = process.env.MIDWAY_SERVER_ENV || 'local';
this.bootstrapOptions = options;
this.getFaaSSpec();
this.app = express() as any;
const { appDir, baseDir } = options;

const faasModule = '@midwayjs/faas';
const faasModulePath = findNpmModule(appDir, faasModule);
if (!faasModulePath) {
throw new Error(`Module '${faasModule}' not found`);
}
const starterName = this.getStarterName();
const usageFaaSModule = this.getFaaSModule();

let usageFaasModulePath = faasModulePath;
if (usageFaaSModule !== faasModule) {
usageFaasModulePath = findNpmModule(appDir, usageFaaSModule);
if (!usageFaasModulePath) {
throw new Error(`Module '${usageFaasModulePath}' not found`);
}
}

// 分析项目结构
const locator = new Locator(appDir);
const midwayLocatorResult = await locator.run({});
const triggerMap = this.getTriggerMap();

const { Framework } = require(usageFaasModulePath);
const startResult = await start2({
appDir,
baseDir: midwayLocatorResult.tsCodeRoot || baseDir,
framework: Framework,
starter: require(starterName),
initializeContext: undefined,
});
this.innerFramework = startResult.framework;
this.runtime = startResult.runtime;
this.innerApp = startResult.framework.getApplication();
const invoke = startResult.invoke;
const httpFuncSpec = await startResult.getFunctionsFromDecorator();
if (!this.spec.functions) {
this.spec.functions = {};
}
Object.assign(this.spec.functions, httpFuncSpec);
this.app.getServerlessInstance = this.getServerlessInstance.bind(this);
this.app.use(bodyParser.urlencoded({ extended: false }));
this.app.use(bodyParser.json());
this.app.use((req, res, next) => {
const gateway = createExpressGateway({
functionDir: appDir,
});
gateway.transform(req, res, next, async () => {
return {
functionList: this.spec.functions,
invoke: async args => {
const trigger = [new triggerMap.http(...args.data)];
let newArgs = trigger;
let callBackTrigger;
if (newArgs?.[0] && typeof newArgs[0].toArgs === 'function') {
callBackTrigger = trigger[0];
newArgs = await trigger[0].toArgs();
}
const result = await new Promise((resolve, reject) => {
if (callBackTrigger?.useCallback) {
// 这个地方 callback 得调用 resolve
const cb = callBackTrigger.createCallback((err, result) => {
if (err) {
return reject(err);
}
return resolve(result);
});
newArgs.push(cb);
}
Promise.resolve(invoke(args.functionHandler, newArgs)).then(
resolve,
reject
);
});
if (callBackTrigger?.close) {
await callBackTrigger.close();
}
return result;
},
};
});
});

this.app.use((req, res) => {
res.statusCode = 404;
res.send(output404(req.path, this.spec.functions));
});

if (process.env.IN_CHILD_PROCESS) {
this.listenMessage();
}
}

protected getFaaSModule() {
return process.env.DEV_MIDWAY_FAAS_MODULE || '@midwayjs/faas';
}

protected getFaasStarterName() {
return 'FaaSStarter';
}

private getFaaSSpec() {
const { appDir } = this.bootstrapOptions;
const specFileInfo = getSpecFile(appDir);
this.spec = loadSpec(appDir, specFileInfo);
}

public async run() {
if (this.configurationOptions.port) {
this.server = require('http').createServer(this.app);
await new Promise<void>(resolve => {
this.server.listen(this.configurationOptions.port, () => {
resolve();
});
});
}
}

private listenMessage() {
process.on('message', async msg => {
if (!msg || !msg.type) {
return;
}
const type = msg.type;
let data;
switch (type) {
case 'functions':
data = this.spec.functions;
break;
}
process.send({ type: 'dev:' + type, data, id: msg.id });
});
}
}
13 changes: 13 additions & 0 deletions packages-serverless/serverless-app/src/interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {
IMidwayApplication,
IConfigurationOptions
} from '@midwayjs/core';
export interface IServerlessApp extends IMidwayApplication {
use: any;
getServerlessInstance<T>(cls: any): Promise<T>;
}

export interface IServerlessAppOptions extends IConfigurationOptions {
port?: string | number;
initContext?: any;
}
9 changes: 9 additions & 0 deletions packages-serverless/serverless-app/src/platform.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const StarterMap = {
aliyun: ['@ali/serverless-fc-starter', '@midwayjs/serverless-fc-starter'],
tencent: ['@midwayjs/serverless-scf-trigger'],
};

export const TriggerMap = {
aliyun: ['@midwayjs/serverless-fc-trigger'],
tencent: ['@midwayjs/serverless-scf-trigger'],
};
Loading

0 comments on commit 01e673f

Please sign in to comment.