Calling other programming languages
External programs are code files saved under the "External Programs Directory", which can be java program archive files (.jar packages), or code source files of other programs. It supports files with extensions like:
java
(.jar)python
(.py)php
(.php)js
(.js)BeanShell
(.bsh)go
(.go)shell
(.sh)ruby
(.rb)lua
(.lua)
:::tip[]
External programs run outside the "sandbox environment", with access to operate other programs, files, and data on your computer, which poses some security risks. Users should verify the security of called programs.
:::
Calling an external program
You can quickly open the "External Program Directory" in the following way:
When calling an external program, Apidog will start a subprocess and run the specified external program in it with command line execution. Finally the standard output (stdout) of the subprocess will be returned as the result of the call. In the whole calling process, the core logic is implemented by users in the external program, while Apidog mainly does the following 3 steps:
- Combine the command string based on the provided parameters
- Execute the command
- Return the result
Among them, the first step is key to understand the calling principles. Apidog uses the formula "command prefix + program path + parameter list" to concatenate the command.
The "command prefix" is inferred from the file extension of the program file. The "program path" and "parameter list" are both provided by users while calling.
For example: pm.execute('com.apidog.Base64EncodeDemo.jar', ['abc','bcd'])
, the actual executed command is java -jar "com.apidog.Base64EncodeDemo.jar" "abc" "bcd"
.
The mapping between program file extensions and command prefixes:
Language | Command Prefix | File Extension |
---|---|---|
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 |
Python | python |
.py |
API
pm.executeAsync
filePath
string External program pathargs
string[] Parameters. When calling specified methods in a jar package,JSON.stringify
will be used for conversion. Except for that, non-string types will be implicitly converted to string.options
Objectcommand
string The execution command of the external program, the first part of "command prefix" is the execution command. Optional, default value is inferred automatically (see "command prefix" table above), can be customized to any program.cwd
string Working directory of the subprocess. Optional, default is "External Programs Directory".env
Record<string, string> Environment variables of the subprocess. Optional, default is{}
.windowsEncoding
string Encoding used on Windows system. Optional, default is"cp936"
.className
string Specify the class name to call in the jar package, e.g."com.apidog.Utils"
. Optional, see Call specified methods in jar packages for details.method
string Specify the method name to call in the jar package, e.g."add"
. Optional (required ifclassName
has value), see Call specified methods in jar packages for details.paramTypes
string[] Specify the parameter types of the method to call in the jar package, e.g.["int", "int"]
. Optional, default is inferred automatically based on parameters, see Call specified methods in jar packages for details.
- Return: Promise<string>
:::tip[Usage of command
parameter]
By default Apidog uses python
to execute .py
files. If python3
is already installed on the computer, command
can be specified as python3
.
pm.executeAsync('./demo.py', [], { command: 'python3' }).then(res => {
console.log('result: ', res);
});
:::
pm.execute
:::tip[]
It is recommended to use pm.executeAsync
instead, See Code migration instructions for details.
:::
pm.execute(filePath, args, options)
filePath
string External program pathargs
string[] Parameters. When calling specified methods in a jar package,JSON.stringify
will be used for conversion. Except for that, non-string types will be implicitly converted to string.options
ObjectwindowsEncoding
string Encoding used on Windows system. Optional, default is"cp936"
.className
string Specify the class name to call in the jar package, e.g."com.apidog.Utils"
. Optional, see Call specified methods in jar packages for details.method
string Specify the method name to call in the jar package, e.g."add"
. Optional (required ifclassName
has value), see Call specified methods in jar packages for details.paramTypes
string[] Specify the parameter types of the method to call in the jar package, e.g.["int", "int"]
. Optional, default is inferred automatically based on parameters, see Call specified methods in jar packages for details.
- Return: string
Execution and Logs
When executing a program, the executed command will be printed in the console (for reference only). If the result does not meet expectations, you can copy the command and paste it in Shell/CMD
to debug.
The console will also print the "standard output (stdout)" and "standard error output (stderr)" of the executed process. The stdout content (excluding the trailing newline character) will be the final result of the execution.
:::tip
For historical reasons, pm.execute
treats execution as failed when there is content in stderr. This causes some programs to fail when outputting warnings or error messages. pm.executeAsync
changes to use the exit code of the process to determine if the execution failed.
:::
Input and output of external programs
Parameters
Since the specified external program runs with command line execution, it can only get the passed in parameters through command line arguments.
For example, in script pm.executeAsync('add.js', [2, 3])
, the actual executed command is node add.js 2 3
. To get the parameters in the external script add.js:
let a = parseInt(process.argv[1]); // 2
let b = parseInt(process.argv[2]); // 3
:::tip
- Different programming languages have different ways to get command line arguments, please refer to corresponding language docs.
- The type of command line arguments is always string, need to convert based on actual types.
:::
Return value
As mentioned above, Apidog uses the stdout content as the result of program execution. So printing content to stdout can return results.
For example, in script const result = await pm.executeAsync('add.js', [2, 3])
, the result can be returned by:
console.log(parseInt(process.argv[1]) + parseInt(process.argv[2]));
:::tip[]
- Different programming languages have different ways to print to stdout, refer to corresponding language docs.
- The return type is string, need to convert based on actual types.
- The trailing newline character of the result will be trimmed.
- When calling specified methods in jar packages, the return value of the called method will be used as the final return value.
:::
Throwing errors
Throwing errors can fail the current task and stop execution. For example:
throw Error("Execution failed");
:::tip[]
- Different programming languages have different ways to throw errors, refer to corresponding docs.
- In JavaScript,
console.error('Error')
only prints to stderr instead of throwing an error. Consider this while using other languages too.
:::
Debug information
Since pm.executeAsync
uses exit code instead of stderr to determine success, stderr can be used to print debug information without affecting execution.
For example:
console.warn("debug info");
console.error("error info");
:::tip
- Only
pm.executeAsync
supports this way of printing debug info. - Different programming languages have different ways to print to stderr, refer to corresponding docs.
:::
Migrate from pm.execute to pm.executeAsync
Since the return value of pm.executeAsync
is Promise type, execute
cannot be directly changed to executeAsync
. But you can use async
/await
to migrate with minimal changes.
:::tip
Apidog version >= 2.3.24 (CLI version >= 1.2.38) supports top-level await.
:::
Steps:
- Change
execute
toexecuteAsync
- Add
await
before function call
// Before
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);
Call specified methods in .jar packages
:::tip
This feature requires Apidog version >= 2.1.39
. It only supports calling jars with reflection, not jars like Spring Boot using internal runtime reflection.
:::
By default, calling a jar will invoke the main method in the Main class. If options.className
is specified, it will override the default behavior and call the specified method in the jar instead.
Calling specified methods in jars is different from other external programs. Apidog will use a built-in executor to find the method in the jar by reflection and call it. If the called method has a return value, it will be used as the final return value after converting to string. Otherwise, it works the same as other calls, using stdout content as return value.
For example:
await pm.executeAsync('./scripts/jar-1.0-SNAPSHOT.jar', ['hello', 'world'], {
className: 'com.apidog.Test',
method: 'combine',
paramTypes: ['String', 'String']
})
The actually executed command is:
java -jar "<app-dist>/assets/JarExecuter-1.1.0-jar-with-dependencies.jar" ./scripts/jar-1.0-SNAPSHOT.jar "com.apidog.Test.combine(String,String)" "\"hello\"" "\"world\""
Where <app-dist>/assets/JarExecuter-1.1.0-jar-with-dependencies.jar
is the built-in executor, responsible for finding the method com.apidog.Test.combine(String,String)
in the user program ./scripts/jar-1.0-SNAPSHOT.jar
through reflection, and calling it with parameters (JSON string) "hello"
and "world"
.
:::tipparamTypes
is optional. If not specified, types will be inferred automatically based on parameters. Integers are inferred as "int"
, floats as "double"
, booleans as "boolean"
, strings as "String"
, arrays are inferred based on the first element, e.g. [3]
is inferred as "int[]"
, [3.14]
as "double[]"
, etc.
If the inferred types do not match the actual parameter types of the called method, paramTypes
needs to be specified manually.
Supported values in paramTypes
array: "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[]"
So the paramTypes
in the example above can be omitted:
await pm.executeAsync('./scripts/jar-1.0-SNAPSHOT.jar', ['hello', 'world'], {
className: 'com.apidog.Test',
method: 'combine'
})
:::
Examples
1. PHP program
Script:
const param1 = { a: 1, b: 2 }
const resultString = await pm.executeAsync('test.php', [JSON.stringify(param1)])
const result = JSON.parse(resultString)
console.log('Result:', result) // 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 program
Script:
const result = await pm.executeAsync('com.apidog.utils.jar', [3, 5], {
className: 'com.apidog.utils.Utils',
method: 'add',
paramTypes: ['Integer', 'Integer']
})
console.log('Result:', result) // Result: 8
com.apidog.utils.jar:
package com.apidog.utils;
public class Utils {
public Integer add(Integer a, Integer b) {
return a + b;
}
};
Common issues
1. Some programs require project config files and will error if missing
**Rust and 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'
Solution: Use pm.executeAsync and specify cwd
.
2. MacOS has built-in Python 3 but no Python 2
Use pm.executeAsync and set command
to "python3"
.
3. Command xxx not found
Install corresponding program and add necessary directories to system PATH. See docs for Java installation.
4. Calling external scripts prints garbled on some Windows systems
Set windowsEncoding
to 'utf-8'
var result = pm.execute(`hello.go`, [], { windowsEncoding: 'utf-8' })