Technical Articles
Call Node.js or Python Functions from ABAP
Node.js rich ecosystem offers plenty of functions and utilities. Using node-rfc server bindings, these assets can be consumed from ABAP, just like standard ABAP functions. SAP Open Source node-rfc connector and SAP NW RFC SDK Library make it possible.
For more info check the node-rfc server documentation and server examples. Example given here is for Node.js platform and it works the same way with Python, using PyRFC.
Background RFC protocol is currently supported for Python and work in progress for Node.js.
Node.js server source code: server-test-blog.mjs
ABAP client source code: zserver_stfc_struct.abap
How it works?
ABAP program can call an ABAP function in Node.js system, over SAP RFC protocol, just like when that function would be in ABAP system. The node-rfc server will route the ABAP RFC call to JavaScript function, registered for serving that ABAP function calls. ABAP function call parameters are automatically transformed to JavaScript and registered JavaScriot function is invoked. After JavaScript function is completed, result is automatically transformed to ABAP format and send back to ABAP client. All standard ABAP RFC call parameters can be used, like ABAP variable (JavaScript variable), ABAP structure (JavaScript “plain” object) or ABAP table (JavaScript array of “plain” objects).
Rather then manually defining ABAP interface the node-rfc server function, the node-rfc can re-use the signature of already existing ABAP function, or empty ABAP function can be created, just to define ABAP function interface for node-rfc server function..
Let try it in real systems, a notebook with Node.js supported LTS release and any new or old ABAP system.
ABAP function module signature for Node.js function
Let use the interface of ABAP function STFC_STRUCTURE, to call JavaScript function which we will create. The STFC_STRUCTURE expects one input structure, IMPORTSTRUCT and returns one variable, the RESPTEXT string and one structure, ECHOSTRUCT. There is also a table parameter RFCTABLE:
FUNCTION STFC_STRUCTURE.
*"----------------------------------------------------------------------
*"*"Lokale Schnittstelle:
*" IMPORTING
*" VALUE(IMPORTSTRUCT) LIKE RFCTEST STRUCTURE RFCTEST
*" EXPORTING
*" VALUE(ECHOSTRUCT) LIKE RFCTEST STRUCTURE RFCTEST
*" VALUE(RESPTEXT) LIKE SY-LISEL
*" TABLES
*" RFCTABLE STRUCTURE RFCTEST
*"----------------------------------------------------------------------
Here is our Node.js function, to be called from ABAP using these parameters:
function my_stfc_structure(request_context, abap_input) {
// inspect request context
const attributes = request_context["connection_attributes"];
console.log(
"[js] my_stfc_structure context:",
attributes["sysId"], attributes["client"], attributes["user"], attributes["progName"]);
console.log("[js] my_stfc_structure input:", abap_input.IMPORTSTRUCT);
// prepare response for ABAP client
const echostruct = abap_input.IMPORTSTRUCT;
echostruct.RFCINT1 = 2 * echostruct.RFCINT1;
echostruct.RFCINT2 = 3 * echostruct.RFCINT2;
echostruct.RFCINT4 = 4 * echostruct.RFCINT4;
const abap_output = {
ECHOSTRUCT: echostruct,
RESPTEXT: `~~~ Node server here ~~~`,
};
console.log("[js] my_stfc_structure response:", abap_output);
// return response data
return abap_output;
}
Here is Node.js function call from ABAP client, from ABAP test report:
call function 'STFC_STRUCTURE' destination 'NWRFC_SERVER_OS'
exporting
importstruct = ls_struct
importing
echostruct = ls_struct
resptext = lv_resp
tables
rfctable = lt_table
exceptions
communication_failure = 1 message lv_error_message
system_failure = 2 message lv_error_message.
To make this ABAP function call into Node.js work just like standard ABAP function call, following steps shall be done, covered in follow-up sections in detail
- ABAP system: Configure RFC destination for node-rfc server
- Node.js system: Configure node-rfc server connections to ABAP system
- Node.js system: Create Node.js function to be called from ABAP and launch the server
- ABAP system: Call Node.js function
Configure RFC destination for node-rfc server
Using transaction SM59 create RFC destination of type TCP/IP connection (“T”), like for example
RFC destination configuration – technical settings
If bgRFC protocol shall be supported by node-rfc server, configure the basXML serializer option here:
RFC destinatin configuration – bgRFC support
Configure node-rfc server connections for ABAP system
node-rfc server requires two RFC destinations for ABAP system, configured in sapnwrfc.ini file in Node.js system.
The first destination, “MME”, is RFC client destination, the node-rfc can use to call ABAP functions. This client connection is used by node-rfc server to obtain ABAP STFC_STRUCTURE function definition, so that node-rfc server can automatically transform ABAP STFC_STRUCTURE call data to JavaScript and vice versa.
The second destination, “MME_GATEWAY”, is RFC server destination, open after server is launched and used for listening on ABAP client requests.
sapnwrfc.ini
DEST=MME
USER=demo
PASSWD=welcome
ASHOST=system51
SYSNR=00
CLIENT=620
LANG=EN
TRACE=0
DEST=MME_GATEWAY
GWSERV=sapgw00
GWHOST=coevi51
PROGRAM_ID=RFCSERVER
REG_COUNT=1
Start node-rfc server
After SAP NW RFC SDK binaries are downloaded and installed on your Node.js system, you can create empty folder and install node-rfc
mkdir server
cd server
npm init -y
npm install node-rfc
Now the first server test can be done, to verify ABAP system connections. Create sapnwrfc.ini file in project root directory and create test script, like:
import {RfcLoggingLevel, Server} from "node-rfc";
// Create server instance, initially inactive
const server = new Server({
serverConnection: { dest: "MME_GATEWAY" },
clientConnection: { dest: "MME" },
// Server options are optional
serverOptions: {
logLevel: RfcLoggingLevel.error,
// authHandler: authHandler,
},
});
(async () => {
try {
// Start the server
await server.start();
console.log(
`[js] Server alive: ${server.alive} client handle: ${server.client_connection}`,
`server handle: ${server.server_connection}`
);
} catch (ex) {
// Catch errors, if any
console.error(ex);
}
})();
// Close the server after 10 seconds
let seconds = 10;
const tick = setInterval(() => {
console.log("tick", --seconds);
if (seconds <= 0) {
server.stop(() => {
clearInterval(tick);
console.log("bye!");
});
}
}, 1000);
Now open again the NWRFC_SERVER_OS RFC destination using SM59 transaction and find “Connection Test” button
RFC destination – connection test
Start your test script in Node.js system and after server alive message press the “Connection Test” button. When RFC connection with ABAP system is working, the output looks like this:
RFC destination connection test output
Now when RFC connectivity is working let create Node.js function and call it from ABAP.
Create Node.js function to be called from ABAP and launch the server
Let add “my_stfc_structure” JavaScript server function, to receive ABAP calls of STFC_STRUCTURE function in Node.js system. Two additions are required in our test script, the server function implementation and registration.
Server function requires nothing special for node-rfc server, it shall implement only “plain” logic to calculate the response for ABAP client. Also promise can be returned.
The first parameter is request_context, just in case the function implementation shall consider it. The second parameter is JavaScript object with ABAP parameters, as defined by ABAP STFC_STRUCTURE function signature.
// Server function
function my_stfc_structure(request_context, abap_input) {
const connection_attributes = request_context["connection_attributes"];
console.log(
"[js] my_stfc_structure context:",
connection_attributes["sysId"],
connection_attributes["client"],
connection_attributes["user"],
connection_attributes["progName"]
);
console.log("[js] my_stfc_structure input:", abap_input.IMPORTSTRUCT);
const echostruct = abap_input.IMPORTSTRUCT;
echostruct.RFCINT1 = 2 * echostruct.RFCINT1;
echostruct.RFCINT2 = 3 * echostruct.RFCINT2;
echostruct.RFCINT4 = 4 * echostruct.RFCINT4;
const abap_output = {
ECHOSTRUCT: echostruct,
RESPTEXT: `~~~ Node server here ~~~`,
};
console.log("[js] my_stfc_structure response:", abap_output);
return abap_output;
}
The server function is registered using node-rfc server “addFuncion” method, telling the server to route ABAP STFC_STRUCTURE function calls to JavaScript function “my_stfc_structure”:
server.addFunction("STFC_STRUCTURE", my_stfc_structure);
ABAP data transformations to/from JavaScript are done by node-rfc server automatically.
Our test script is now ready and the node-rfc server can be started, to serve ABAP client calls:
import { RfcLoggingLevel, Server } from "node-rfc";
// Create server instance, initially inactive
const server = new Server({
serverConnection: { dest: "MME_GATEWAY" },
clientConnection: { dest: "MME" },
// Server options are optional
serverOptions: {
logLevel: RfcLoggingLevel.error,
// authHandler: authHandler,
},
});
// Server function
function my_stfc_structure(request_context, abap_input) {
const connection_attributes = request_context["connection_attributes"];
console.log(
"[js] my_stfc_structure context:",
connection_attributes["sysId"],
connection_attributes["client"],
connection_attributes["user"],
connection_attributes["progName"]
);
console.log("[js] my_stfc_structure input:", abap_input.IMPORTSTRUCT);
const echostruct = abap_input.IMPORTSTRUCT;
echostruct.RFCINT1 = 2 * echostruct.RFCINT1;
echostruct.RFCINT2 = 3 * echostruct.RFCINT2;
echostruct.RFCINT4 = 4 * echostruct.RFCINT4;
const abap_output = {
ECHOSTRUCT: echostruct,
RESPTEXT: `~~~ Node server here ~~~`,
};
console.log("[js] my_stfc_structure response:", abap_output);
return abap_output;
}
(async () => {
try {
// Register server function
server.addFunction("STFC_STRUCTURE", my_stfc_structure);
console.log(
`[js] Node.js function '${my_stfc_structure.name}'`,
"registered as ABAP 'STFC_STRUCTURE' function"
);
// Start the server
await server.start();
console.log(
`[js] Server alive: ${server.alive} client handle: ${server.client_connection}`,
`server handle: ${server.server_connection}`
);
} catch (ex) {
// Catch errors, if any
console.error(ex);
}
})();
// Close the server after 10 seconds
let seconds = 10;
const tick = setInterval(() => {
console.log("tick", --seconds);
if (seconds <= 0) {
server.stop(() => {
clearInterval(tick);
console.log("bye!");
});
}
}, 1000);
When test script is started in Node.js system and ABAP test report calls STFC_STRUCTURE function in Node.js system, the test script output looks like:
ts-node ci/test/server-test.ts (py3.11.4) ✘ 1 main ◼
[js] Node.js function 'my_stfc_structure' registered as ABAP 'STFC_STRUCTURE' function
[js] Server alive: false client handle: 5554800128 server handle: 0
tick 9
tick 8
[js] my_stfc_structure context: MME 620 D037732 ZSERVER_STFC_STRUCT
[js] my_stfc_structure input: {
RFCFLOAT: 0,
RFCCHAR1: '',
RFCINT2: 2,
RFCINT1: 1,
RFCCHAR4: '',
RFCINT4: 4,
RFCHEX3: <Buffer 00 00 00>,
RFCCHAR2: '',
RFCTIME: '000000',
RFCDATE: '00000000',
RFCDATA1: '',
RFCDATA2: ''
}
[js] my_stfc_structure response: {
ECHOSTRUCT: {
RFCFLOAT: 0,
RFCCHAR1: '',
RFCINT2: 6,
RFCINT1: 2,
RFCCHAR4: '',
RFCINT4: 16,
RFCHEX3: <Buffer 00 00 00>,
RFCCHAR2: '',
RFCTIME: '000000',
RFCDATE: '00000000',
RFCDATA1: '',
RFCDATA2: ''
},
RESPTEXT: '~~~ Node server here ~~~'
}
tick 7
tick 6
tick 5
^C
Calling Node.js function from ABAP
Here is ABAP test report for calling Node.js function, used in this example
*&---------------------------------------------------------------------*
*& Report ZSERVER_STFC_STRUCT
*&---------------------------------------------------------------------*
*&
*&---------------------------------------------------------------------*
report zserver_stfc_struct.
data lv_echo like sy-lisel.
data lv_resp like sy-lisel.
data ls_struct like rfctest.
data lt_table like table of rfctest.
data lv_error_message type char512.
ls_struct-rfcint1 = 1.
ls_struct-rfcint2 = 2.
ls_struct-rfcint4 = 4.
insert ls_struct into table lt_table.
call function 'STFC_STRUCTURE' destination 'NWRFC_SERVER_OS'
exporting
importstruct = ls_struct
importing
echostruct = ls_struct
resptext = lv_resp
tables
rfctable = lt_table
exceptions
communication_failure = 1 message lv_error_message
system_failure = 2 message lv_error_message.
if sy-subrc eq 0.
write: / 'rfcint1:', ls_struct-rfcint1.
write: / 'rfcint2:', ls_struct-rfcint2.
write: / 'rfcint4:', ls_struct-rfcint4.
write: / 'resptext:', lv_resp.
else.
write: 'subrc :', sy-subrc.
write: / 'msgid :', sy-msgid, sy-msgty, sy-msgno.
write: / 'msgv1-4:', sy-msgv1, sy-msgv2, sy-msgv3, sy-msgv4.
write: / 'message:', lv_error_message.
exit.
endif.
When node-rfc server is running and this ABAP report started, the output looks like:
Node.js server call output
Error Handling
In case of error, the server function shall raise exception message and error message will be returned to ABAP, with RFC_EXTERNAL_FAILURE error code.
throw new Error("my_stfc_function error");
console.log("[js] my_stfc_structure response:", abap_output);
return abap_output;
}
ABAP report output
ABAP test report – error
Logging
When activated, the log is saved in local file: _noderfc.log and above mentioned error looks like:
Error log
Enjoy calling Node.js function from ABAP 🙂
Thanks for sharing! Could you please also add "ABAP Development" tag to this post? It's the main ABAP tag that I know many developers follow.
Done, thanks.
Thanks! Impressive!
However, is there are any point when the approach above is more preferable comparing with HTTP(S)-call?
Hello Oleg,
it depends on scenario I would say. This approach works in older systems as well, with by factors higher performance and less code on both sides.
HTTP call from ABAP to Node.js system requires additional ABAP code for ABAP HTTP request and additional JavaScript code in Node.js system (express ...), to receive ABAP HTTP request and respond. ABAP request data must be serialized to JSON and de-serialized in Node.js system, also the other way around.
With RFC call, ABAP makes direct call of JavaScript function, without JSON in-between. ABAP data are automatically transformed to JavaScript and vice-versa, at C++ level, by factors faster then JSON. Data are also transfered with SAP proprietary compression algorithm, optimized for ABAP data, more effective than zip in that case.
RFC protocol is now also internet enabled, using HTTP(s) and WebSockets, see WebSocket RFC to Cloud Using SAP Business Connector
From users feedback, one use case is extraction of higher volume data from ABAP systems, sometimes combined with node-rfc client (same as server, only Node.js is calling ABAP).
Kind regards,
Srdjan
Thanks for explanation. So (as I understood correctly), the main and the only feature is that C++ inside this functionality is faster than HTTP with JSON. and it could be useful in high volume of data transfer.
Thanks once more for the option! I will try to implement it somewhere.
yes, the difference is performance and less (no) code on client and server side, no wrappers needed.
Regarding usage scenarios, it is up to applications. The component is open source and high volume data extraction was mentioned by one of users
Thanks Srdjan Boskovic!!
Does this work on ABAP on Cloud( BTP ABAP environment)
Technically yes but this is new technology and use-cases shall be discussed with development.
RFC calls from ABAP on Cloud to Node/Python/Java RFC server on BTP require port configuration on BTP.
RFC calls from BTP to ABAP on Cloud work via WebSocket (ABAP is RFC client).