Appearance
SAP Application Router
Node.js based application router.
When a business application consists of several different apps (microservices), the SAP Application Router is used to provide a single entry point to that business application
Not part of the SAP Discovery Center!
Almost every application in Cloud Foundry requires an Application Router to authenticate users and dispatch requests to microservices. It needs to be configured and deployed by using the offered @sap/approuter
npm package.

Feature | Description |
---|---|
Reverse proxy | Dispatch requests to backend microservices |
UAA | Authenticate users |
Static resources | Serve static content |
Routes
The following configurations are examples of how to integrate the respective services into the Approuter.
SAP HTML5 App Repository
If using a HTML5 App Repo, generally all unknown requests are forward to it by the last rule:
json
{
"source": "^(/.*)",
"target": "$1",
"service": "html5-apps-repo-rt",
"authenticationType": "xsuaa"
}
The route name of an UI5 app is defined in the manifest.json
file:
json
{
"sap.app": {
"id": "namespace.app"
...
}
}
The target route name looks like this:
APP_ID = namespaceapp (chars other than [A..Z][0..9] will be removed!)
With this name, you can reference the app behind the approuter out-of-the-box.
Different Route for HTML5 app
If you need a different route (besides above) you have to take care of some specialities! The Repo uses a cache proxy mechanism using ~(.*)~
cache parts, that need to be handled be the route. Also some files should not be cached, so they need a special cache control.
Usage of a custom route ROUTE_NAME
for app APP_ID
json
{
"source": "^/ROUTE_NAME/index.html(.*)$",
"target": "/APP_ID/index.html$1",
"service": "html5-apps-repo-rt",
"cacheControl": "no-cache, no-store, must-revalidate"
},
{
"source": "^/ROUTE_NAME/sap-ui-cachebuster-info.json(.*)$",
"target": "/APP_ID/sap-ui-cachebuster-info.json$1",
"service": "html5-apps-repo-rt",
"cacheControl": "no-cache, no-store, must-revalidate"
},
{
"source": "^/ROUTE_NAME/~(.*)~/(.*)$",
"target": "/APP_ID/$2",
"service": "html5-apps-repo-rt"
},
{
"source": "^/ROUTE_NAME/(.*)$",
"target": "/APP_ID/$1",
"service": "html5-apps-repo-rt"
}
UI Theme Designer Runtime
Using a custom theme, an additional route is nessessary to deliver the endpoint to the com.sap.ui.theming
service with the approuter:
json
{
"source": "^/themes/(.*)$",
"target": "$1",
"service": "com.sap.ui.theming",
"endpoint": "runtime",
"authenticationType": "xsuaa"
}
UI5 Versioning
SAPUI5 can either be loaded locally with a relative path from an SAP Web server or externally from a Content Delivery Network (CDN).
Bootstrapping from SAPUI5 CDN
Loading SAPUI5 from a CDN improves your app performance: You can load from a server that (in most cases) is much closer to your location, and you can benefit from the caching mechanism and the language fallback logic.
SAP removes SAPUI5 versions from CDN one year after end of maintenance!
To ensure outdated versions are no longer posing a potential security risk, SAP removes SAPUI5 versions from the SAPUI5 CDN one year after their end of maintenance. In addition, also patches of versions in maintenance older than one year will be removed. Details see in SAP Note 3001696. It's recommended to always upgrade to the latest version in order to get the latest fixes and improvements!
Variants for Bootstrapping from CDN
Default Version | Evergreen Version | Specific Version |
---|---|---|
New releases approximately 2 weeks after the release becomes the default version. | The evergreen version allows you to automatically select the latest available patch level of a specific (minor) long-term maintenance version for bootstrapping. You refer to a particular <majorVersion>.<minorVersion> long-term maintenance version using a versioned URL. | The specific version allows you to select a particular fixed version for bootstrapping. You can refer to a specific version by using a versioned URL. |
../resources/.. | ../1.120/resources/.. | ../1.120.25/resources/.. |
Deployment Options
Option | Default Version | Evergreen Version | Specific Version | Switch Tasks | Bandwidth Costs |
---|---|---|---|---|---|
Destination | ✓ | ✓ | - | Yes | |
App Router Extension | ✓ | ✓ | ✓ | Restart | No |
Recommendation from a maintenance and cost perspective
The AppRouter variant allows the use of Evergreen bootstrapping to automatically benefit from security updates within an LTS release. In case of a problem, it is possible to immediately switch to a previous specific version without the need for a deployment. So you get the best of both options.
Always using the latest version is not an option, as there may be incompatible changes that need to be tested in advance.
Services are being retired by SAP!
When a service is retired, the plan is no longer available and the solution cannot be redeployed. Therefore, dependencies such as the UI5_VERSION must be controllable externally.
App Router Extension
Replacement allows to manipulate text content before delivering using placeholder replacement in static text resources.
The version part of the index.html cdn reference can be outsourced to a environment variable set in the btp approuter context
Use UI5_VERSION
placeholder inside index.html
template.
hbs
src="https://ui5.sap.com/{{UI5_VERSION}}/resources/sap-ui-core.js"
Create my-ui5version-ext.js
ui5 version replacement middleware
js
'use strict';
const UI5_VERSION = process.env.UI5_VERSION || 'latest'
const UI5_VERSION_RE = (UI5_VERSION === 'latest')
? '' : `${UI5_VERSION}/`
module.exports = {
insertMiddleware: {
beforeRequestHandler: [
{
// manifest sap.app.id (replaced dots with '')
path: '/MYNSAPP/index.html',
handler: replaceConstant('{{UI5_VERSION}}/',
UI5_VERSION_RE)
}
]
}
}
function replaceConstant(constant, replacement) {
return (_req, res, next) => {
let write = res.write;
let end = res.end;
let chunks = [];
res.write = function (chunk) {
chunks.push(chunk);
};
res.end = function (chunk) {
if (chunk) chunks.push(chunk);
let body = Buffer.concat(chunks).toString('utf8');
if (res.getHeader('Content-Type')
&& res.getHeader('Content-Type').includes('text/html')) {
body = body.replace(new RegExp(constant, 'g'), replacement);
}
res.setHeader('Content-Length', Buffer.byteLength(body));
write.call(res, body);
end.call(res);
};
next();
};
}
Use mustache for complex replacements
js
// mustache is availavle as an App Router dependency
const Mustache = require('mustache');
// Logic-less {{mustache}} templates with JavaScript
const body = Mustache.render(body, { constant: replacement });
Create approuter.js
launcher file that includes extension
js
const approuter = require('@sap/approuter');
const ar = approuter();
ar.start({
extensions: [
require('./my-ui5version-ext.js')
]
});
Change package.json
start script to launch custom approuter.js
json
{
"scripts": {
"start": "node approuter.js"
}
}
Add User-Provided Variable
inside mta.yaml
yaml
modules:
- name: approuter
type: approuter.nodejs
path: app/router
properties:
# UI5_VERSION: latest # Default Version (latest)
UI5_VERSION: 1.120 # Evergreen Version
# UI5_VERSION: 1.120.25 # Specific Version
Set UI5 Version with BTP Cockpit
After redeployment there will be a custom UI5_VERSION
variable available inside:
Cloud Foundry -> Space -> Service -> Apps -> AppRouter -> User-Provided Variables
or create the variable manually using Create Variable
button.
Select User-Provided Variables
and set UI5_VERSION
to the needed version.
Inside Overview
press Restart
button (reload env vars).
Destination
The Destination allows to externalize and configure the used cdn path using an ui5
alias inside the AppRouter as destination.
Automatically created by Fiori Tools
This is the default behavior by the SAP Fiori Tools, but SAPUI5 does not support Evergreen Versions without having version string inside uri path!
Use sub part of the CDN API inside index.html
template.
html
src="/resource/sap-ui-core.js"
Add replacement rule to xs-app.json
json
{
"source": "^/resources/(.*)$",
"target": "/resources/$1",
"destination": "ui5"
}
Create default destination inside mta.yaml
yaml
resources:
- name: APP-destination-service
type: org.cloudfoundry.managed-service
parameters:
init_data:
subacount:
destinations:
- Name: ui5
Authentication: NoAuthentication
ProxyType: Internet
Type: HTTP
# Default Version (not for production!)
# URL: https://ui5.sap.com
# Evergreen Version (not working/supported!)
# URL: https://ui5.sap.com/1.120
# Specific Version
URL: https://ui5.sap.com/1.120.25
Extending
See extending for information how to extend the application router with custom logic.
You can use the application router as a regular Node.js package. Insead of starting the application router directly, your application can have its own start script:
approuter.js
js
var approuter = require('@sap/approuter');
var ar = approuter();
ar.start({
extensions: [
require('./my-ext.js')
]
});
Implement the addtional logic in custiom middlewares:
my-ext.js
js
'use strict';
const jwtDecode = require('jwt-decode')
module.exports = {
insertMiddleware: {
first: []
beforeRequestHandler: [
{
path: '/',
handler: function forwardUserInfo(req, res, next) {
res.setHeader('x-authenticated', !!req.user)
next()
}
},
{
path: '/me',
handler: function getUserInfo(req, res, next) {
if (!req.user) {
res.statusCode = 403;
res.end('Missing JWT Token');
} else {
const { user } = req
const token =
jwtDecode(user.token.accessToken);
res.statusCode = 200;
res.setHeader('Content-Type",
'application/json');
res.setHeader('Cache-Control',
'no-cache, no-store');
res.end(JSON.stringify({
'id': user.id,
'loginName': user.id,
'firstName': token.given_name,
'lastName': token.family_name,
'email': token.email,
'scopes': user.scopes,
'roles': token['xs.system.attributes']
['xs.rolecollections'],
'sessionTimeout':
req.routerConfig.sessionTimeout
}));
}
}
},
],
afterRequestHandler: []
}
};
Support
Component Name | Component Description |
---|---|
BC-XS-APR | Support component for this service |