Skip to main content

Calling Other Programming Languages

Note
  1. Apidog version needs to be >= 1.0.25 to call external programs with scripts.
  2. 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.

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)
  • .bat (.bat)
  • .ps1 (.ps1)

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:

  1. Combine the command string based on the provided parameters
  2. Execute the command
  3. 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:

LanguageCommand PrefixFile Extension
Javajava -jar.jar
Pythonpython.py
PHPphp.php
JavaScriptnode.js
BeanShelljava bsh.Interpreter.bsh
Gogo run.go
Shellsh.sh
Rubyruby.rb
Lualua.lua
Rustcargo run.rs
Pythonpython.py
Windows Batch Filecmd /c.bat
Windows PowerShell Script Filepowershell.ps1

API

pm.executeAsync

  • filePath string External program path
  • args 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 Object
    • command 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 if className 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>
info

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 path
  • args 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 Object
    • 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 if className 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
  1. Different programming languages have different ways to get command line arguments, please refer to corresponding language docs.
  2. 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
  1. Different programming languages have different ways to print to stdout, refer to corresponding language docs.
  2. The return type is string, need to convert based on actual types.
  3. The trailing newline character of the result will be trimmed.
  4. 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
  1. Different programming languages have different ways to throw errors, refer to corresponding docs.
  2. 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
  1. Only pm.executeAsync supports this way of printing debug info.
  2. 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:

  1. Change execute to executeAsync
  2. 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".

info

paramTypes 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' })