Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Srdjan
Product and Topic Expert
Product and Topic Expert
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 🙂

 
11 Comments