Advanced Endpoints

Advanced Endpoints are a very powerful way to embed your corporate logic directly in the Web API. This eliminates the need of the middleware between inmation and ERP or other business applications. For instance, Web API can directly react on ERP HTTP requests and respond in a format, which is most suitable for the caller.

  • Allows advanced endpoint implementation.

  • Behave as a native endpoints.

  • Is able to route any URL.

  • Supports many HTTP methods.

  • Accepts various types of arguments

  • Response can be 'any' text based format

Description

The advanced endpoint embeds custom endpoints by hosting Lua libraries in the system. The name of the Lua library will be the first part of the custom path, the second part of the custom path will be the function name, unless the library returns a function itself. For example: api/v2/execfunction/corporate/readMachineHistory. In this example, 'corporate' is the name of the Lua library and 'readMachineHistory' is the name of the function, which will be executed to process the request.

The implementation of the Advanced Endpoint is done in Lua. The Lua script will be stored as a Script Library on the Web API object in the Server Model as a Script Library. Such a library can be implemented in two distinctive ways:

  1. Returning an object (Lua table) containing function(s).

  2. Returning one function.

The signature of the function is always the same. Containing three arguments: (arg, req, hlp).

  • (arg) argument
    The content of the arg parameter depends on what is passed in the body of the HTTP request and will be set as follows:

    • Empty Body: If the request body is not set or empty, arg will be initialised to an empty Lua table.

    • The body is a JSON object - More specifically, the request payload is of type JSON and the root element is not an array. For example:

      {
          "start_time": "2019-04-02T10:00:00.000Z",
          "limit": 35
      }

      In this case, the arg parameter will be set to a Lua table which looks like this:

      {
          ["start_time"] = "2019-04-02T10:00:00.000Z",
          ["limit"] = 35
      }

      In other words, the arg Lua table will be a dictionary with a key-value-pair for each property in the JSON object. The values of the properties will be converted to Lua strings, numbers or tables as appropriate.

    • All other non-empty request bodies: If the request content type is not JSON or it is a JSON array (as opposed to a JSON object), then arg will be a Lua table with a single key called data and value set to the request body. Let’s look at a couple of examples:

      • Text Request body: Let’s say the body was set to “Hello World”, in this case arg will look like this:

        {
            data = "Hello World"
        }
      • JSON Array body: Body contains a simple array, say [1, 2, 3, 4]. In this case, the arg parameter will be set to:

        {
            data = { 1, 2, 3, 4 }
        }
      • Array of Objects body: Body contains an array of objects: [ { “temp”:25.2}, {“temp”:27.1} ]. Then, we get a Lua table that looks like this:

        {
           data = {
                  { temp = 25.2 },
                  { temp = 27.1 }
           }
        }
  • (req) argument
    A Lua table which contains the information about the request. On root level the req argument contains the following fields:

    • access_token_payload
      Optional, holds the payload of the provided JSON Web Token (JWT).

    • headers
      A Lua table containing the HTTP request headers.

    • method
      The HTTP method of the request, like DELETE, GET, POST, PUT.

    • path
      The part of the path after the function name. If no path is provided, the value will be an empty string.

    • query
      A Lua table which contains the provided URL query parameters. If no query parameter is provided, the value will be an empty Lua table.

    • remote_ip_address
      The client’s IP Address.

    • server_oid
      The object id of the Web API Server object, which processes the request.

    • context_info
      A Lua table which contains the context information of the request. The ctx field specifies the path of the object in which the script is running. The profile field contains the name of the profile used as security context. The topic field contains the name of the endpoint, like execfunction, read, write. In case of an Advanced Endpoint or Exec Function request, the execfunction field holds a Lua table containing the fields lib and func.

    • url
      A Lua table which contains the full URL (href) and its components protocol, hostname, port, pathname and optional the search and hash.

The advanced endpoint accepts any path. The part of the path after the function name can be accessed by the path field of the request req argument. URL query parameters can be accessed by the query fields. The path and query fields can be used to handle routing within Lua.

`/api/v2/execfunction/corporate/readMachineData/lab/1234`

This will result in the readMachineData will be executed of the library corporate where the /lab/1234 can be accessed on the req.path parameter.

The values in both the path and query fields of the (req) argument are unescaped. In versions <1.92 the values were per percent-encoded (URL) strings.

  • Example:

    /api/v2/execfunction/echo/custom path with spaces?param01=valueWith Space
    • In versions <1.92 (escaped)

      • "path": "/custom%20path%20with%20spaces"

      • "query": { "param01": "valueWith%20Space"}

    • In versions >=1.92 (unescaped)

      • "path": "/custom path with spaces"

      • "query": { "param01": "valueWith Space"}

The provided query parameters in the URL are accessible through the query field of the request req argument. This field is a Lua table which contains the query parameter names as keys and query parameter values as strings. If the query parameter is provided more than once, this query table contains one entry of which the value is a Lua table string array.

The HTTP request type can be accessed by the method field. This field contains one of the following string values: DELETE, GET, HEAD, OPTIONS, PUT, POST, PATCH.

  • (hlp) argument
    The hlp argument is a helper object which contains convenience functions, like checkpermission and createResponse.

    The createResponse function can be used to set the response body, error, HTTP status code and response headers, which should be returned by the Web API.

    Extended explanation and examples of how to use the helper function can be found in the execfunction endpoint documentation.

Examples

The Advanced Endpoint can make use of the standard logic in order to e.g. read historical data. This way it is very easy to add custom logic in combination with standard API functionality. Add the code examples to the Script Library of the Web API Server Model object. Name the library "my-lib" and click Apply to save changes.

The standard API logic can be accessed via the syslib.api Lua library by using require('syslib.api') to the top of the Script Library in the Web API Server Model object.

To test these examples, please download Postman here. This allows you to make calls to the Web API by constructing query URLs.

Remember to enable Authentication by adding authorization credentials to your URL requests. This can be done in the header of the request.

Example - Read Historical Data

This simple example reads historical data for a performance counter. In the function call, a start time must be supplied.

local inAPI = require('syslib.api')

local lib = {}

function lib:readperfcounters(arg, req, hlp)
    arg = arg or {}
    local now = syslib.currenttime()
    local startTime
    if type(arg.starttime) == 'string' then
        -- Use starttime supplied by the caller.
        startTime = arg.starttime
    else
        -- Fallback to a default relative starttime
        startTime = syslib.gettime(now-(5*60*1000))
    end
    local endTime = syslib.gettime(now)
    local qry = {
        start_time = startTime,
        end_time = endTime,
        intervals_no = 100,
        items = {
            {
                p = "/System/Core/Performance Counter/Core/CPU",
                aggregate = "AGG_TYPE_INTERPOLATIVE"
            }
        }
    }
    return inAPI:readhistoricaldata(qry, req, hlp)
end

return lib

Invoke this endpoint by:

Method: POST

URL: /api/v2/execfunction/my-lib/readperfcounters

Body:

{
    "start_time": "2019-04-02T10:00:00.000Z"
}

Example - Read Raw Historical Data with filter

This example is used to read raw historical data for a performance counter. For this example, only a start time and a value must be specified in the function call. This value is used to filter results so that only values greater than or equal to the filter value are returned.

local inAPI = require('syslib.api')

local lib = {}

function lib:readperfcounters(arg, req, hlp)
    arg = arg or {}
    local now = syslib.currenttime()
    local startTime
    if type(arg.starttime) == 'string' then
        -- Use starttime supplied by the caller.
        startTime = arg.starttime
    else
        -- Fallback to a default relative starttime
        startTime = syslib.gettime(now-(5*60*1000))
    end
    local endTime = syslib.gettime(now)
    local qry = {
        start_time = startTime,
        end_time = endTime,
        filter = {
            v = {
              ["$gte"] = arg.limit or 50
            }
        },
        items = {
            {
                p = "/System/Core/Performance Counter/Core/CPU",
            }
        }
    }
    return inAPI:readrawhistoricaldata(qry, req, hlp)
end

return lib

Invoke this endpoint by:

Method: POST

URL: /api/v2/execfunction/my-lib/readperfcounters

Body:

{
    "start_time": "2019-04-02T10:00:00.000Z",
    "limit": 35
}

Example - Read Raw Historical Data and respond in CSV format

This example shows how to read raw historical data for one of the performance counters. The required parameters are a start time and a value. This value is used to filter results so that only values greater than or equal to the filter value are returned. The createResponse helper function is used to set the content-type of the response body to CSV format.

local inAPI = require('syslib.api')

local lib = {}

function lib:readperfcounters(arg, req, hlp)
    arg = arg or {}
    local now = syslib.currenttime()
    local startTime
    if type(arg.starttime) == 'string' then
        -- Use starttime supplied by the caller.
        startTime = arg.starttime
    else
        -- Fallback to a default relative starttime
        startTime = syslib.gettime(now-(5*60*1000))
    end
    local endTime = syslib.gettime(now)
    local qry = {
        start_time = startTime,
        end_time = endTime,
        filter = {
            v = {
              ["$gte"] = arg.limit or 50
            }
        },
        items = {
            {
                p = "/System/Core/Performance Counter/Core/CPU",
            }
        }
    }
    local respData = {}
    local res = inAPI:readrawhistoricaldata(qry, req, hlp)
    local rawData = res.data or {}
    local histData = rawData.historical_data or {}
    local queryData = histData.query_data or {}
    if #queryData > 0 then
        queryData = queryData[1]
        local items = queryData.items or {}
        if #items > 0 then
            items = items[1]
            for i,t in ipairs(items.t) do
                local value = items.v[i]
                local timestampISO = syslib.gettime(t)
                local row = ("%s,%s"):format(timestampISO, value)
                table.insert(respData, row)
            end
        end
    end
    local result = table.concat(respData, '\n')
    return hlp:createResponse(result, nil, 200, { ["Content-Type"] = "text/csv" })
end

return lib

Invoke this endpoint by:

Method: POST

URL: /api/v2/execfunction/my-lib/readperfcounters

Body:

{
    "start_time": "2019-04-02T10:00:00.000Z",
    "limit": 35
}