@sap/async-xsjs

2.0.0 • Public • Published

@sap/async-xsjs

Asynchronous compatibility layer for SAP HANA XS applications running on Node.js 16 and higher.

For the API of the XS Engine:

Usage

'use strict';

var xsenv = require('@sap/xsenv');
var async_xsjs = require('@sap/async-xsjs');

var port = process.env.PORT || 3000;
var options = xsenv.getServices({
    uaa: 'xsuaa',
    hana: 'hana-hdi',
    jobs: 'scheduler',
    mail: 'mail',
    secureStore: 'secureStore',
    auditLog: 'audit-log'
});
async_xsjs(options).then((async_xsjs_server)=>{
  async_xsjs_server.listen(port, (err)=>{
    if(!err) {
      console.log('Node XS server listening on port %d', port);
    }else{
      console.log('Node XS server failed to start on port %d', port);
    }
  });
});

The starting function takes an object that contains service credentials and application options.

You need to set up the Application Router for authentication. To learn how, see: Configure the XS Advanced Application Router

For local testing, you can set options.anonymous = true to disable authentication.

Options

Here is a list of options you can provide:

property default usage
rootDir 'lib' XSJS files location
rootDirs Same as above but array of directories can be provided. Overrides rootDir if provided.
uaa UAA configuration necessary to enable JWT token authentication and business user propagation to SAP HANA
hana Object containing SAP HANA DB connection parameters - used for DB connectivity
secureStore Object containing SAP HANA DB connection parameters - used for secure store connectivity
jobs Job Scheduler connection parameters - used for registering jobs during application startup, and later for updating job execution status when the job is finished
mail Mail options - used by $.net.Mail API
maxBodySize '1mb' Maximum body size accepted by async-xsjs. The value is passed to the bytes library for parsing.
anonymous false Enables anonymous access - without credentials
formData Special restrictions over form-data submitted to server
destinationProvider Custom function, synchronous or asynchronous, to be used when $.net.http.readDestination is called in the XSJS code. For more information on destination support, check the detailed description for this configuration option.
ca certificates listed in the XS_CACERT_PATH env variable Trusted SSL certificates for any outgoing HTTPS connections. Should be an array of loaded certificates.
compression true By default, text resources over 1K are compressed.
auditLog Object containing Audit Log service credentials. If not provided, audit logging will be disabled. If set to { logToConsole: true } Audit Log messages will be written to the console (only suitable for non-productive setup, e.g. local development).
context {} Extends the default context in .xsjs scripts
libraryCache {} Contains the .xsjslibs that should be cached
redirectUrl If specified, a redirect to this URL is triggered when the root path is requested.
Note: When async-xsjs is performed behind a reverse proxy (for example, Application Router), the value of this property should be aligned with the path rewriting rules that may apply.
xsApplicationUser true If set to false, the session variable XS_APPLICATIONUSER will not be set.

Note: When there are several rootDirs (for example, repo1 and repo2) and their file structures are identical (/repo1/hello.xsjs and /repo2/hello.xsjs), then:

  • The file from the first directory, as listed in the rootDirs property, will be used (/repo1/hello.xsjs).
  • The file from the second directory (/repo2/hello.xsjs) will be ignored with a warning message in the logs.

SAP HANA XS Advanced applications connect to SAP HANA with a fixed technical user provided via a Cloud Foundry service (environment variables). The actual (business) user of the application is retrieved from the JWT token and propagated to SAP HANA.

The connection to the Job Scheduler service is done by using a fixed technical user provided by the Cloud Foundry service binding.

hana

property mandatory usage
host x DB host
port x DB port
user x Technical user used for a DB connection
password x Technical user password
schema If provided, it will be set as a current schema to DB connection
connectWithLoggedUser Possible values are true / false. Default is false. If provided, the DB connection will be done with the SAML assertion contained in the JWT token of the logged user.
Note: This option is provided only for the SAP HANA cockpit transition to SAP HANA XS Advanced. In general, this option should be avoided.
sqlcc Object containing all SQLCC configurations as properties with name after SQLCC name used in the XSJS code
ca Trusted SSL certificates explicitly for SAP HANA connection. Should be an array of loaded certificates. If not provided, certificate from the service binding will be used. If none are available, SAP HANA connection will not be encrypted.
  • sqlcc - referring to the example above. SQLCC property can be initialized from the bound services like this:
...
options.hana.sqlcc = xsenv.getServices({
  'com.sap.my.sqlcc_config': 'SQLCC__NAME',
  'com.sap.my.other_sqlcc_config': 'OTHER_SQLCC_UPS_NAME'
  });
...

and later used in an XSJS code like:

var connection = await $.db.getConnection('com.sap.my.sqlcc_config');

secureStore

property mandatory usage
host x DB host
port x DB port
user x Technical user used for DB connection
password x Technical user password
schema If provided, it will be set as a current schema to DB connection

formData

Object with the following properties:

property default usage
maxFilesSizeInBytes 10485760 It restricts the total size of all uploaded files.

mail

Object with the following properties:

property mandatory usage
host x SMTP server host
port x SMTP server port
ignoreTLS Can be true or false. Represents whether a STARTTLS command should be invoked, if available by the mail server. Default: false
secure Can be true or false. Represents whether the connection should be over TLS/SSL. Default: false
connectionTimeout Connection timeout in ms. Default: 60000
authMethod Authentication method to use. Can be: 'PLAIN' / 'LOGIN' / 'CRAM-MD5'
auth Authentication credentials. Example: {user: 'user', pass: 'pass'} Default: no authentication

destinationProvider

If your application requires different mechanism for destination configuration - for example, dynamic configuration changes or dynamically adding new destinations to your application - you can provide your own function that retrieves these configurations from your storage.

For convenience, we support synchronous and asynchronous destination provider function. Depending on the number of parameters your function has, we call it synchronously or asynchronously.

Here are the signatures for both:

function getDestinationSync(packagename, objectname, dtDescriptor) {
}

function getDestinationAsync(packagename, objectname, dtDescriptor, callback) {
}
parameter description
packagename The package of the destination supplied to $.net.http.readDestination
objectname The object name of the destination supplied to $.net.http.readDestination
dtDescriptor Object containing all properties contained in the corresponding .xshttpdest file, if such file is available. Otherwise - undefined.
callback Provided only in the asynchronous case - should be called by your provider function to return the destination or report an error.

auditLog

This package generates audit log entries in the following cases:

  • When the validation of the incoming JWT token fails
  • When the token in the request used to trigger a job does not contain the required scope

Applications can also write audit log messages:

var async_xsjs = require('@sap/async-xsjs');
var xsenv = require('@sap/xsenv');
var auditLogging = require('@sap/audit-logging');

var port = process.env.PORT || 3000;
var options = xsenv.getServices({ auditLog: 'audit-log', uaa: 'xsuaa', hana: 'hana-hdi' });

// Using Audit Log REST API v2
var auditLog = await auditLogging.v2(options.auditLog);
options.context = { auditLog: auditLog };
(await async_xsjs(options)).listen(port);

It is recommended to use Audit Log REST API v2. For more information, refer to the documentation of the @sap/audit-logging package.

The audit log client object can then be used as following:

await auditLog
  .securityMessage('Content of the message')
  .by($.session.getUsername())
  .log();

Note: $.session.getUsername() returns undefined in case anonymous mode is used. It is up to applications whether to use another string as a user or not to audit log at all.

context

This option can be used if you want to extend the XSJS scripts with additional global variables.

Example:

var async_xsjs = require('@sap/async_xsjs');
var options = {
  anonymous: true,
  context: { answer: 42 }
};
(await async_xsjs(options)).listen(3000);

This configuration extends the context of XSJS scripts with one additional variable called answer. Every time an XSJS script is executed, it will not only have the $ variable in its context, but will also include the variable answer with value 42.

Let's say we have a file answer.xsjs with the following content:

await $.response.setBody(answer);

A request to http://<your_domain>:3000/answer.xsjs will respond with 42.

With the context property set, you can expose Node.js packages and variables:

var options = {
  anonymous: true,
  context: { environment: process.env, _: require('lodash') }
};

Currently, we are aware of a limitation which causes <variable> instanceof <constructor_function> used in an XSJS script to have odd behaviour. Also, stubbing or mocking constructor functions such as Date, String, etc in an XSJS script won't affect other XSJS files.

The context property also finds usage in a workaround for this limitations.

Setting:

var options = {
  anonymous: true,
  context: { Array: Array, String: String }
};

will fix these problems, but has the following side effects:

  • Creating an array in an XSJS script and checking its instance will now return false:
  var myArray = [1, 2, 3];
  await $.response.setBody(myArray instanceof Array); // Responds with false
  • Monkey-patching built-in types won't work as expected if they are exposed through the context property:
  String.prototype.contains = function(str) { return this.indexOf(str) >= 0; };
  var stringLiteral = 'Abc';
  var stringObject = new String('Abc');
  console.log(stringLiteral.contains); // undefined
  console.log(stringObject.contains); // [Function]

Since there might be other side effects, use this feature at your own risk.

libraryCache

An object that contains the .xsjslibs that will be cached. Example:

{
  'my.libs.utility': 'global'
}

In an XSJS code, the import looks like this:

var utility = await $.import('my.libs', 'utility');

Note: It is recommended to use this feature only when necessary, because it keeps the cached content until the application is running. It might have side effects in case the .xsjslib keeps state. It also leads to extensive memory consumption and this should be considered while calculating application memory limits.

Multitenant Usage

In multitenant scenarios, the hana and secureStore properties can contain an object with the credentials of a managed-hana (Instance Manager) service (created with the appropriate service plan) instead of the credentials of the hana service. In this case, the application will connect to a SAP HANA system, depending on the tenant (identity zone) of the incoming request. A managed service instance for the particular tenant should be created in advance, and the corresponding database artefacts should be deployed prior to requesting the application with this tenant. Otherwise, the processing of the request will be terminated with an error.

Note: Currently, jobs are not multitenant-aware. They are shared between tenants, and a connection to a SAP HANA database cannot be established. A job executing XSJS code can still connect to a specific SAP HANA service instance, by using SQLCC configuration.

OData Support

OData support is provided by OData package @sap/xsodata. Details on what features are provided can be found in the project itself. The compatibility layer scans for .xsodata files in the specified source directory and registers OData endpoints for each valid descriptor. Both JavaScript and SQL script exits are supported.

Clear OData model cache

For each OData service, the model is loaded and cached in memory upon first request. In case the schema of the underlying DB objects is changed at runtime, it is necessary to reload the model. Here is an example:

var app = await xsjs(options);

function onSchemaChange(tenant) {
  app.clearODataCache(tenant);
}
  • clearODataCache(tenant) clears the OData model cache for the given tenant.
  • tenant argument is optional. If not provided, the cache for all tenants will be cleared.

Note: Each application instance contains a cache of OData models. Clearing the cache in one of those instances does not automatically trigger cache invalidation in the others. The application itself is responsible for calling clearODataCache in each instance. Since an HTTP request is received only by a single instance, it cannot be used to trigger clearing the cache in a consistent manner for the whole application. Other solutions, like messaging, are more suitable for that purpose.

NPM Package Support

As an extension in the dollar API, we included support for all the available NPM packages.
For example, in your XSJS file you can add the following code:

var _ = $.require('underscore');
// Count to ten
var count = '';
_.range(11).forEach(function(number) {
  count += number + ' ';
});
await $.response.setBody(count);

You can also require a file relatively. The required file will execute in a Node.js context. This means, you will have access to global Node.js variables, such as __dirname, process, etc. in it.

For example, if we have a file called myAPI.js with content:

// myAPI.js
module.exports = {
  getDirname: function() {
    return __dirname;
  }
};

Let's say myAPI.js is located in a parent directory for the following XSJS file:

var myAPI = $.require('../myAPI.js');
await $.response.setBody(myAPI.getDirname());

Destinations Support

Via user provided services

By default, the compatibility layer supports destination configuration via user-provided services. The destination name (the repo resource ID, e.g. package + '.' + xshttpdest name) is matched to a service name.

Example content of VCAP_SERVICES:

"VCAP_SERVICES": {
  "user-provided": [
    {
      "label": "user-provided",
      "name": "foobar.httpdest.mydest",
      "credentials": {
        "host": "some.host",
        "port": 8088,
        "username": "user",
        "password": "secret"
      }
    }
  ]
}

Example usage in an XSJS code:

var destination = await $.net.http.readDestination('foobar.httpdest', 'mydest');

If there is no service in VCAP_SERVICES with the same name as the destination requested, an exception is thrown.

When a destination is read, the content of the design time descriptor is merged with the properties provided in the user-provided service. Property values of the UP service override DT descriptor values.

Via custom provider function

If the default support is not enough for your use case, you can provide a custom destination provider function. For details how to do that, see the destinationProvider configuration option explained above.

Accessing Column Values by Index in $.hdb.ResultSet Rows

Column values from within a row of $.hdb.ResultSet in XS Classic can be accessed either by column name or by column index. If an application does not make use of accessing columns by index, then this capability can be turned off, which will result in an improved performance:

var connection = await $.hdb.getConnection({ enableColumnIndices: false });

Tracing Via $.trace API

Each trace entry is associated with a location. The entry point being accessed is used as a location:

  • All application trace entries produced during OData handling (entries produced by exits and the imported .xsjslib scripts, @sap/xsodata entries) use the location of the .xsodata service itself, e.g. '/odata/service.xsodata'.
  • All application trace entries produced during job execution (entries from .xsjs scripts and the imported .xsjslib scripts) use the location of the .xsjob descriptor, e.g '/jobs/my-job.xsjob'.
  • All application trace entries produced by .xsjs code (including the imported .xsjslib scripts) use the location of the .xsjs file itself, e.g. '/xsjs/service.xsjs'. The same applies to scripts referenced from $.response.followUp.

This allows easier change of the tracing level (only of the entry point - .xsodata, .xsjob or .xsjs) without doing so for every single script involved in the execution or by using wildcards. The trace message can be used to find the source line that has produced it.

Note: Regarding request-ids for jobs: the run-id received from the jobscheduler is used as the request-id instead of an auto-generated one.

Troubleshooting

This package uses @sap/logging package, so all of its features are available to control logging. For example, to set all logging and tracing to the finest level, set XS_APP_LOG_LEVEL environment variable to debug.

If the application is deployed on XS Advanced On-Premise Runtime, you can change the log level without restarting the application. For example, the following command will set all logging and tracing to the finest level.

xs set-logging-level <application-name> "*" debug

See the @sap/logging documentation for more information.

Some of the libraries used by this package employ other tracing mechanisms. For example, many use the popular debug package. This means that by setting DEBUG environment variable, you can enable additional traces. Set it to * to enable all of them, but be careful as the output may be overwhelming.

In addition, internal Node.js traces can be enabled via the NODE_DEBUG environment variable.

Warning: Enabling some of these options may trace security sensitive data, so use with caution.

Differences with SAP HANA XS Classic

See the differences here.

Readme

Keywords

none

Package Sidebar

Install

npm i @sap/async-xsjs

Weekly Downloads

495

Version

2.0.0

License

SEE LICENSE IN LICENSE file

Unpacked Size

1.82 MB

Total Files

175

Last publish

Collaborators

  • sap_extncrepos