调用其它编程语言
- Apifox 版本需
>= 1.0.25
才能使用脚本调用外部程序。 - 外部程序是在「沙盒环境」以外运行的,有权限访问和操作电脑上的其他程序、文件及数据,存在一定的安全性风险,使用者请务必确保被调程序的安全性。
外部程序是保存在“外部程序目录”下的代码文件,可以是 java 程序归档文件 jar 包,也可以是其他程序的代码源文件,支持 .jar
、.py
、.php
、.js
、.bsh
、.go
、.sh
、.rb
、.lua
、.rs
、.py
等后缀的文件。可以使用以下方式快速打开“外部程序目录”。
调用外部程序时,Apifox 会启动子进程并在其中以命令行执行的方式运行用户指定的外部程序,最后将子进程的标准输出(stdout)作为调用的返回值。在整个调用过程中,核心逻辑由用户在外部程序中实现,而 Apifox 主要的工作分为以下 3 步:
- 根据用户提供的参数拼接出命令字符串
- 执行命令
- 返回结果
其中第一步是了解调用原理的关键,Apifox 使用的命令拼接公式为:”命令前缀 + 程序路径 + 参数列表“。
“命令前缀“是通过程序文件扩展名推断出来的,“程序路径”与“参数列表”都是由用户调用的时候提供。
如:脚本pm.execute('cn.apifox.Base64EncodeDemo.jar', ['abc','bcd'])
,实际执行命令为java -jar "cn.apifox.Base64EncodeDemo.jar" "abc" "bcd"
,两者之间的转换关系如图所示:
程序扩展名与命令前缀的关系:
语言 | 命令前缀 | 程序扩展名 |
---|---|---|
Java | java -jar | .jar |
Python | python | .py |
PHP | php | .php |
JavaScript | node | .js |
BeanShell | java bsh.Interpreter | .bsh |
Go | go run | .go |
Shell | sh | .sh |
Ruby | ruby | .rb |
Lua | lua | .lua |
Rust | cargo run | .rs |
接口
pm.executeAsync
Apifox 版本 2.3.24(CLI 版本 1.2.38)新增接口,pm.execute
已废弃不推荐使用(代码迁移说明)
pm.executeAsync(filePath, args, options)
filePath
string 外部程序路径args
string[] 参数。调用 jar 包中的指定方法时,会使用JSON.stringify
进行转换。除此之外非 string 类型会进行隐式类型转换自动转换为 string 类型。options
Objectcommand
string 外部程序的执行命令,“命令前缀”中的前面部分就是执行的命令。非必填,默认为自动推断的值(见上文的“命令前缀”表格),可以自定义为任意程序cwd
string 子进程工作目录。非必填,默认为“外部程序目录”env
Record<string, string> 子进程环境变量。非必填,默认为{}
windowsEncoding
string Windows 系统用使用的编码格式。非必填,默认为"cp936"
className
string 指定 jar 包中调用的类名,例如"cn.apifox.Utils"
。非必填,详见调用 jar 包中的指定方法method
string 指定 jar 包中调用的方法名,例如"add"
。非必填(className
有值时为必填),详见调用 jar 包中的指定方法paramTypes
string[] 指定 jar 包中调用的方法参数类型,例如["int", "int"]
。非必填,默认根据参数自动推断,详见调用 jar 包中的指定方法
- 返回:Promise<string>
command
参数使用场景:
Apifox 默认使用 python
执行 .py
文件,如果电脑中已经安装了 python3
,那么可以将 command
指定为 python3
。
pm.executeAsync('./demo.py', [], { command: 'python3' }).then(res => {
console.log('result: ', res);
});
pm.execute
建议使用 pm.executeAsync
代替。详细说明请参考(《代码迁移说明》)。
pm.execute(filePath, args, options)
filePath
string 外部程序路径args
string[] 参数。调用 jar 包中的指定方法时,会使用JSON.stringify
进行转换。除此之外非 string 类型会进行隐式类型转换自动转换为 string 类型。options
ObjectwindowsEncoding
string Windows 系统用使用的编码格式。非必填,默认为"cp936"
className
string 指定 jar 包中调用的类名,例如"cn.apifox.Utils"
。非必填,详见调用 jar 包中的指定方法method
string 指定 jar 包中调用的方法名,例如"add"
。非必填(className
有值时为必填),详见调用 jar 包中的指定方法paramTypes
string[] 指定 jar 包中调用的方法参数类型,例如["int", "int"]
。非必填,默认根据参数自动推断,详见调用 jar 包中的指定方法
- 返回:string
执行与日志
程序执行时,会在控制台输出所执行的命令内容(内容仅供参考),假如执行的结果不符合预期,可以将命令内容复制出来并粘贴到 Shell/CMD
中执行调试。
控制台中还会输出程序执行进程的“标准输出(stdout)”与“标准错误输出(stderr)”,其中“标准输出”的所有内容(去除最后一行末尾的换行符)为程序最终执行的结果。
由于历史原因 pm.execute
在“标准错误输出”有内容时即认为程序执行失败。这导致有些程序在输出 warning 或 error 信息时,会造成执行失败。pm.executeAsync
则改为使用程序退出码来判断程序是否执行失败。
外部程序的输入与输出
参数
由于用户指定的外部程序是以命令行执行的方式运行的,所以外部程序只能通过命令行参数获取到执行时传入的参数。
例如脚本pm.executeAsync('add.js', [2, 3])
,实际执行命令为node add.js 2 3
,则在外部脚本 add.js 中,获取参数的方式为:
let a = parseInt(process.argv[1]); // 2
let b = parseInt(process.argv[2]); // 3
- 不同的编程语言对于命令行参数的获取方式不一样,具体请参考对应语言的文档。
- 命令行参数的类型都是 string,需要按照实际情况转换类型。
返回值
上文提到,Apifox 使用标准输出(stdout)的内容作为程序调用的结果,所以往标准输出打印内容就可以返回结果。
例如脚本 const result = await pm.executeAsync('add.js', [2, 3])
,可以通过以下方式将结果输出并赋值给变量 result
:
console.log(parseInt(process.argv[1]) + parseInt(process.argv[2]));
- 不同的编程语言输出内容到标准输出(stdout)的方式不一样,具体请参考对应语言的文档。
- 返回的结果类型为 string,需要按照实际情况转换类型。
- 返回的结果末尾的换行符会被自动去除。
- 调用 jar 包中的指定方法时,会优先使用被调用方法的返回值作为最终的返回值。
抛出错误
通过抛出错误可以让当前的任务失败并退出执行。例如:
throw Error("Execution failed");
- 不同的编程语言抛出错误的方式不一样,具体请参考对应语言的文档
- JavaScript 代码
console.error('Error')
只是往标准错误输出(stderr)输出内容,不是抛出错误。其他语言也需要注意这一点。
调试信息
pm.executeAsync
使用退出码而不是标准错误输出(stderr)内容来判断程序是否执行成功,所以可以利用标准错误输出(stderr)来打印调试内容,而不影响程序的执行。
例如:
console.warn("debug info");
console.error("error info");
- 仅
pm.executeAsync
接口支持这种方式输出调试信息。 - 不同的编程语言输出内容到标准错误输出(stderr)的方式不一样,具体请参考对应语言的文档。
从 pm.execute 迁移到 pm.executeAsync
由于 pm.executeAsync
的返回值是 Promise 类型,这导致不可以直接将旧代码中的 execute
改为 executeAsync
。
但是可以使用 async
/await
方式,在最小改动的前提下,迁移代码。
Apifox 版本 >= 2.3.24(CLI 版本 >= 1.2.38)支持顶层 await
具体操作如下:
- 将
execute
改为executeAsync
- 在函数调用前添加
await
关键字
// 修改前
const result = pm.execute("add.js", [3, 4]);
pm.environment.set("result", result);
const result = await pm.executeAsync("add.js", [3, 4]);
pm.environment.set("result", result);
调用 jar 包中的指定方法
该功能在 Apifox 版本 >= 2.1.39
中才能使用。并且仅支持能直接使用反射调用的 jar 包,不支持像 Spring Boot 这类由内部运行时进行反射调用的 jar 包。
外部程序为 jar 包时,默认调用的是 jar 包中 Main 类的 main 方法。如果参数 options.className
指定了值,则代表忽略默认行为改为调用 jar 中指定的方法。
调用 jar 中的指定方法有别于其他外部程序的调用。Apifox 会使用一个内置的执行器,通过反射的方式查找到 jar 中的方法并调用。并且如果被调用的方法有返回值,则使用该返回值(转换为字符串后)作为最终的返回值返回,否则与其他调用方式一样使用标准输出(stdout)内容作为返回值。
例如:
await pm.executeAsync('./jar-1.0-SNAPSHOT.jar', ['hello', 'world'], {
className: 'com.apifox.Test',
method: 'combine',
paramTypes: ['String', 'String']
})
实际调用的命令为:
java -jar "<app-dist>/assets/JarExecuter-1.1.0-jar-with-dependencies.jar" ./scripts/jar-1.0-SNAPSHOT.jar "com.apifox.Test.combine(String,String)" "\"hello\"" "\"world\""`
其中 <app-dist>/assets/JarExecuter-1.1.0-jar-with-dependencies.jar
为内置的执行器,负责从用户程序 ./scripts/jar-1.0-SNAPSHOT.jar
中通过反射查找方法com.apifox.Test.combine(String,String)
,
并使用参数(JSON 字符串格式) "hello"
、"world"
进行调用。
paramTypes
为选填字段,如果为空,则默认通过参数推断类型, 整数推断为 "int"
,浮点数推断为 "double"
,布尔值推断为 "boolean"
,字符串推断为 "String"
, 数组则根据第一个元素的类型来推断,例如 [3]
推断为 "int[]"
,[3.14]
推断为 "double[]"
,依此类推。
如果自动推断的类型,不符合被调用方法的参数类型,则需要手动指定 paramTypes
的值。
paramTypes
数组支持的元素值有: "Number"
、 "int"
、 "Integer"
、 "long"
、 "Long"
、 "short"
、 "Short"
、 "float"
、 "Float"
、 "double"
、 "Double"
、 "boolean"
、 "Boolean"
、 "String"
、 "Number[]"
、 "int[]"
、 "Integer[]"
、 "long[]"
、 "Long[]"
、 "short[]"
、 "Short[]"
、 "float[]"
、 "Float[]"
、 "double[]"
、 "Double[]"
、 "boolean[]"
、 "Boolean[]"
、 "String[]"
所以,上面的例子可以省略 paramTypes
参数:
await pm.executeAsync('./scripts/jar-1.0-SNAPSHOT.jar', ['hello', 'world'], {
className: 'com.apifox.Test',
method: 'combine'
})
示例
1. php 程序
脚本内容:
const param1 = { a: 1, b: 2 }
const resultString = await pm.executeAsync('test.php', [JSON.stringify(param1)])
const result = JSON.parse(resultString)
console.log('运行结果:', result) // 运行结果:{ a: 2, b: 4 }
test.php 代码:
<?php
$param = json_decode($argv[1]);
$result = [];
foreach($param as $key=>$value)
{
$result[$key] = $value * 2;
}
echo json_encode($result);
2. jar 程序
脚本内容:
const result = await pm.executeAsync('cn.apifox.utils.jar', [3, 5], {
className: 'cn.apifox.utils.Utils',
method: 'add',
paramTypes: ['Integer', 'Integer']
})
console.log('运行结果:', result) // 运行结果:8
cn.apifox.utils.jar 包中代码:
package cn.apifox.utils;
public class Utils {
public Integer add(Integer a, Integer b) {
return a + b;
}
};
常见问题
1. 某些程序需要项目配置文件,找不到的时候会报错
例如 rust 与 go:
rust:
could not find `Cargo.toml` in `<...>/ExternalPrograms` or any parent directory
go:
go.mod file not found in current directory or any parent directory; see 'go help modules'
解决方案:使用 pm.executeAsync 接口并指定 cwd
2. MacOS 内置 Python3,但没有 Python2
使用 pm.executeAsync 接口并设置参数 command
值为 "python3"
3. 找不到 xxx 命令
安装对应的程序,将必要的目录加入操作系统的 PATH。Java 安装可以参考文档。
4. 部分 Windows 系统引用外部脚本,打印中文乱码
设置 windowsEncoding
参数值为 'utf8'
var result = pm.execute(`hello.go`, [], { windowsEncoding: 'utf8' })
5. 部分 Windows 系统调用 python3 会出现编码相关错误
设置环境变量 PYTHONIOENCODING
参数值为 'utf-8'
try {
const result = await pm.executeAsync('./test.py', [], {
windowsEncoding: 'utf8',
env: {
PYTHONIOENCODING: 'utf-8'
}
}};
console.log(result);
} catch (err){
console.log('err:', err.toString())
}