REST Handler Middleware for Akera Application Server.
As opposed to the @akera/web that can only execute special
'handlers' - either the 'classical' WebSpeed wrap-cgi
procedures (includes wrap-cgi.i
) or the new
WebHandler
objects (implements OpenEdge.Web.WebHandler
) - this module ca be used to expose
generic business logic by dynamically calling any procedure (internal entries included) through the
api module or expose database(s) content for basic CRUD operation through the crud or jsdo modules.
$ npm set registry "http://repository.akera.io/"
$ npm install @akera/rest
doc
folder.var akeraRest = require('@akera/rest')
The akeraRest
object exposes a number of factories that can be used to create an Express middleware.
Often when building web interfaces it might happen the web server
does not share the same machine
as the server exposing micro-services
and CORS related issues might be experienced. This is also true
for akera.io REST services, this can be solved by using the CORS module.
Returns an akera.io API Handler middleware that can be used on an Express application to execute any generic business logic by passing input/output parameters much like the @akera/api does.
The middleware can then be used to call business logic procedures on the akera.io broker(s) by making
HTTP POST
or GET
requests and send the business logic call information using the request parameters.
The api
interface must be enabled on all akera.io application server(s) used, otherwise the API
connections are rejected by the brokers.
Can be either a 'shared' @akera/web instance or one or more broker configuration(s) for remote akera.io brokers. If one configuration is used then all requests on the middleware router will be dispatched directly to that broker.
When multiple configurations are used then for each unique broker a sub-route is added
to the router using the broker's alias as route and each request on the middleware requires
one route parameter - the broker alias, based on which the request is dispatched to the
corresponding broker. If all configurations have the same alias - fail-over
configuration,
then the broker will be mounted on the router's root as if a single configuration is provided.
An instance of AkeraLogger
to be used by the router for logging.
When a single broker is configured for the middleware the api
interface will dispatch all requests
to that one broker:
POST
URL
: /
BODY
: Business Logic Information
in JSON format.Content-Type
: application/json
GET
URL
: /
Query String
: ?call=[call]¶meters=[parameters]
Parameters must be an encoded string value of the JSON array holding parameters
information. Optional information can be sent through additional query string
parameters: method
, type
, returns
.
For convenience more specific routes were defined for calling internal entries:
/[external procedure path]/procedure/[internal procedure name]
/[external procedure path]/function/[internal function name]
/[class full name]/method/[instance method name]
/[class full name]/static/[static method name]
The information on what business logic need to be executed is sent either in
a JSON format for POST
requests or through query parameters for GET
requests.
call
: the business logic object namemethod
: the internal entry of the business logic objecttype
: the call
type (procedure
, function
, method
or static
).returns
: the data type of return parameter, only for function or method.parameters
: array of optional call parametersThe business logic object name must include the relative path (if any), and it can be an external procedure or an OO class.
If method
value is not set the business logic must be an external procedure
that normally takes parameters although this is not required.
When calling internal entries of 'super procedures' the external procedure
must not have any parameters. Depending on the type
value an internal
function
or procedure
can be executed, the name of the internal entry is
specified using the method
information.
When calling methods of OO instance objects the class must have a
public constructor with no parameters. Depending on the type
value
an instance (method
) or a static
method can be executed, the name of the
method is specified using the method
information.
If type
value is not set it defaults to procedure
.
If the parameters
sent does not match the business logic entry parameters an
error will be thrown back.
Each parameter entry has the following structure:
type
: parameter data typedirection
: parameter directionvalue
: parameter value for input/input-output parametersjson
: for output/input-output parameters of type character/longchar.All Progress primitive data types are supported as input/output/input-output
parameters - character
, date
, datetime
, dataset
, decimal
, integer
,
int64
, logical
, longchar
, memptr
- for output complex data types like
table
and dataset
are also supported.
If type
is not specified it defaults to character.
Valid values for parameter direction
are:
input
(in
, i
), if not set this is the default.output
(out
, o
)input-output
(inout
, io
)For input/input-output parameters if value
is not set it defaults to null
(?
in 4GL).
If json
flag is set to true the value is sent as a JSON object instead of encoding it as a string.
Dataset and table parameters are always sent as JSON objects.
The response is a always a JSON object sent as response body with a parameters
property
as array.
Only output and input-output parameters are sent back in the parameters
array keeping
the same order as in the input parameters array - the first entry in the array will be
the first parameter with direction input
or input-output
.
If an internal function
or method
is executed the response object might also have an
return
object, the value depends on the data type of the function/method return parameter.
{ "parameters": [
"Hello world!",
2018
],
"return": true
}
If there was an error while executing the business logic the response body will contain an
error
property containing the error message as string.
{"error": "Mismatched number of parameters passed to routine xxxxxx.p. (3234)"}
The Express.js application or router where the middleware is mounted must have a
JSON body parser installed in order for POST
requests to work (Content-Type
header value set to application/json
).
Only the procedures available in akera.io application server WEBPATH
can be executed - not everything
from agent's PROPATH
is available through the REST API interface.
A 'failsafe' configuration is defined using two akera.io application servers
running on different ports on the same computer as the Express web server (localhost).
All requests sent on /api
path will be handled by the API Handler middleware and will
be dispatched to the first broker to which a connection can be established.
All requests sent on /crud
path will be handled by the CRUD Handler middleware while
those made on /jsdo
will be handled by the JSDO Handler.
import * as express from 'express';
import * as bodyParser from 'body-parser';
import akeraRest from '@akera/rest';
let app: express.Application = express();
let config = [ { "name": "demo",
"host": "localhost",
"port": 8900 },
{ "name": "failsafe",
"alias": "demo",
"host": "localhost",
"port": 9900 } ];
app.use(bodyParser.json());
app.use('/api', akeraRest.api(config));
app.use('/crud', akeraRest.crud(config));
app.use('/jsdo', akeraRest.jsdo(config));
A HTTP POST
request made on /api
with the body:
{
"call": "demo/sports/getCustBalance.p",
"parameters": [
{ "direction": "i", "value": 2 },
{ "direction": "o", "type": "decimal" },
{ "direction": "o", "type": "decimal" },
{ "direction": "o", "type": "decimal" }
]
}
will run the external procedure demo/sports/getCustBalance.p
on the akera.io
application server passing one input parameter with a value of 2
and expecting
three output parameters all of decimal
data type.
A configuration is defined using two separate akera.io application servers
running on different ports on the same computer as the Express web server (localhost).
All requests sent on /api
path will be handled by the API Handler middleware and will
be dispatched to the corresponding broker based on the broker alias (first route parameter).
All requests sent on /crud
path will be handled by the CRUD Handler middleware while
those made on /jsdo
will be handled by the JSDO Handler.
import * as express from 'express';
import {AkeraWeb} from '@akera/web';
import akeraRest from '@akera/rest';
let app: express.Application = express();
let config = [ { "name": "demo1",
"host": "localhost",
"port": 8900 },
{ "name": "demo2",
"host": "localhost",
"port": 9900 } ];
let akeraWeb = new AkeraWeb(config);
app.use('/api', akeraRest.api(akeraWeb));
app.use('/crud', akeraRest.crud(akeraWeb));
app.use('/jsdo', akeraRest.jsdo(akeraWeb));
A request made on /api/demo1
will be executed on the demo1
broker while requests
made on /api/demo2
will be dispatched to the demo2
broker.
Multiple akera.io application servers can be configured for each 'broker' - using
the same alias, for failover
setup.
As of version 1.0.8 the interface also support GET
requests with procedure
and parameters
values passed through the query string value, also on POST
requests the call
wrapper object is no longer mandatory - procedure
and
parameters
can be sent directly into the root JSON object.
As of version 1.0.10 the interface also support json
flag for
output/input-output string parameters (characters/longchar).
As of version 1.1.1 the API
and CRUD
interfaces were integrated in
@akera/rest
and code was migrated to typescript.
MIT