Compare commits

..

No commits in common. "master" and "fix/error-log" have entirely different histories.

14 changed files with 192 additions and 152 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@ node_modules
package-lock.json package-lock.json
.idea .idea
.vscode .vscode
lib

View File

@ -1,7 +1,8 @@
node_modules node_modules
.s .s
tsconfig.json
LICENSE LICENSE
package-lock.json package-lok.json
.prettierrc.js .prettierrc.js
.gitignore .gitignore
@ -14,4 +15,3 @@ package-lock.json
README.md README.md
./example ./example
./.signore ./.signore
./src

View File

@ -23,9 +23,9 @@ services:
```` ````
$ s invoke --invocation-type sync --event ${payload} $ s exec -- invoke --invocation-type sync --event ${payload}
$ s invoke --invocation-type async --event-file ${path} $ s exec -- invoke --invocation-type async --event-file ${path}
$ s invoke --event-stdin $ s exec -- invoke --event-stdin
```` ````
## CLI 用法 ## CLI 用法

51
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,16 +1,11 @@
{ {
"body": 123,
"method": "POST", "method": "POST",
"headers": { "headers": {
"key": "value", "key": "value"
"Content-Type": "application/json"
}, },
"queries": { "queries": {
"key": "value" "key": "value"
}, },
"body": { "path": "string"
"abc": "body",
"tmpNasZipPath": "/tmpNasZipPath",
"body": "body"
},
"path": "download"
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "fc-remote-invoke", "name": "fc-remote-invoke",
"version": "0.0.22", "version": "0.0.17",
"description": "This is a component demo for Serverless Devs Tool ", "description": "This is a component demo for Serverless Devs Tool ",
"keywords": [ "keywords": [
"Serverless", "Serverless",
@ -13,7 +13,7 @@
"author": "Serverless-Devs", "author": "Serverless-Devs",
"contributors": [], "contributors": [],
"license": "MIT", "license": "MIT",
"main": "./dist/index.js", "main": "./lib/index.js",
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
@ -24,17 +24,15 @@
"scripts": { "scripts": {
"start": "npm run watch", "start": "npm run watch",
"watch": "tsc -w", "watch": "tsc -w",
"prebuild": "rm -rf node_modules && rm -rf package-lock.json && npm i && rimraf dist", "prebuild": "rm -rf node_modules && rm -rf package-lock.json && npm i && rimraf lib",
"esbuild": "esbuild src/index.ts --bundle --log-level=error --minify --platform=node --format=cjs --target=node10.4 --external:@serverless-devs/core --outfile=dist/index.js", "build": "ncc build src/index.ts -m -e @serverless-devs/core -o lib"
"build": "npm run esbuild"
}, },
"dependencies": { "dependencies": {
"@serverless-devs/core": "latest", "@alicloud/fc2": "^2.2.2",
"form-data": "^4.0.0", "@serverless-devs/core": "^0.0.*",
"fs-extra": "^10.0.0", "fs-extra": "^10.0.0",
"got": "^11.8.2", "got": "^11.8.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"qs": "^6.10.1",
"readline": "^1.3.0" "readline": "^1.3.0"
}, },
"autoInstall": false, "autoInstall": false,
@ -47,6 +45,6 @@
"ts-node": "^8.10.2", "ts-node": "^8.10.2",
"typedoc": "^0.20.35", "typedoc": "^0.20.35",
"typescript": "^3.9.7", "typescript": "^3.9.7",
"esbuild": "^0.14.0" "@vercel/ncc": "^0.24.0"
} }
} }

View File

@ -2,7 +2,7 @@ Type: Component
Name: fc-remote-invoke Name: fc-remote-invoke
Provider: Provider:
- 其它 - 其它
Version: 0.0.22 Version: 0.0.17
Description: 初始化component模板 Description: 初始化component模板
HomePage: https://www.serverless-devs.com HomePage: https://www.serverless-devs.com
Tags: #标签详情 Tags: #标签详情

View File

@ -75,9 +75,9 @@ export default [
content: [ content: [
'$ s invoke', '$ s invoke',
'$ s <ProjectName> invoke', '$ s <ProjectName> invoke',
'$ s invoke --invocation-type sync --event <payload>', '$ s exec -- invoke --invocation-type sync --event <payload>',
'$ s invoke --event-file <file-path>', '$ s exec -- invoke --event-file <file-path>',
'$ s invoke --event-stdin', '$ s exec -- invoke --event-stdin',
], ],
}, },
{ {

View File

@ -5,6 +5,7 @@ import HELP from './common/help';
import { InputProps, isProperties, IProperties } from './interface/entity'; import { InputProps, isProperties, IProperties } from './interface/entity';
// import StdoutFormatter from './common/stdout-formatter'; // import StdoutFormatter from './common/stdout-formatter';
import RemoteInvoke from './lib/remote-invoke'; import RemoteInvoke from './lib/remote-invoke';
import Client from './lib/client';
export default class FcRemoteInvoke { export default class FcRemoteInvoke {
/** /**
@ -19,7 +20,6 @@ export default class FcRemoteInvoke {
credentials, credentials,
isHelp, isHelp,
invocationType, invocationType,
statefulAsyncInvocationId,
} = await this.handlerInputs(inputs); } = await this.handlerInputs(inputs);
await this.report('fc-remote-invoke', 'invoke', credentials?.AccountID); await this.report('fc-remote-invoke', 'invoke', credentials?.AccountID);
@ -30,11 +30,10 @@ export default class FcRemoteInvoke {
let fcClient; let fcClient;
if (!props.domainName) { if (!props.domainName) {
const fcCommon = await core.loadComponent('devsapp/fc-common'); fcClient = await Client.buildFcClient(props.region, credentials);
fcClient = await fcCommon.makeFcClient({ ...inputs, props: { region: props.region }});
} }
const remoteInvoke = new RemoteInvoke(fcClient, credentials.AccountID); const remoteInvoke = new RemoteInvoke(fcClient, credentials.AccountID);
await remoteInvoke.invoke(props, eventPayload, { invocationType, statefulAsyncInvocationId }); await remoteInvoke.invoke(props, eventPayload, { invocationType });
} }
private async report(componentName: string, command: string, accountID: string): Promise<void> { private async report(componentName: string, command: string, accountID: string): Promise<void> {
@ -51,12 +50,11 @@ export default class FcRemoteInvoke {
const parsedArgs: {[key: string]: any} = core.commandParse({ ...inputs, args }, { const parsedArgs: {[key: string]: any} = core.commandParse({ ...inputs, args }, {
boolean: ['help', 'event-stdin'], boolean: ['help', 'event-stdin'],
string: ['invocation-type', 'event', 'event-file', 'region', 'domain-name','service-name', 'function-name', 'qualifier', 'stateful-async-invocation-id'], string: ['invocation-type', 'event', 'event-file', 'region', 'domain-name','service-name', 'function-name', 'qualifier'],
alias: { alias: {
'help': 'h', 'help': 'h',
'event': 'e', 'event': 'e',
'event-file': 'f', 'event-file': 'f',
'event-stdin': 's',
} }
}); });
@ -75,7 +73,6 @@ export default class FcRemoteInvoke {
'event-stdin': eventStdin, 'event-stdin': eventStdin,
'invocation-type': invocationType = 'sync', 'invocation-type': invocationType = 'sync',
'domain-name': domainName, 'domain-name': domainName,
'stateful-async-invocation-id': statefulAsyncInvocationId,
} = argsData; } = argsData;
const eventPayload = { event, eventFile, eventStdin }; const eventPayload = { event, eventFile, eventStdin };
// @ts-ignore: 判断三个值有几个真 // @ts-ignore: 判断三个值有几个真
@ -114,7 +111,6 @@ export default class FcRemoteInvoke {
eventPayload, eventPayload,
isHelp: false, isHelp: false,
invocationType: _.upperFirst(invocationType), invocationType: _.upperFirst(invocationType),
statefulAsyncInvocationId,
}; };
} }

114
src/lib/client.ts Normal file
View File

@ -0,0 +1,114 @@
import FC from '@alicloud/fc2';
import querystring from 'querystring';
import kitx from 'kitx';
import httpx from 'httpx';
import * as core from '@serverless-devs/core';
import { ICredentials } from '../interface/entity';
FC.prototype.costom_request = async function (method, path, query, body, headers = {}, opts = {}) {
var url = `${this.endpoint}/${this.version}${path}`;
if (query && Object.keys(query).length > 0) {
url = `${url}?${querystring.stringify(query)}`;
}
headers = Object.assign(this.buildHeaders(), this.headers, headers);
var postBody;
if (body) {
var buff = null;
if (Buffer.isBuffer(body)) {
buff = body;
headers['content-type'] = 'application/octet-stream';
} else if (typeof body === 'string') {
buff = new Buffer(body, 'utf8');
headers['content-type'] = 'application/octet-stream';
} else if ('function' === typeof body.pipe) {
buff = body;
headers['content-type'] = 'application/octet-stream';
} else {
buff = new Buffer(JSON.stringify(body), 'utf8');
headers['content-type'] = 'application/json';
}
if ('function' !== typeof body.pipe) {
const digest = kitx.md5(buff, 'hex');
const md5 = new Buffer(digest, 'utf8').toString('base64');
headers['content-length'] = buff.length;
headers['content-md5'] = md5;
}
postBody = buff;
}
var queriesToSign = null;
if (path.startsWith('/proxy/')) {
queriesToSign = query || {};
}
var signature = FC.getSignature(this.accessKeyID, this.accessKeySecret, method, `/${this.version}${path}`, headers, queriesToSign);
headers['authorization'] = signature;
const response = await httpx.request(url, {
method,
timeout: this.timeout,
headers,
data: postBody
});
var responseBody;
if (!opts['rawBuf'] || response.headers['x-fc-error-type']) {
responseBody = await httpx.read(response, 'utf8');
} else {
// @ts-ignore: .
responseBody = await httpx.read(response);
}
const contentType = response.headers['content-type'] || '';
if (contentType.startsWith('application/json')) {
try {
responseBody = JSON.parse(responseBody);
} catch (ex) {}
}
let err;
if (response.statusCode < 200 || response.statusCode >= 300) {
const code = response.statusCode;
const requestid = response.headers['x-fc-request-id'];
var errMsg;
if (responseBody.ErrorMessage) {
errMsg = responseBody.ErrorMessage;
} else {
errMsg = responseBody.errorMessage;
}
err = new Error(`${method} ${path} failed with ${code}. requestid: ${requestid}, message: ${errMsg}.`);
err.name = `FC${responseBody.ErrorCode}Error`;
// @ts-ignore: .
err.code = responseBody.ErrorCode;
}
return {
err,
code: response.statusCode,
'headers': response.headers,
'data': responseBody,
};
}
export default class Client {
static async buildFcClient(region: string, credentials: ICredentials) {
return new FC(credentials.AccountID, {
accessKeyID: credentials.AccessKeyID,
accessKeySecret: credentials.AccessKeySecret,
securityToken: credentials.SecurityToken,
region,
endpoint: await this.getFcEndpoint(),
timeout: 6000000,
})
}
private static async getFcEndpoint(): Promise<string | undefined> {
const fcDefault = await core.loadComponent('devsapp/fc-default');
const fcEndpoint: string = await fcDefault.get({ args: 'fc-endpoint' });
if (!fcEndpoint) { return undefined; }
const enableFcEndpoint: any = await fcDefault.get({ args: 'enable-fc-endpoint' });
return (enableFcEndpoint === true || enableFcEndpoint === 'true') ? fcEndpoint : undefined;
}
}

View File

@ -16,10 +16,9 @@ export default class File {
let input; let input;
if (eventFile === '-') { // read from stdin if (eventFile === '-') { // read from stdin
logger.log('Reading event data from stdin, which can be ended with Enter then Ctrl+D'); logger.log('Reading event data from stdin, which can be ended with Enter then Ctrl+D')
input = process.stdin; input = process.stdin;
} else { } else {
logger.log('Reading event file content:');
input = fs.createReadStream(eventFile, { input = fs.createReadStream(eventFile, {
encoding: 'utf-8' encoding: 'utf-8'
}) })
@ -33,10 +32,7 @@ export default class File {
rl.on('line', (line) => { rl.on('line', (line) => {
event += line event += line
}) })
rl.on('close', () => { rl.on('close', () => resolve(event))
logger.log('');
resolve(event)
})
rl.on('SIGINT', () => reject(new Error('^C'))) rl.on('SIGINT', () => reject(new Error('^C')))
}) })

View File

@ -1,34 +0,0 @@
import _ from 'lodash';
import qs from 'qs';
import FormData from 'form-data';
export default function handlerBody(contentType: string, body: any) {
if (contentType.includes('text/') || contentType.includes('application/json') || contentType.includes('application/xml')) {
if (_.isString(body)) return body;
try {
return JSON.stringify(body);
} catch (_ex) {
return body.toString();
}
}
if (contentType.includes('application/x-www-form-urlencoded')) {
return qs.stringify(body, { indices: false });
}
if (contentType.includes('multipart/form-data')) {
const form = new FormData();
try {
const newBody = _.isObject(body) ? body : JSON.parse(body);
for (const [key, value] of Object.entries(newBody)) {
form.append(key, value);
}
return form;
} catch (_ex) {
throw new Error(`Handler body error: The request header is ${contentType}, but the request body is not an object`);
}
}
return body;
}

View File

@ -13,7 +13,7 @@ export default class RemoteInvoke {
this.accountId = accountId; this.accountId = accountId;
} }
async invoke (props: IProperties, eventPayload: IEventPayload, { invocationType, statefulAsyncInvocationId }) { async invoke (props: IProperties, eventPayload: IEventPayload, { invocationType }) {
const event = await Event.eventPriority(eventPayload); const event = await Event.eventPriority(eventPayload);
logger.debug(`event: ${event}`); logger.debug(`event: ${event}`);
@ -32,7 +32,6 @@ export default class RemoteInvoke {
const payload: any = { event, serviceName, functionName, qualifier }; const payload: any = { event, serviceName, functionName, qualifier };
if (_.isEmpty(httpTriggers)) { if (_.isEmpty(httpTriggers)) {
payload.invocationType = invocationType; payload.invocationType = invocationType;
payload.statefulAsyncInvocationId = statefulAsyncInvocationId;
payload.event = event; payload.event = event;
await this.eventInvoke(payload); await this.eventInvoke(payload);
} else { } else {
@ -75,15 +74,13 @@ export default class RemoteInvoke {
functionName, functionName,
event, event,
qualifier = 'LATEST', qualifier = 'LATEST',
invocationType, invocationType
statefulAsyncInvocationId
}) { }) {
if (invocationType === 'Sync') { if (invocationType === 'Sync') {
const rs = await this.fcClient.invokeFunction(serviceName, functionName, event, { const rs = await this.fcClient.invokeFunction(serviceName, functionName, event, {
'X-Fc-Log-Type': 'Tail', 'X-Fc-Log-Type': 'Tail',
'X-Fc-Invocation-Code-Version': 'Latest', 'X-Fc-Invocation-Type': invocationType
'X-Fc-Invocation-Type': invocationType,
}, qualifier); }, qualifier);
this.showLog(rs.headers['x-fc-log-result']); this.showLog(rs.headers['x-fc-log-result']);
@ -91,11 +88,8 @@ export default class RemoteInvoke {
console.log(rs.data); console.log(rs.data);
console.log('\n'); console.log('\n');
} else { } else {
logger.debug(`Stateful async invocation id: ${statefulAsyncInvocationId}`);
const { headers } = await this.fcClient.invokeFunction(serviceName, functionName, event, { const { headers } = await this.fcClient.invokeFunction(serviceName, functionName, event, {
'X-Fc-Invocation-Code-Version': 'Latest', 'X-Fc-Invocation-Type': invocationType
'X-Fc-Invocation-Type': invocationType,
'X-Fc-Stateful-Async-Invocation-Id': statefulAsyncInvocationId || "",
}, qualifier); }, qualifier);
const rId = headers['x-fc-request-id']; const rId = headers['x-fc-request-id'];
@ -107,7 +101,7 @@ export default class RemoteInvoke {
const q = qualifier ? `.${qualifier}` : ''; const q = qualifier ? `.${qualifier}` : '';
event.path = `/proxy/${serviceName}${q}/${functionName}/${event.path || ''}`; event.path = `/proxy/${serviceName}${q}/${functionName}/${event.path || ''}`;
logger.log(`Request url: https://${this.accountId}.${region}.fc.aliyuncs.com/2016-08-15/proxy/${serviceName}${q}/${functionName}/`); logger.log(`https://${this.accountId}.${region}.fc.aliyuncs.com/2016-08-15/proxy/${serviceName}${q}/${functionName}/`);
await this.request(event) await this.request(event)
} }
@ -116,13 +110,7 @@ export default class RemoteInvoke {
* path /proxy/serviceName/functionName/path , * path /proxy/serviceName/functionName/path ,
*/ */
async request (event) { async request (event) {
const { headers = {}, queries, method = 'GET', path: p, body } = event; const { headers, queries, method, path: p, body } = this.handlerHttpParmase(event);
if (!headers['X-Fc-Log-Type']) {
headers['X-Fc-Log-Type'] = 'Tail';
}
if (!headers['X-Fc-Invocation-Code-Version']) {
headers['X-Fc-Invocation-Code-Version'] = 'Latest';
}
let resp; let resp;
try { try {
@ -137,7 +125,8 @@ export default class RemoteInvoke {
resp = await this.fcClient.costom_request('PUT', p, null, body, headers); resp = await this.fcClient.costom_request('PUT', p, null, body, headers);
} else if (mt === 'DELETE') { } else if (mt === 'DELETE') {
resp = await this.fcClient.costom_request('DELETE', p, queries, null, headers); resp = await this.fcClient.costom_request('DELETE', p, queries, null, headers);
} else if (method.toLocaleUpperCase() === 'PATCH') { }
else if (method.toLocaleUpperCase() === 'PATCH') {
resp = await this.fcClient.costom_request('PATCH', p, queries, body, headers); resp = await this.fcClient.costom_request('PATCH', p, queries, body, headers);
} else if (method.toLocaleUpperCase() === 'HEAD') { } else if (method.toLocaleUpperCase() === 'HEAD') {
resp = await this.fcClient.costom_request('HEAD', p, queries, body, headers); resp = await this.fcClient.costom_request('HEAD', p, queries, body, headers);
@ -155,20 +144,55 @@ export default class RemoteInvoke {
if (resp?.err) { if (resp?.err) {
this.showLog(resp.headers['x-fc-log-result']); this.showLog(resp.headers['x-fc-log-result']);
logger.log(`\nFC Invoke Result[Code: ${resp.code}]:`, 'red'); logger.log(`\nFC Invoke Result[code: ${resp.code}]:`, 'red');
console.log(resp.data); console.log(resp.data);
console.log('\n'); console.log('\n');
} else { } else {
if (resp) { if (resp) {
this.showLog(resp.headers['x-fc-log-result']); this.showLog(resp.headers['x-fc-log-result']);
logger.log(`\nFC Invoke Result[Code: ${resp.code}]:`, 'green'); logger.log('\nFC Invoke Result[code: ${resp.code}]:', 'green');
console.log(resp.data); console.log(resp.data);
console.log('\n'); console.log('\n');
} }
} }
} }
handlerHttpParmase (event) {
const { body = '', headers = {}, method = 'GET', queries = '', path: p = '' } = event;
let postBody;
if (body) {
let buff = null;
if (Buffer.isBuffer(body)) {
buff = body;
headers['content-type'] = 'application/octet-stream';
} else if (typeof body === 'string') {
buff = Buffer.from(body, 'utf8');
headers['content-type'] = 'application/octet-stream';
} else if (typeof body.pipe === 'function') {
buff = body;
headers['content-type'] = 'application/octet-stream';
} else {
buff = Buffer.from(JSON.stringify(body), 'utf8');
headers['content-type'] = 'application/json';
}
postBody = buff;
}
if (!headers['X-Fc-Log-Type']) {
headers['X-Fc-Log-Type'] = 'Tail';
}
return {
headers,
queries,
method,
path: p,
body: postBody
}
}
private showLog(log) { private showLog(log) {
if (log) { if (log) {
logger.log('========= FC invoke Logs begin =========', 'yellow'); logger.log('========= FC invoke Logs begin =========', 'yellow');

View File

@ -24,7 +24,7 @@
"./node_modules/@types" "./node_modules/@types"
], ],
"rootDir": "src", "rootDir": "src",
"outDir": "dist" "outDir": "lib"
}, },
"exclude": [ "exclude": [
"**/node_modules/**", "**/node_modules/**",