diff --git a/runtimes/nodejs/Dockerfile b/runtimes/nodejs/Dockerfile index d376610085..d09d33a4f0 100644 --- a/runtimes/nodejs/Dockerfile +++ b/runtimes/nodejs/Dockerfile @@ -5,6 +5,8 @@ RUN apt update && apt-get install build-essential libcairo2-dev libpango1.0-dev # RUN npm install npm -g EXPOSE 8000 +EXPOSE 9000 + WORKDIR /app ENV LOG_LEVEL=debug COPY . /app diff --git a/runtimes/nodejs/package-lock.json b/runtimes/nodejs/package-lock.json index 985baf1ae5..001678e384 100644 --- a/runtimes/nodejs/package-lock.json +++ b/runtimes/nodejs/package-lock.json @@ -21,6 +21,7 @@ "dotenv": "^8.2.0", "ejs": "^3.1.8", "express": "^4.18.2", + "express-http-proxy": "^2.0.0", "express-xml-bodyparser": "^0.3.0", "jsonwebtoken": "^9.0.0", "lodash": "^4.17.21", @@ -39,6 +40,7 @@ "@types/dotenv": "^8.2.0", "@types/ejs": "^3.1.1", "@types/express": "^4.17.15", + "@types/express-http-proxy": "^1.6.3", "@types/express-xml-bodyparser": "^0.3.2", "@types/jsonwebtoken": "^8.5.1", "@types/lodash": "^4.14.171", @@ -2701,6 +2703,15 @@ "@types/serve-static": "*" } }, + "node_modules/@types/express-http-proxy": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@types/express-http-proxy/-/express-http-proxy-1.6.3.tgz", + "integrity": "sha512-dX3+Cb0HNPtqhC5JUWzzuODHRlgJRZx7KvwKVVwkOvm+8vOtpsh3qy8+qLv5X1hs4vdVHWKyXf86DwJot5H8pg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/express-serve-static-core": { "version": "4.17.31", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", @@ -3696,6 +3707,11 @@ "node": ">= 0.4" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3766,6 +3782,32 @@ "node": ">= 0.10.0" } }, + "node_modules/express-http-proxy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-2.0.0.tgz", + "integrity": "sha512-TXxcPFTWVUMSEmyM6iX2sT/JtmqhqngTq29P+eXTVFdtxZrTmM8THUYK59rUXiln0FfPGvxEpGRnVrgvHksXDw==", + "dependencies": { + "debug": "^3.0.1", + "es6-promise": "^4.1.1", + "raw-body": "^2.3.0" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/express-http-proxy/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/express-http-proxy/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/express-xml-bodyparser": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/express-xml-bodyparser/-/express-xml-bodyparser-0.3.0.tgz", @@ -8430,6 +8472,15 @@ "@types/serve-static": "*" } }, + "@types/express-http-proxy": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@types/express-http-proxy/-/express-http-proxy-1.6.3.tgz", + "integrity": "sha512-dX3+Cb0HNPtqhC5JUWzzuODHRlgJRZx7KvwKVVwkOvm+8vOtpsh3qy8+qLv5X1hs4vdVHWKyXf86DwJot5H8pg==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, "@types/express-serve-static-core": { "version": "4.17.31", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", @@ -9235,6 +9286,11 @@ "is-symbol": "^1.0.2" } }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -9300,6 +9356,31 @@ } } }, + "express-http-proxy": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/express-http-proxy/-/express-http-proxy-2.0.0.tgz", + "integrity": "sha512-TXxcPFTWVUMSEmyM6iX2sT/JtmqhqngTq29P+eXTVFdtxZrTmM8THUYK59rUXiln0FfPGvxEpGRnVrgvHksXDw==", + "requires": { + "debug": "^3.0.1", + "es6-promise": "^4.1.1", + "raw-body": "^2.3.0" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, "express-xml-bodyparser": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/express-xml-bodyparser/-/express-xml-bodyparser-0.3.0.tgz", diff --git a/runtimes/nodejs/package.json b/runtimes/nodejs/package.json index 0faf4e9746..b218a36df4 100644 --- a/runtimes/nodejs/package.json +++ b/runtimes/nodejs/package.json @@ -38,6 +38,7 @@ "dotenv": "^8.2.0", "ejs": "^3.1.8", "express": "^4.18.2", + "express-http-proxy": "^2.0.0", "express-xml-bodyparser": "^0.3.0", "jsonwebtoken": "^9.0.0", "lodash": "^4.17.21", @@ -56,6 +57,7 @@ "@types/dotenv": "^8.2.0", "@types/ejs": "^3.1.1", "@types/express": "^4.17.15", + "@types/express-http-proxy": "^1.6.3", "@types/express-xml-bodyparser": "^0.3.2", "@types/jsonwebtoken": "^8.5.1", "@types/lodash": "^4.14.171", diff --git a/runtimes/nodejs/src/config.ts b/runtimes/nodejs/src/config.ts index 81def5545e..4da9d9b7cd 100644 --- a/runtimes/nodejs/src/config.ts +++ b/runtimes/nodejs/src/config.ts @@ -50,6 +50,10 @@ export default class Config { return (process.env.PORT ?? 8000) as number } + static get STORAGE_PORT(): number { + return (process.env.STORAGE_PORT ?? 9000) as number + } + /** * in production deploy or not */ @@ -77,15 +81,23 @@ export default class Config { return process.env.REQUEST_LIMIT_SIZE || '10mb' } - static get LOG_SERVER_URL(): string { + static get LOG_SERVER_URL(): string { return process.env.LOG_SERVER_URL || '' } - static get LOG_SERVER_TOKEN(): string { + static get LOG_SERVER_TOKEN(): string { return process.env.LOG_SERVER_TOKEN || '' } - static get CHANGE_STREAM_RECONNECT_INTERVAL(): number { + static get CHANGE_STREAM_RECONNECT_INTERVAL(): number { return (process.env.CHANGE_STREAM_RECONNECT_INTERVAL || 3000) as number } + + static get OSS_INTERNAL_ENDPOINT(): string { + return process.env.OSS_INTERNAL_ENDPOINT || '' + } + + static get OSS_EXTERNAL_ENDPOINT(): string { + return process.env.OSS_EXTERNAL_ENDPOINT + } } diff --git a/runtimes/nodejs/src/constants.ts b/runtimes/nodejs/src/constants.ts index 9185b94ce2..dc75ffbb8f 100644 --- a/runtimes/nodejs/src/constants.ts +++ b/runtimes/nodejs/src/constants.ts @@ -4,6 +4,7 @@ export const CLOUD_FUNCTION_COLLECTION = '__functions__' export const POLICY_COLLECTION = '__policies__' export const CONFIG_COLLECTION = '__conf__' +export const WEBSITE_HOSTING_COLLECTION = '__website_hosting__' export const WEBSOCKET_FUNCTION_NAME = '__websocket__' export const INTERCEPTOR_FUNCTION_NAME = '__interceptor__' diff --git a/runtimes/nodejs/src/handler/router.ts b/runtimes/nodejs/src/handler/router.ts index f4ae0d6ebe..f5206dc3f3 100644 --- a/runtimes/nodejs/src/handler/router.ts +++ b/runtimes/nodejs/src/handler/router.ts @@ -58,4 +58,4 @@ router.all('*', uploader.any(), (req, res) => { req.params['name'] = func_name handleInvokeFunction(req, res) -}) \ No newline at end of file +}) diff --git a/runtimes/nodejs/src/handler/typings.ts b/runtimes/nodejs/src/handler/typings.ts index cf35eee94b..dcdff3ecf3 100644 --- a/runtimes/nodejs/src/handler/typings.ts +++ b/runtimes/nodejs/src/handler/typings.ts @@ -21,15 +21,15 @@ const nodeModulesRoot = path.resolve(__dirname, '../../node_modules') export async function handlePackageTypings(req: IRequest, res: Response) { const requestId = req['requestId'] - // verify the debug token - const token = req.get('x-laf-develop-token') - if (!token) { - return res.status(400).send('x-laf-develop-token is required') - } - const auth = parseToken(token) || null - if (auth?.type !== 'develop') { - return res.status(403).send('permission denied: invalid develop token') - } + // verify the debug token + const token = req.get('x-laf-develop-token') + if (!token) { + return res.status(400).send('x-laf-develop-token is required') + } + const auth = parseToken(token) || null + if (auth?.type !== 'develop') { + return res.status(403).send('permission denied: invalid develop token') + } const packageName = req.query.packageName as string if (!packageName) { diff --git a/runtimes/nodejs/src/index.ts b/runtimes/nodejs/src/index.ts index 6566ef2a99..5b02ec399c 100644 --- a/runtimes/nodejs/src/index.ts +++ b/runtimes/nodejs/src/index.ts @@ -19,14 +19,15 @@ import xmlparser from 'express-xml-bodyparser' // init static method of class import './support/cloud-sdk' +import storageServer from './storage-server' +import { DatabaseChangeStream } from './support/database-change-stream' import { FunctionCache } from './support/function-engine/cache' -import { DatabaseChangeStream } from './support/db-change-stream' const app = express() DatabaseAgent.accessor.ready.then(() => { - FunctionCache.initialize() DatabaseChangeStream.initialize() + FunctionCache.initialize() }) if (process.env.NODE_ENV === 'development') { @@ -69,7 +70,8 @@ app.use(function (req, res, next) { if (req.url !== '/_/healthz') { logger.info( requestId, - `${req.method} "${req.url}" - referer: ${req.get('referer') || '-' + `${req.method} "${req.url}" - referer: ${ + req.get('referer') || '-' } ${req.get('user-agent')}`, ) logger.trace(requestId, `${req.method} ${req.url}`, { @@ -102,8 +104,9 @@ process.on('SIGINT', gracefullyExit) async function gracefullyExit() { await DatabaseAgent.accessor.close() - server.close(async () => { - logger.info('process gracefully exited!') - process.exit(0) - }) + await server.close() + await storageServer.close() + + logger.info('process gracefully exited!') + process.exit(0) } diff --git a/runtimes/nodejs/src/storage-server.ts b/runtimes/nodejs/src/storage-server.ts new file mode 100644 index 0000000000..2c4f7d5199 --- /dev/null +++ b/runtimes/nodejs/src/storage-server.ts @@ -0,0 +1,79 @@ +import express from 'express' +import Config from './config' +import { logger } from './support/logger' +import { DatabaseAgent } from './db' +import './support/cloud-sdk' +import { WebsiteHostingChangeStream } from './support/database-change-stream/website-hosting-change-stream' +import proxy from 'express-http-proxy' +import axios from 'axios' + +const app = express() + +DatabaseAgent.accessor.ready.then(() => { + WebsiteHostingChangeStream.initialize() +}) + +const tryPath = (bucket: string, path: string) => { + const testPaths = path.endsWith('/') + ? [path + 'index.html', '/index.html'] + : [path, path + '/index.html', '/index.html'] + return testPaths.map((v) => `/${bucket}${v}`) +} + +app.use( + proxy(Config.OSS_INTERNAL_ENDPOINT, { + preserveHostHdr: true, + parseReqBody: false, + proxyReqOptDecorator: function (proxyReqOpts, srcReq) { + // patch for + if ('content-length' in srcReq.headers) { + proxyReqOpts.headers['content-length'] = + srcReq.headers['content-length'] + } + if ('connection' in srcReq.headers) { + proxyReqOpts.headers['connection'] = srcReq.headers['connection'] + } + return proxyReqOpts + }, + proxyReqPathResolver: async function (req) { + // check if is website hosting + const websiteHosting = WebsiteHostingChangeStream.websiteHosting.find( + (item) => req.hostname === item.domain, + ) + if (!websiteHosting) { + return req.url + } + + // req.url doesn't have hostname + const minioUrl = new URL(req.url, Config.OSS_INTERNAL_ENDPOINT) + const paths = tryPath(websiteHosting.bucketName, req.path) + const getUrl = () => minioUrl.pathname + minioUrl.search + + for (const [idx, path] of paths.entries()) { + minioUrl.pathname = path + + if (idx === paths.length - 1) { + return getUrl() + } + + try { + await axios.head(minioUrl.toString()) + return getUrl() + } catch (err) { + if (err.response.status === 404) { + continue + } + return getUrl() + } + } + }, + }), +) + +const storageServer = app.listen(Config.STORAGE_PORT, () => + logger.info( + `storage server ${process.pid} listened on ${Config.STORAGE_PORT}`, + ), +) + +export default storageServer diff --git a/runtimes/nodejs/src/support/database-change-stream/conf-change-stream.ts b/runtimes/nodejs/src/support/database-change-stream/conf-change-stream.ts new file mode 100644 index 0000000000..cf7b2ab7ff --- /dev/null +++ b/runtimes/nodejs/src/support/database-change-stream/conf-change-stream.ts @@ -0,0 +1,27 @@ +import { CONFIG_COLLECTION } from '../../constants' +import { DatabaseAgent } from '../../db' +import { DatabaseChangeStream } from '.' + +export class ConfChangeStream { + static initialize() { + DatabaseChangeStream.onStreamChange( + CONFIG_COLLECTION, + this.updateEnvironments, + ) + } + + private static async updateEnvironments() { + const conf = await DatabaseAgent.db + .collection(CONFIG_COLLECTION) + .findOne({}) + + if (!conf) { + return + } + + const environments = conf.environments || [] + for (const env of environments) { + process.env[env.name] = env.value + } + } +} diff --git a/runtimes/nodejs/src/support/database-change-stream/index.ts b/runtimes/nodejs/src/support/database-change-stream/index.ts new file mode 100644 index 0000000000..54a48d4ba8 --- /dev/null +++ b/runtimes/nodejs/src/support/database-change-stream/index.ts @@ -0,0 +1,70 @@ +import EventEmitter from 'events' +import { DatabaseAgent } from '../../db' +import { logger } from '../logger' +import Config from '../../config' +import { + CLOUD_FUNCTION_COLLECTION, + CONFIG_COLLECTION, + WEBSITE_HOSTING_COLLECTION, +} from '../../constants' + +const collectionsToWatch = [ + CONFIG_COLLECTION, + CLOUD_FUNCTION_COLLECTION, + WEBSITE_HOSTING_COLLECTION, +] as const +export class DatabaseChangeStream extends EventEmitter { + private static instance: DatabaseChangeStream + + private constructor() { + super() + } + + static getInstance() { + if (!this.instance) { + this.instance = new DatabaseChangeStream() + } + return this.instance + } + + initializeForCollection(collectionName: string) { + const stream = DatabaseAgent.db.collection(collectionName).watch() + + stream.on('change', (change) => { + this.emit(collectionName, change) + }) + + stream.once('close', () => { + stream.off('change', this.emit) + logger.error(`${collectionName} collection change stream closed.`) + + setTimeout(() => { + logger.info( + `Reconnecting ${collectionName} collection change stream...`, + ) + this.initializeForCollection(collectionName) + }, Config.CHANGE_STREAM_RECONNECT_INTERVAL) + }) + } + + static initialize() { + const instance = DatabaseChangeStream.getInstance() + + collectionsToWatch.forEach((collectionName) => { + instance.initializeForCollection(collectionName) + }) + } + + static onStreamChange( + collectionName: (typeof collectionsToWatch)[number], + listener: (...args: any[]) => void, + ) { + const instance = DatabaseChangeStream.getInstance() + instance.on(collectionName, listener) + } + + static removeAllListeners() { + const instance = DatabaseChangeStream.getInstance() + instance.removeAllListeners() + } +} diff --git a/runtimes/nodejs/src/support/database-change-stream/website-hosting-change-stream.ts b/runtimes/nodejs/src/support/database-change-stream/website-hosting-change-stream.ts new file mode 100644 index 0000000000..45dddc14ee --- /dev/null +++ b/runtimes/nodejs/src/support/database-change-stream/website-hosting-change-stream.ts @@ -0,0 +1,22 @@ +import { DatabaseChangeStream } from '.' +import { WEBSITE_HOSTING_COLLECTION } from '../../constants' +import { DatabaseAgent } from '../../db' + +export class WebsiteHostingChangeStream { + static websiteHosting = [] + + static initialize() { + DatabaseChangeStream.onStreamChange( + WEBSITE_HOSTING_COLLECTION, + this.onStreamChange.bind(this), + ) + } + + static async onStreamChange() { + const websiteHosting = await DatabaseAgent.db + .collection(WEBSITE_HOSTING_COLLECTION) + .find() + .toArray() + this.websiteHosting = websiteHosting + } +} diff --git a/runtimes/nodejs/src/support/db-change-stream.ts b/runtimes/nodejs/src/support/db-change-stream.ts deleted file mode 100644 index 2268e3225b..0000000000 --- a/runtimes/nodejs/src/support/db-change-stream.ts +++ /dev/null @@ -1,54 +0,0 @@ -import Config from '../config' -import { CONFIG_COLLECTION } from '../constants' -import { DatabaseAgent } from '../db' - -import { logger } from './logger' - -export class DatabaseChangeStream { - static async initialize() { - DatabaseChangeStream.watchConf() - } - - /** - * stream the change of cloud function - * @param - * @returns - */ - static async watchConf() { - logger.info('Listening for changes in conf collection...') - DatabaseChangeStream.updateEnvironments() - - const stream = DatabaseAgent.db.collection(CONFIG_COLLECTION).watch() - - const changeEvent = async (_change) => { - DatabaseChangeStream.updateEnvironments() - } - - stream.on('change', changeEvent) - - stream.once('close', () => { - stream.off('change', changeEvent) - logger.error('Conf collection change stream closed.') - - setTimeout(() => { - logger.info('Reconnecting conf collection change stream...') - DatabaseChangeStream.watchConf() - }, Config.CHANGE_STREAM_RECONNECT_INTERVAL) - }) - } - - private static async updateEnvironments() { - const conf = await DatabaseAgent.db - .collection(CONFIG_COLLECTION) - .findOne({}) - - if (!conf) { - return - } - - const environments = conf.environments || [] - for (const env of environments) { - process.env[env.name] = env.value - } - } -} diff --git a/runtimes/nodejs/src/support/function-engine/cache.ts b/runtimes/nodejs/src/support/function-engine/cache.ts index 36fc2ee925..c0a7dc677b 100644 --- a/runtimes/nodejs/src/support/function-engine/cache.ts +++ b/runtimes/nodejs/src/support/function-engine/cache.ts @@ -1,11 +1,11 @@ import { CLOUD_FUNCTION_COLLECTION } from '../../constants' import { DatabaseAgent } from '../../db' -import { ICloudFunctionData, RequireFuncType } from './types' +import { FunctionContext, ICloudFunctionData, RequireFuncType } from './types' import { FunctionRequire } from './require' import { logger } from '../logger' import assert from 'assert' import { InitHook } from '../init-hook' -import Config from '../../config' +import { DatabaseChangeStream } from '../database-change-stream' export class FunctionCache { private static cache: Map = new Map() @@ -21,7 +21,10 @@ export class FunctionCache { FunctionCache.cache.set(func.name, func) } - FunctionCache.streamChange() + DatabaseChangeStream.onStreamChange( + CLOUD_FUNCTION_COLLECTION, + this.streamChange.bind(this), + ) logger.info('Function cache initialized.') // invoke init function @@ -33,7 +36,11 @@ export class FunctionCache { * @param module * @returns */ - static requireCloudFunction(moduleName: string, fromModules?: string[]): any { + static requireCloudFunction( + moduleName: string, + fromModules: string[], + ctx: FunctionContext, + ): any { const func = FunctionCache.cache.get(moduleName) assert( func, @@ -44,6 +51,7 @@ export class FunctionCache { func.name, func.source.compiled, fromModules, + ctx, ) return module } @@ -53,41 +61,22 @@ export class FunctionCache { * @param * @returns */ - static async streamChange() { - logger.info('Listening for changes in cloud function collection...') - const stream = DatabaseAgent.db - .collection(CLOUD_FUNCTION_COLLECTION) - .watch() - - const changeEvent = async (change) => { - if (change.operationType === 'insert') { - const func = await DatabaseAgent.db - .collection(CLOUD_FUNCTION_COLLECTION) - .findOne({ _id: change.documentKey._id }) + static async streamChange(change) { + if (change.operationType === 'insert') { + const func = await DatabaseAgent.db + .collection(CLOUD_FUNCTION_COLLECTION) + .findOne({ _id: change.documentKey._id }) - // add func in map - FunctionCache.cache.set(func.name, func) - } else if (change.operationType == 'delete') { - // remove this func - for (const [funcName, func] of this.cache) { - if (change.documentKey._id.equals(func._id)) { - this.cache.delete(funcName) - } + // add func in map + FunctionCache.cache.set(func.name, func) + } else if (change.operationType == 'delete') { + // remove this func + for (const [funcName, func] of this.cache) { + if (change.documentKey._id.equals(func._id)) { + this.cache.delete(funcName) } } } - - stream.on('change', changeEvent) - - stream.once('close', () => { - logger.error('Cloud function change stream closed...') - stream.off('change', changeEvent) - - setTimeout(() => { - logger.info('Reconnecting cloud function change stream......') - FunctionCache.streamChange() - }, Config.CHANGE_STREAM_RECONNECT_INTERVAL) - }) } /** @@ -98,7 +87,8 @@ export class FunctionCache { */ static requireFunc: RequireFuncType = ( module: string, - fromModules?: string[], + fromModules: string[], + ctx: FunctionContext, ): any => { if (module === '@/cloud-sdk') { return require('@lafjs/cloud') @@ -116,7 +106,7 @@ export class FunctionCache { ) } - return FunctionCache.requireCloudFunction(cloudModule, fromModules) + return FunctionCache.requireCloudFunction(cloudModule, fromModules, ctx) } return require(module) as any } diff --git a/runtimes/nodejs/src/support/function-engine/console.ts b/runtimes/nodejs/src/support/function-engine/console.ts index ae2f8b8236..aa60cb4099 100644 --- a/runtimes/nodejs/src/support/function-engine/console.ts +++ b/runtimes/nodejs/src/support/function-engine/console.ts @@ -10,20 +10,25 @@ export class FunctionConsole { if (!Config.LOG_SERVER_URL || !Config.LOG_SERVER_TOKEN) return const doc = { - request_id: ctx.requestId, + request_id: ctx.requestId || '', func: ctx.__function_name, + is_required: ctx.__is_required || false, data: message, created_at: new Date(), } - axios.post(`${Config.LOG_SERVER_URL}/function/log`, { - appid: Config.APPID, - log: doc, - }, { - headers: { - 'x-token': Config.LOG_SERVER_TOKEN - } - }) + axios.post( + `${Config.LOG_SERVER_URL}/function/log`, + { + appid: Config.APPID, + log: doc, + }, + { + headers: { + 'x-token': Config.LOG_SERVER_TOKEN, + }, + }, + ) } constructor(ctx: FunctionContext) { diff --git a/runtimes/nodejs/src/support/function-engine/engine.ts b/runtimes/nodejs/src/support/function-engine/engine.ts index 2d9b050613..34c060ddc5 100644 --- a/runtimes/nodejs/src/support/function-engine/engine.ts +++ b/runtimes/nodejs/src/support/function-engine/engine.ts @@ -68,7 +68,7 @@ export class FunctionEngine { const wrapped = ` const require = (module) => { fromModules.push(__filename) - return requireFunc(module, fromModules) + return requireFunc(module, fromModules, __context__) } ${code}; const __main__ = exports.main || exports.default diff --git a/runtimes/nodejs/src/support/function-engine/require.ts b/runtimes/nodejs/src/support/function-engine/require.ts index 7c47b66fd6..3b6363f369 100644 --- a/runtimes/nodejs/src/support/function-engine/require.ts +++ b/runtimes/nodejs/src/support/function-engine/require.ts @@ -1,4 +1,4 @@ -import { RequireFuncType } from './types' +import { FunctionContext, RequireFuncType } from './types' import { FunctionVm } from './vm' const defaultRequireFunction: RequireFuncType = (module): any => { @@ -20,13 +20,23 @@ export class FunctionRequire { * @param code * @returns */ - load(name: string, code: string, fromModules: string[]): any { + load( + name: string, + code: string, + fromModules: string[], + ctx: FunctionContext, + ): any { const context = { + ...ctx, __function_name: name, - requestId: '', + __is_required: true, } - const sandbox = FunctionVm.buildSandbox(context, this.requireFunc, fromModules) + const sandbox = FunctionVm.buildSandbox( + context, + this.requireFunc, + fromModules, + ) const wrapped = this.warp(code) const script = FunctionVm.createVM(wrapped, {}) return script.runInNewContext(sandbox, {}) @@ -41,7 +51,7 @@ export class FunctionRequire { return ` const require = (module) => { fromModules.push(__filename) - return requireFunc(module, fromModules) + return requireFunc(module, fromModules, __context__) } const exports = {}; ${code} diff --git a/runtimes/nodejs/src/support/function-engine/types.ts b/runtimes/nodejs/src/support/function-engine/types.ts index ace121e61c..2453e01264 100644 --- a/runtimes/nodejs/src/support/function-engine/types.ts +++ b/runtimes/nodejs/src/support/function-engine/types.ts @@ -4,7 +4,11 @@ import { Request, Response } from 'express' import { ObjectId } from 'mongodb' import WebSocket = require('ws') -export type RequireFuncType = (module: string, fromModules?: string[]) => any +export type RequireFuncType = ( + module: string, + fromModules: string[], + ctx: FunctionContext, +) => any /** * vm run context (global) @@ -26,7 +30,7 @@ export interface RuntimeContext { process: { env: { [key: string]: string } } - global: RuntimeContext, + global: RuntimeContext fromModules: string[] } @@ -50,6 +54,7 @@ export interface FunctionContext { request?: Request response?: Response __function_name?: string + __is_required: boolean [key: string]: any }