ILEastic lets you define endpoints and routes HTTP requests to these endpoints. This works great as you can add a regular expression for the matching URL to the call of il_addRoute to specify which URLs should be routed to which endpoints.

But there are cross cutting concerns (see aspect oriented programming) that affect the processing of most or all requests like authentication, authorization or logging. We don’t want to put those parts of the request processing in every endpoint … and we don’t have to.

ILEastic has a workflow for processing requests. This workflow has two points (exit points) where we can hook into and execute our own procedures.

We call these procedures plugins.

plugin workflow

So we can execute our own code before the request is routed to our endpoint (pre request). This is a great place for doing authentication and authorization. And we can execute our own code after the endpoint procedure has been called (post response). Here we can do some cleanup for example.

Chaining

We can not only add plugins to our processing workflow but we can also tie them together to a chain of plugins. This is done automatically when you add multiple plugins to an exit point (pre request or post response). These plugins will be called in the order we register them with il_addPlugin.

By using this feature we can setup workflows like the following:

  1. retrieve authentication HTTP header from request (in pre request exit point)
  2. evaluate authentication information (f. e. authenticate with user profile in pre request exit point)
  3. call endpoint procedure
  4. cleanup (in post response)

Now the endpoint procedure doesn’t have to handle authentication at all. Every request which is processed by the endpoint procedure has already been authenticated by the plugins.

Plugin Interface

A plugin is just a procedure which has a predefined interface which it must implement.

dcl-pi *n ind;
    request  likeds(IL_REQUEST);
    response likeds(IL_RESPONSE);
end-pi;

It can modify the request and response as needed, f. e. evaluating the request or adding HTTP headers to the response.

Note: Anything added to a response at the pre request exit point will be available to the endpoint procedure as it is the same response data structure which is used in both procedures. Because of that anything you would do in a pre response exit point (which does not exist) can be done in the pre request exit point, f. e. adding CORS HTTP headers to the response.

The return value tells ILEastic if it should proceed or abort processing of the request.

Plugin Configuration

Some plugins may need some configuration data, f. e. a JWT sign key or a Basic Auth realm name. This data needs to be scoped to the current job but available to all threads in the current job. We can achieve this very easily thanks to the ILE concept and the multi threading support in RPG.

The configuration data can be placed into a global variable of the module (regardless if it is directly bound to the web service program or to another service program). We need to make this global variable available to the outside by either using the export keyword or a setter procedure which assigns the passed value to the global variable. The web service module can now pass the configuration data either directly or indirectly to the module.

Note: As this configuration data should be available to all threads we need to use the keyword static(*allthread) on the global variable which holds the configuration data.

Breaking the Chain

We may need to stop ILEastic from calling further plugins and stop processing the request (f. e. on a not authenticated request). This is very easy to do simply by returning *off in a plugin procedure. This tells ILEastic to stop processing the current request.

Route Id

In some workflows we want to handle different routes in different ways, f. e. we want to handle authorization on a per route basis. The workflow for this might look like this:

  1. Plugin A : retrieve authentication data from HTTP Authorization header
  2. Plugin B : authenticate request
  3. Plugin C : get authorization data for authenticated user
  4. Plugin D : authorize request
  5. End Point : handle request
  6. Plugin E : cleanup

An easy way of sharing data between plugins is to put the data into the thread local memory of the request, see il_getThreadMem. By doing this you don’t have to worry about any cleanup as this is done by ILEastic itself.

The id of a route (end point) can be set on the registration call (last parameter):

il_addRoute(config : %paddr(list) : IL_GET : '/api/champion' : *omit : 'champion_list');

The route id is part of the data structure il_request and can be retrieved by simply accessing the data structure subfile routeId:

dcl-proc list;
    dcl-pi *n ind;
        request  likeds(IL_REQUEST);
        response likeds(IL_RESPONSE);
    end-pi;

    ...

    if (request.routeId = 'champion_list');
        ...
    endif;

    ...
end-proc;

Available Plugins

Basic Auth

The Basic Auth HTTP header is retrieved from the request and the username and password are stored in the thread local storage (TLS) of the request. It can be retrieved from the thread local storage by using il_getThreadMem.

  • /ileastic/auth/username : username
  • /ileastic/auth/password : password

System Authentication

The request is authenticated on the hosting system using the systems user profiles. The credentials from the thread local storage are used.

  • /ileastic/auth/username : username
  • /ileastic/auth/password : password

JWT

The JWT authentication information is retrieved from the request and stored in TLS.

  • /ileastic/jwt/token : complete authentication HTTP header value
  • /ileastic/jwt/payload : decoded JWT payload data as noxDB graph

Note: Currently only Bearer tokens are supported with the algorithm HS256.

CORS

Some application may need CORS headers to be set on the HTTP responses. The CORS plugin does this for every response. We just need to register it like this:

il_addPlugin(config : %paddr('il_addCorsHeaders') : IL_PRE_REQUEST);

Note: It will add allow all HTTP header entries.