Skip to content

Commit

Permalink
feat(cloudfunction): export response object to function; refactor inv…
Browse files Browse the repository at this point in the history
…oke result, directly return what function return;
  • Loading branch information
maslow committed Sep 13, 2021
1 parent 516e79e commit 902edb4
Show file tree
Hide file tree
Showing 14 changed files with 920 additions and 174 deletions.
2 changes: 1 addition & 1 deletion packages/app-service/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions packages/app-service/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-07-30 10:30:29
* @LastEditTime: 2021-08-18 13:19:45
* @LastEditTime: 2021-09-13 22:00:08
* @Description:
*/

Expand Down Expand Up @@ -37,14 +37,15 @@ server.all('*', function (_req, res, next) {
/**
* Parsing bearer token
*/
server.use(function (req, _res, next) {
server.use(function (req, res, next) {
const token = splitBearerToken(req.headers['authorization'] ?? '')
const auth = parseToken(token) || null
req['auth'] = auth

const requestId = req['requestId'] = uuidv4()
logger.info(requestId, `${req.method} "${req.url}" - referer: ${req.get('referer') || '-'} ${req.get('user-agent')}`)
logger.trace(requestId, `${req.method} ${req.url}`, { body: req.body, headers: req.headers, auth })
res.set('requestId', requestId)
next()
})

Expand Down
91 changes: 91 additions & 0 deletions packages/app-service/src/router/function/debug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-07-30 10:30:29
* @LastEditTime: 2021-09-13 21:01:37
* @Description:
*/

import { Request, Response } from 'express'
import { FunctionContext, CloudFunction } from 'cloud-function-engine'
import { getFunctionByName } from '../../api/function'
import { parseToken } from '../../lib/utils/token'
import { logger } from '../../lib/logger'
import { addFunctionLog } from '../../api/function-log'

/**
* Handler of debugging cloud function
*/
export async function handleDebugFunction(req: Request, res: Response) {

const requestId = req['requestId']
const func_name = req.params?.name
const debug_token = req.get('debug-token') ?? undefined

// verify the debug token
const parsed = parseToken(debug_token as string)
if (!parsed || parsed.type !== 'debug') {
return res.status(403).send('permission denied: invalid debug token')
}

// load function data from db
const funcData = await getFunctionByName(func_name)
if (!funcData) {
return res.send({ code: 1, error: 'function not found', requestId })
}

const func = new CloudFunction(funcData)

// compile the func while func hadn't been compiled
if (!func.compiledCode) {
func.compile2js()
}

try {
// execute the func
const ctx: FunctionContext = {
query: req.query,
files: req.files as any,
body: req.body,
headers: req.headers,
method: req.method,
auth: req['auth'],
requestId,
response: res
}
const result = await func.invoke(ctx)

// log this execution to db
await addFunctionLog({
requestId: requestId,
func_id: func.id,
func_name: func_name,
logs: result.logs,
time_usage: result.time_usage,
created_at: Date.now(),
updated_at: Date.now(),
created_by: req['auth']?.uid,
data: result.data,
error: result.error,
debug: true
})

if (result.error) {
logger.error(requestId, `debug function ${func_name} error: `, result)
return res.send({
error: 'invoke function got error: ' + result.error.toString(),
logs: result.logs,
time_usage: result.time_usage,
requestId
})
}

logger.trace(requestId, `invoke ${func_name} invoke success: `, result)

if (res.writableEnded === false) {
return res.send(result.data)
}
} catch (error) {
logger.error(requestId, 'failed to invoke error', error)
return res.status(500).send('Internal Server Error')
}
}
124 changes: 12 additions & 112 deletions packages/app-service/src/router/function/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-07-30 10:30:29
* @LastEditTime: 2021-08-18 15:40:09
* @LastEditTime: 2021-09-13 20:16:56
* @Description:
*/

import { Request, Response, Router } from 'express'
import { FunctionContext, CloudFunction } from 'cloud-function-engine'
import { Router } from 'express'
import { CloudFunction } from 'cloud-function-engine'
import * as multer from 'multer'
import * as path from 'path'
import * as uuid from 'uuid'
import { getFunctionByName } from '../../api/function'
import { DatabaseAgent } from '../../lib/database'
import { Constants } from '../../constants'
import { parseToken } from '../../lib/utils/token'
import Config from '../../config'
import { logger } from '../../lib/logger'
import { handleDebugFunction } from './debug'
import { handleInvokeFunction } from './invoke'

/**
* Custom require function in cloud function
Expand All @@ -30,8 +26,6 @@ CloudFunction.require_func = (module): any => {
return require(module) as any
}

const db = DatabaseAgent.db

export const FunctionRouter = Router()

/**
Expand All @@ -47,114 +41,20 @@ const uploader = multer({
})

/**
* Invoke cloud function through HTTP request.
* Using `/invoke` prefix in URI, which support files uploading.
* Debug cloud function through HTTP request.
* @method POST
*/
FunctionRouter.post('/invoke/:name', uploader.any(), handleInvokeFunction)
FunctionRouter.post('/debug/:name', uploader.any(), handleDebugFunction)

/**
* Invoke cloud function through HTTP request.
* Alias for `/invoke/:name` but no files uploading support.
* @method *
*/
FunctionRouter.all('/:name', handleInvokeFunction)
FunctionRouter.all('/:name', uploader.any(), handleInvokeFunction)

/**
* Handler of invoking cloud function
* Invoke cloud function through HTTP request.
* Alias for `/:name` for fallback to old version api
* @method *
*/
async function handleInvokeFunction(req: Request, res: Response) {
const requestId = req['requestId']
const func_name = req.params?.name
const debug = req.get('debug-token') ?? undefined

// verify the debug token
if (debug) {
const parsed = parseToken(debug as string)
if (!parsed || parsed.type !== 'debug') {
return res.status(403).send('permission denied: invalid debug token')
}
}

// load function data from db
const funcData = await getFunctionByName(func_name)
if (!funcData) {
return res.send({ code: 1, error: 'function not found', requestId })
}

const func = new CloudFunction(funcData)

// reject while no HTTP enabled (except debug mode)
if (!func.enableHTTP && !debug) {
return res.status(404).send('Not Found')
}

// reject while func was disabled (except debug mode)
if (1 !== func.status && !debug) {
return res.status(404).send('Not Found')
}

// compile the func while in debug mode or func hadn't been compiled
if (debug || !func.compiledCode) {
func.compile2js()

await db.collection(Constants.function_collection)
.doc(func.id)
.update({ compiledCode: func.compiledCode, updated_at: Date.now() })
}

try {
// execute the func
const ctx: FunctionContext = {
query: req.query,
files: req.files as any,
body: req.body,
headers: req.headers,
method: req.method,
auth: req['auth'],
requestId,
}
const result = await func.invoke(ctx)

// log this execution to db
const shouldLog = Config.ENABLE_CLOUD_FUNCTION_LOG === 'always' || (Config.ENABLE_CLOUD_FUNCTION_LOG === 'debug' && debug)
if (shouldLog) {
await db.collection(Constants.function_log_collection)
.add({
requestId: requestId,
func_id: func.id,
func_name: func_name,
logs: result.logs,
time_usage: result.time_usage,
created_at: Date.now(),
updated_at: Date.now(),
created_by: req['auth']?.uid,
data: result.data,
error: result.error,
debug: debug ? true : false
})
}

if (result.error) {
logger.error(requestId, `/func/${func_name} invoke error: `, result)
return res.send({
error: 'invoke function got error',
logs: debug ? result.logs : undefined,
time_usage: debug ? result.time_usage : undefined,
requestId
})
}

logger.trace(requestId, `/func/${func_name} invoke success: `, result)

return res.send({
requestId,
data: result.data,
time_usage: debug ? result.time_usage : undefined,
logs: debug ? result.logs : undefined
})
} catch (error) {
logger.error(requestId, 'failed to invoke error', error)
return res.status(500).send('Internal Server Error')
}
}
FunctionRouter.all('/invoke/:name', uploader.any(), handleInvokeFunction)
91 changes: 91 additions & 0 deletions packages/app-service/src/router/function/invoke.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* @Author: Maslow<wangfugen@126.com>
* @Date: 2021-07-30 10:30:29
* @LastEditTime: 2021-09-13 22:09:23
* @Description:
*/

import { Request, Response } from 'express'
import { FunctionContext, CloudFunction } from 'cloud-function-engine'
import { getFunctionByName } from '../../api/function'
import Config from '../../config'
import { logger } from '../../lib/logger'
import { addFunctionLog } from '../../api/function-log'


/**
* Handler of invoking cloud function
*/
export async function handleInvokeFunction(req: Request, res: Response) {
const requestId = req['requestId']
const func_name = req.params?.name

// load function data from db
const funcData = await getFunctionByName(func_name)
if (!funcData) {
return res.send({ code: 1, error: 'function not found', requestId })
}

const func = new CloudFunction(funcData)

// reject while no HTTP enabled
if (!func.enableHTTP) {
return res.status(404).send('Not Found')
}

// reject while func was disabled
if (1 !== func.status) {
return res.status(404).send('Not Found')
}

try {
// execute the func
const ctx: FunctionContext = {
query: req.query,
files: req.files as any,
body: req.body,
headers: req.headers,
method: req.method,
auth: req['auth'],
requestId,
response: res,
}
const result = await func.invoke(ctx)

// log this execution to db
if (Config.ENABLE_CLOUD_FUNCTION_LOG === 'always') {
await addFunctionLog({
requestId: requestId,
func_id: func.id,
func_name: func_name,
logs: result.logs,
time_usage: result.time_usage,
created_at: Date.now(),
updated_at: Date.now(),
created_by: req['auth']?.uid,
data: result.data,
error: result.error,
debug: false
})
}

if (result.error) {
logger.error(requestId, `invoke function ${func_name} invoke error: `, result)

return res.status(400).send({
error: 'invoke function got error',
detail: Config.isProd ? undefined : result.error,
logs: Config.isProd ? undefined : result.logs
})
}

logger.trace(requestId, `invoke function ${func_name} invoke success: `, result)

if (res.writableEnded === false) {
return res.send(result.data)
}
} catch (error) {
logger.error(requestId, 'failed to invoke error', error)
return res.status(500).send('Internal Server Error')
}
}
Loading

0 comments on commit 902edb4

Please sign in to comment.