@midwayjs/faas
Version:

457 lines • 19.3 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MidwayFaaSFramework = void 0;
const core_1 = require("@midwayjs/core");
const simple_lock_1 = require("@midwayjs/simple-lock");
const logger_1 = require("@midwayjs/logger");
const serverless_http_parser_1 = require("@midwayjs/serverless-http-parser");
const util_1 = require("util");
const { isAnyArrayBuffer, isUint8Array } = util_1.types;
const LOCK_KEY = '_faas_starter_start_key';
let MidwayFaaSFramework = class MidwayFaaSFramework extends core_1.BaseFramework {
constructor() {
super(...arguments);
this.defaultHandlerMethod = 'handler';
this.funMappingStore = new Map();
this.lock = new simple_lock_1.default();
this.isReplaceLogger = process.env['MIDWAY_SERVERLESS_REPLACE_LOGGER'] === 'true';
this.developmentRun = false;
this.httpMiddlewareManager = this.createMiddlewareManager();
this.eventMiddlewareManager = this.createMiddlewareManager();
this.legacyVersion = false;
this.loadedFunction = false;
}
configure(options) {
var _a;
const faasConfig = (_a = this.configService.getConfiguration('faas')) !== null && _a !== void 0 ? _a : {};
if (options || faasConfig['developmentRun']) {
this.developmentRun = true;
this.configurationOptions = options;
}
else {
return faasConfig;
}
}
isEnable() {
return !this.developmentRun;
}
async applicationInitialize(options) {
var _a, _b, _c;
if (!this.logger) {
this.logger = options.logger || logger_1.loggers.getLogger('appLogger');
}
this.applicationAdapter =
this.configurationOptions.applicationAdapter || {};
if (this.applicationAdapter.getApplication) {
this.legacyVersion = true;
}
this.app =
((_b = (_a = this.applicationAdapter).getApplication) === null || _b === void 0 ? void 0 : _b.call(_a)) ||
new serverless_http_parser_1.Application();
this.defineApplicationProperties({
/**
* return init context value such as aliyun fc
*/
getInitializeContext: () => {
return this.configurationOptions.initializeContext;
},
/**
* @deprecated
* @param middlewareId
*/
generateMiddleware: async (middlewareId) => {
return this.generateMiddleware(middlewareId);
},
getFunctionName: () => {
var _a;
return (process.env.MIDWAY_SERVERLESS_FUNCTION_NAME ||
((_a = this.configurationOptions.applicationAdapter) === null || _a === void 0 ? void 0 : _a.getFunctionName()) ||
'');
},
/**
* get function service/group in runtime
*/
getFunctionServiceName: () => {
var _a;
return (process.env.MIDWAY_SERVERLESS_SERVICE_NAME ||
((_a = this.configurationOptions.applicationAdapter) === null || _a === void 0 ? void 0 : _a.getFunctionServiceName()) ||
'');
},
useEventMiddleware: middleware => {
return this.useEventMiddleware(middleware);
},
getEventMiddleware: () => {
return this.getEventMiddleware();
},
getServerlessInstance: async () => {
throw new core_1.MidwayFeatureNotImplementedError('Please use it in by @midwayjs/mock in test.');
},
invokeTriggerFunction: (context, handlerMapping, options) => {
return this.invokeTriggerFunction(context, handlerMapping, options);
},
});
// hack use method
this.app.originUse = this.app.use;
this.app.use = this.app.useMiddleware;
if ((_c = this.configurationOptions.applicationAdapter) === null || _c === void 0 ? void 0 : _c.runAppHook) {
this.configurationOptions.applicationAdapter.runAppHook(this.app);
}
}
async run() {
if (this.legacyVersion) {
return this.loadFunction();
}
}
async loadFunction() {
if (!this.loadedFunction) {
this.loadedFunction = true;
return this.lock.sureOnce(async () => {
var _a;
// set app keys
this.app['keys'] = (_a = this.configService.getConfiguration('keys')) !== null && _a !== void 0 ? _a : '';
// store all http function entry
this.serverlessFunctionService = await this.applicationContext.getAsync(core_1.MidwayServerlessFunctionService);
const functionList = await this.serverlessFunctionService.getFunctionList();
for (const funcInfo of functionList) {
// store handler
this.funMappingStore.set(funcInfo.funcHandlerName, funcInfo);
}
this.respond = this.app.callback();
}, LOCK_KEY);
}
}
getFrameworkType() {
return core_1.MidwayFrameworkType.FAAS;
}
/**
* @deprecated
* @param handlerMapping
*/
handleInvokeWrapper(handlerMapping) {
const funOptions = this.funMappingStore.get(handlerMapping);
return async (...args) => {
if (args.length === 0) {
throw new Error('first parameter must be function context');
}
if (!funOptions) {
throw new Error(`function handler = ${handlerMapping} not found`);
}
const context = this.getContext(args.shift());
const isHttpFunction = !!(context.headers && context.get);
const globalMiddlewareFn = await this.applyMiddleware();
const middlewareManager = new core_1.ContextMiddlewareManager();
middlewareManager.insertLast(globalMiddlewareFn);
middlewareManager.insertLast(async (ctx, next) => {
const fn = await this.middlewareService.compose([
...(isHttpFunction
? this.httpMiddlewareManager
: this.eventMiddlewareManager),
...funOptions.controllerMiddleware,
...funOptions.middleware,
async (ctx, next) => {
if (funOptions.controllerClz &&
typeof funOptions.method === 'string') {
const isPassed = await this.app
.getFramework()
.runGuard(ctx, funOptions.controllerClz, funOptions.method);
if (!isPassed) {
throw new core_1.httpError.ForbiddenError();
}
}
if (isHttpFunction) {
args = [ctx];
}
// invoke handler
const result = await this.invokeHandler(funOptions, ctx, args, isHttpFunction);
if (isHttpFunction && result !== undefined) {
if (result === null) {
// 这样设置可以绕过 koa 的 _explicitStatus 赋值机制
ctx.response._body = null;
}
else {
ctx.body = result;
}
}
return result;
},
], this.app);
return await fn(ctx, next);
});
const composeMiddleware = await this.middlewareService.compose(middlewareManager, this.app);
return await composeMiddleware(context);
};
}
async invokeTriggerFunction(context, handlerMapping, options) {
var _a, _b;
let funOptions = this.funMappingStore.get(handlerMapping);
const isHttpFunction = options.isHttpFunction;
if (!funOptions && isHttpFunction) {
funOptions = await this.serverlessFunctionService.getMatchedRouterInfo(context.path, context.method);
if (funOptions) {
const matchRes = core_1.PathToRegexpUtil.match(funOptions.fullUrlFlattenString)(context.path);
context.req.pathParameters = matchRes['params'] || {};
}
else {
// options request pass throuth to middleware
if (((_a = context.method) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'options') {
funOptions = {
url: context.path,
method: 'options',
requestMethod: 'options',
controllerMiddleware: [],
middleware: [],
};
}
}
}
if (!funOptions) {
throw new Error(`function handler = ${handlerMapping} not found`);
}
context = this.getContext(context);
if ((_b = this.configurationOptions.applicationAdapter) === null || _b === void 0 ? void 0 : _b.runContextHook) {
this.configurationOptions.applicationAdapter.runContextHook(context);
}
const result = await (await this.applyMiddleware(async (ctx, next) => {
const fn = await this.middlewareService.compose([
...(isHttpFunction
? this.httpMiddlewareManager
: this.eventMiddlewareManager),
...funOptions.controllerMiddleware,
...funOptions.middleware,
async (ctx, next) => {
let args;
if (isHttpFunction) {
args = [ctx];
}
else {
args = [ctx.originEvent, ctx.originContext];
}
// invoke handler
const result = await this.invokeHandler(funOptions, ctx, args, isHttpFunction);
if (isHttpFunction && result !== undefined) {
if (result === null) {
// 这样设置可以绕过 koa 的 _explicitStatus 赋值机制
ctx.response._body = null;
}
else {
ctx.body = result;
}
}
// http 靠 ctx.body,否则会出现状态码不正确的问题
if (!isHttpFunction) {
return result;
}
},
], this.app);
return await fn(ctx, next);
}))(context);
if (isHttpFunction) {
if (options.isCustomHttpResponse) {
return context.body;
}
else {
return this.formatHttpResponse(context, options);
}
}
else {
return result;
}
}
formatHttpResponse(context, options = {}) {
var _a;
if (!((_a = context.response) === null || _a === void 0 ? void 0 : _a._explicitStatus)) {
if (context.body === null || context.body === 'undefined') {
context.body = '';
context.type = 'text';
context.status = 204;
}
}
let encoded = false;
const data = context.body;
if (typeof data === 'string') {
if (!context.type) {
context.type = 'text/plain';
}
context.body = data;
}
else if (isAnyArrayBuffer(data) || isUint8Array(data)) {
if (!context.type) {
context.type = 'application/octet-stream';
}
if (options.supportBufferResponse) {
context.body = data;
}
else {
encoded = true;
// data is reserved as buffer
context.body = Buffer.from(data).toString('base64');
context.length = data.byteLength;
}
}
else if (typeof data === 'object') {
if (!context.type) {
context.type = 'application/json';
}
// set data to string
context.body = JSON.stringify(data);
}
else {
if (!context.type) {
context.type = 'text/plain';
}
// set data to string
context.body = data + '';
}
// middleware return value and will be got 204 status
if (context.body === undefined &&
!context.response._explicitStatus &&
context._matchedRoute) {
// 如果进了路由,重新赋值,防止 404
context.body = undefined;
}
return {
isBase64Encoded: encoded,
statusCode: context.status,
headers: context.res.headers,
body: context.body,
};
}
async wrapHttpRequest(req, res, options) {
const newReq = res ? new serverless_http_parser_1.HTTPRequest(req, res) : req;
const newRes = new serverless_http_parser_1.HTTPResponse(options);
return this.createHttpContext(newReq, newRes);
}
/**
* @deprecated
* @param middlewareId
*/
async generateMiddleware(middlewareId) {
const mwIns = await this.getApplicationContext().getAsync(middlewareId);
return mwIns.resolve();
}
getContext(context = {}) {
if (!context.env) {
context.env = this.environmentService.getCurrentEnvironment();
}
if (this.isReplaceLogger || !context.logger) {
context._serverlessLogger = this.createContextLogger(context);
/**
* 由于 fc 的 logger 有 bug,FC公有云环境我们会默认替换掉,其他平台后续视情况而定
*/
Object.defineProperty(context, 'logger', {
get() {
return context._serverlessLogger;
},
});
}
this.app.createAnonymousContext(context);
return context;
}
async invokeHandler(routerInfo, context, args, isHttpFunction) {
if (typeof routerInfo.method !== 'string') {
if (!isHttpFunction) {
args.unshift(context);
}
return routerInfo.method(...args);
}
else {
const funModule = await context.requestContext.getAsync(routerInfo.controllerId);
const handlerName = this.getFunctionHandler(context, args, funModule, routerInfo.method) ||
this.defaultHandlerMethod;
if (funModule[handlerName]) {
// invoke real method
const result = await funModule[handlerName](...args);
// implement response decorator
const routerResponseData = routerInfo.responseMetadata;
if (isHttpFunction) {
for (const routerRes of routerResponseData) {
switch (routerRes.type) {
case core_1.WEB_RESPONSE_HTTP_CODE:
context.status = routerRes.code;
break;
case core_1.WEB_RESPONSE_HEADER:
for (const key in (routerRes === null || routerRes === void 0 ? void 0 : routerRes.setHeaders) || {}) {
context.set(key, routerRes.setHeaders[key]);
}
break;
case core_1.WEB_RESPONSE_CONTENT_TYPE:
context.type = routerRes.contentType;
break;
case core_1.WEB_RESPONSE_REDIRECT:
context.status = routerRes.code;
context.redirect(routerRes.url);
return;
}
}
}
return result;
}
}
}
getFunctionHandler(ctx, args, target, method) {
if (method && typeof target[method] !== 'undefined') {
return method;
}
const handlerMethod = this.defaultHandlerMethod;
if (handlerMethod && typeof target[handlerMethod] !== 'undefined') {
return handlerMethod;
}
throw new Error(`no handler setup on ${target.name}#${method || this.defaultHandlerMethod}`);
}
createLogger(name, option = {}) {
// 覆盖基类的创建日志对象,函数场景下的日志,即使自定义,也只启用控制台输出
return (0, logger_1.createConsoleLogger)(name, option);
}
getFrameworkName() {
return 'midway:faas';
}
getServer() {
return this.server;
}
async beforeStop() {
if (this.server) {
new Promise(resolve => {
this.server.close(resolve);
});
}
}
async createHttpContext(req, res) {
return new Promise(resolve => {
this.respond(req, res, resolve);
});
}
useMiddleware(middleware) {
this.httpMiddlewareManager.insertLast(middleware);
}
useEventMiddleware(middleware) {
this.eventMiddlewareManager.insertLast(middleware);
}
getEventMiddleware() {
return this.eventMiddlewareManager;
}
getAllHandlerNames() {
return Array.from(this.funMappingStore.keys());
}
};
__decorate([
(0, core_1.Inject)(),
__metadata("design:type", core_1.MidwayEnvironmentService)
], MidwayFaaSFramework.prototype, "environmentService", void 0);
__decorate([
(0, core_1.Inject)(),
__metadata("design:type", core_1.MidwayMiddlewareService)
], MidwayFaaSFramework.prototype, "middlewareService", void 0);
MidwayFaaSFramework = __decorate([
(0, core_1.Framework)()
], MidwayFaaSFramework);
exports.MidwayFaaSFramework = MidwayFaaSFramework;
//# sourceMappingURL=framework.js.map