SAP-BTP-Spielwiese/app1/node_modules/@sap/approuter/doc/extending.md
Markus Rettig 775ac7b58c completed step 3 from the tutorial
you must login with an BTP account in order to see the app
2024-02-08 16:13:36 +01:00

13 KiB

Extending Application Router

Basics

Insead of starting the application router directly, your application can have its own start script. You can use the application router as a regular Node.js package.

var approuter = require('@sap/approuter');

var ar = approuter();
ar.start();

Inject Custom Middleware

The application router uses the connect framework. You can reuse all connect middlewares within the application router directly. You can do this directly in your start script:

var approuter = require('@sap/approuter');

var ar = approuter();

ar.beforeRequestHandler.use('/my-ext', function myMiddleware(req, res, next) {
  res.end('Request handled by my extension!');
});
ar.start();

Tip: Name your middleware to improve troubleshooting.

The path argument is optional. You can also chain use calls.

var approuter = require('@sap/approuter');
var morgan = require('morgan');

var ar = approuter();

ar.beforeRequestHandler
  .use(morgan('combined'))
  .use('/my-ext', function myMiddleware(req, res, next) {
    res.end('Request handled by my extension!');
  });
ar.start();

The application router defines the following slots where you can insert custom middleware:

  • first - right after the connect application is created, and before any application router middleware. At this point security checks are not performed yet. Tip: This is a good place for infrastructure logic like logging and monitoring.
  • beforeRequestHandler - before standard application router request handling, that is static resource serving or forwarding to destinations. Tip: This is a good place for custom REST API handling.
  • beforeErrorHandler - before standard application router error handling. Tip: This is a good place to capture or customize error handling.

If your middleware does not complete the request processing, call next to return control to the application router middleware:

ar.beforeRequestHandler.use('/my-ext', function myMiddleware(req, res, next) {
  res.setHeader('x-my-ext', 'passed');
  next();
});

Application Router Extensions

You can use application router extensions.

An extension is defined by an object with the following properties:

  • insertMiddleware - describes the middleware provided by this extension
    • first, beforeRequestHandler, beforeErrorHandler - an array of middleware, where each one is either
      • a middleware function (invoked on all requests), or
      • an object with properties:
        • path - handle requests only for this path
        • handler - middleware function to invoke

Here is an example (my-ext.js):

module.exports = {
  insertMiddleware: {
    first: [
      function logRequest(req, res, next) {
        console.log('Got request %s %s', req.method, req.url);
      }
    ],
    beforeRequestHandler: [
      {
        path: '/my-ext',
        handler: function myMiddleware(req, res, next) {
          res.end('Request handled by my extension!');
        }
      }
    ]
  }
};

You can use it in your start script like this:

var approuter = require('@sap/approuter');

var ar = approuter();
ar.start({
  extensions: [
    require('./my-ext.js')
  ]
});

Customize Command Line

By default the application router handles its command line parameters, but you can customize that too.

An approuter instance provides the property cmdParser that is a commander instance. It is configured with the standard application router command line options. There you can add custom options like this:

var approuter = require('@sap/approuter');

var ar = approuter();

var params = ar.cmdParser
  // add here custom command line options if needed
  .option('-d, --dummy', 'A dummy option')
  .parse(process.argv);

console.log('Dummy option:', params.dummy);

To completely disable the command line option handling in the application router, reset the following property:

ar.cmdParser = false;

Dynamic Routing

The application router can use a custom routing configuration for each request.

Here is an example:

var approuter = require('@sap/approuter');

var ar = approuter();
ar.start({
  getRouterConfig: getRouterConfig
});

var customRouterConfig;
var options = {
  xsappConfig: {
    routes: [
      {
        source: '/service',
        destination: 'backend',
        scope: '$XSAPPNAME.viewer',
      }
    ]
  },
  destinations: [
    {
      name: 'backend',
      url: 'https://my.app.com',
      forwardAuthToken: true
    }
  ],
  xsappname: 'MYAPP'
};
ar.createRouterConfig(options, function(err, routerConfig) {
  if (err) {
    console.error(err);
  } else {
    customRouterConfig = routerConfig;
  }
});

function getRouterConfig(request, callback) {
  if (/\?custom-query/.test(request.url)) {
    callback(null, customRouterConfig);
  } else {
    callback(null, null); // use default router config
  }
}

State synchronization

The application router can be scaled to run with multiple instances like any other application on Cloud Foundry. Still application router instances are not aware of each other and there is no communication among them. So if extensions introduce some state, they should take care to synchronize it across application router instances.

API Reference

approuter

approuter()

Creates a new instance of the application router.

Event: 'login'

Parameters:

  • session
    • id - session id as a string

Emitted when a new user session is created.

Event: 'logout'

Parameters:

  • session
    • id - session id as a string

Emitted when a user session has expired or a user has requested to log out.

first

A Middleware Slot before the first application router middleware

beforeRequestHandler

A Middleware Slot before the standard application router request handling

afterRequestHandler

A function that can be added to the request object - for example in a "first" or "beforeRequestHandler" extension. If exists, this function will be called by the standard application router after the standard backend response handling is completed. Input:

  • ctx context object containing the following properties
    • incomingRequest the request sent from client to application router
    • incomingResponse the response that will be sent from application router to client
    • outgoingRequest the request sent from application router to backend application
    • outgoingResponse the response that was received in application router from backend application
  • done a callback function that receives (optionally) and error and the modified incomingResponse

Note that this function is called after standard application router headers processing. Data piping is not modified. If an error is passed to done callback it will be just logged, piping process will not be stopped. Note that also in case of error the incomingResponse object should be returned.

Example:

var approuter = require('@sap/approuter');
var ar = approuter();
ar.first.use('/backend', function (req, res, next) {
    req.afterRequestHandler = function(ctx, done){
        if (ctx.outgoingResponse.statusCode === 200) {
          let incomingResponse = ctx.incomingResponse;
          incomingResponse.setHeader('header1', 'abc');
          done(null, incomingResponse);
        } else {
          done('An error occurred in backend, returned status ' + ctx.outgoingResponse.statusCode, ctx.incomingResponse);
        }
    };
    next();
});
ar.start();

backendTimeout

A function that can be added to the request object - for example in a "first" or "beforeRequestHandler" extension. If exists, this function will be called by the standard application router when a backend connection timeout occurs. Input:

  • req the request object
  • done a callback function that doesn't return any parameter

beforeErrorHandler

A Middleware Slot before the standard application router error handling

start(options, callback)

Starts the application router with the given options.

  • options this argument is optional. If provided, it should be an object which can have any of the following properties:

    • port - a TCP port the application router will listen to (string, optional)

    • workingDir - the working directory for the application router, should contain the xs-app.json file (string, optional)

    • extensions - an array of extensions, each one is an object as defined in Application Router Extensions (optional)

    • xsappConfig - An object representing the content which is usually put in xs-app.json file. If this property is present it will take precedence over the content of xs-app.json. (optional)

    • httpsOptions - Options similar to https.createServer. If this property is present, application router will be started as an https server. (optional)

    • getToken - function(request, callback) Provide custom access token (optional)

      • request - Node request object
      • callback - function(error, token)
        • error - Error object in case of error
        • token - Access token to use in request to backend
    • getRouterConfig - function(request, callback) Provide custom routing configuration (optional)

      • request - Node request object
      • callback - function(error, routerConfig)
        • error - Error object in case of error
        • routerConfig - Custom routing configuration to use for given request. This object should be created via createRouterConfig. If null or undefined, default configuration will be used.

      Note: When approuter is bound to html5 repository, you cannot provide getRouterConfig function.

  • callback - optional function with signature callback(err). It is invoked when the application router has started or an error has occurred. If not provided and an error occurs (e.g. the port is busy), the application will abort.

close(callback)

Stops the application router.

  • callback - optional function with signature callback(err). It is invoked when the application router has stopped or an error has occurred.

createRouterConfig(options, callback)

Prepares the routing configuration to be used by the application router. As part of this, the application router validates the given options. This function can be used at any point in runtime to create additional routing configurations.

Note: This function can be called only after start function.

  • options
    • xsappname - Value to replace $XSAPPNAME placeholder in scope names. If not provided, it will be taken from UAA service binding. (optional)
    • xsappConfig - An object representing the content which is usually put in xs-app.json file. Note: Only the following configurations are taken into account from this property (the rest are taken from the xs-app.json file): welcomeFile, logout.logoutEndpoint, logout.logoutPage, routes, websockets, errorPage.
    • destinations - An array containing the configuration of the backend destinations. If not provided, it will be taken from destinations environment variable. (optional)
  • callback - function(error, routerConfig)
    • error - Error object in case of error
    • routerConfig - Routing configuration to be passed to the callback of getRouterConfig. Approuter extensions should not access the content of this object.

resolveUaaConfig(request, uaaOptions, callback)

Calculates tenant-specific UAA configuration.

  • request - node request object used to identify the tenant
  • uaaOptions - UAA options as provided in service binding
  • callback - function(error, tenantUaaOptions)
    • error - Error object in case of error
    • tenantUaaOptions - new UAA configuration with tenant-specific properties

Middleware Slot

use(path, handler)

Inserts a request handling middleware in the current slot.

  • path - handle only requests starting with this path (string, optional)
  • handler - a middleware function to invoke (function, mandatory)

Returns this for chaining.