Calling Web Services

Avantra provides a lightweight HTTP client to allow customers to call web based services on SAP and other systems to allow you to perform custom checks, custom automation steps or custom script notification output channels.

With this API we aim to make it easy for developers to make use of the response (either JSON or XML) or perform your own logic on the raw response.

Code Walkthrough

The global object web provides access to our lightweight HTTP client API that was introduced in 21.11 and will be used in this walkthrough.

Performing a GET request

Perfoming a GET request can be accomplished with a single line making use of method chaining in JavaScript to get a response object that can then be interrogated for information retrieved from the service.

In this example, we assume http://example.domain.com/service is a valid HTTP service that we can query:

const res = web.request("http://example.domain.com/service")
                .fetch();

// Prints: My service responded with <HTTP status code e.g. 200>
console.log("My service responded with " + res.status);
// Prints: With a body of <response body>
console.log("With a body of " + res.body);

Receiving JSON data

If the code above returned a JSON response, res.body would contain a raw string that we would need to process in our script.

We provide a couple of methods to do this:

// Assume this service returns the following JSON:
// { message: "Hello JSON world!" }
const res = web.request("http://example.domain.com/service/json")
                .fetch();

console.log(res.body); // Prints the entire JSON

// Option 1: manually interpret the response ourselves:
const jsonBody = JSON.parse(res.body);
console.log(jsonBody.message); // "Hello JSON world!"

// Option 2: use the built in json object
console.log(res.json.message); // "Hello JSON world!"

This can be particularly useful if you need to use different properties of the response.

Receiving XML data

If the service you want to use returns an XML response, we have a built in parser that converts the XML structure to JSON and allows you to access this in the script. And as always, res.body contains a string version of the content received.

// Assume this service returns the following XML:
// <message>
//      <text>Hello XML world!</text>
// </message>
const res = web.request("http://example.domain.com/service/xml")
                .fetch();

console.log(res.body); // Prints the entire XML

// Or use the the built in xml object:
console.log(res.xml.text); // "Hello XML world!"

Because XML can be quite a bit more complicated, for example, with multiple child tags named the same and tags having attributes etc., the mapping from the XML is sometimes not as straightforward so that we don’t lose information.

For more information on this, see the xml property documentation below.

If at any point you would like to understand the particular mapping of your XML service, you can print the JSON object out in and view this in the check tester.

const res = web.request("http://example.domain.com/service/xml")
                .fetch();

// Converts the response XML to JSON then back to a string
// and outputs to the check result:
console.log(JSON.stringify(res.xml));

Sending Data

It is also possible to send data using this API and make POST, PUT, PATCH, or DELETE requests on a web service.

Data can be provided to the request object by setting the raw body and a content type header or using the utility json method which essentially does this for you.

const req = web.request("http://example.domain.com/service/json");

req.method(web.POST);

req.body("This is the POST body to send");

req.header("Content-Type", "text/html; charset=UTF-8");

const res = req.fetch();

console.log("Response status = " res.status);

Of course, we can utilise method chaining in the API to make this easier to understand. The following is equivalent to the last example:

const res = web.request("http://example.domain.com/service/text")
                .method(web.POST)
                .body("This is the POST body to send")
                .header("Content-Type", "text/html; charset=UTF-8")
                .fetch();

console.log("Response status = " res.status);

If the content to be sent is JSON, we can also use the json utility method and the API will set the correct content-type header for us.

const content = {
    message: "Saying hello",
    more: {
        property: "value",
        other: 1
    }
};

const res = web.request("http://example.domain.com/service/json")
                .method(web.POST)
                .json(content)
                .fetch();

console.log("Response status = " res.status);

Authenticating Requests

Most of the time REST APIs that return enterprise information or perform useful actions on your landscape will require authentication of the user to prevent misuse.

The authentication methods the web API supports depends on what monitored object the custom JavaScript is being run on. For Cloud Services, the web API automatically inherits the authentication defined in the service itself.

When the web API is not run on a Cloud Service monitored object, there is no default authentication method. This API provides two methods for supplying credentials to the request, either through the useBasicAuth method and supplying username and password directly which is a convenience function that fills the Authorization header, or by manually filling the Authorization header manually using the header or headers methods and the utility method encodeBase64 if required.

Cloud Service Web API

For convenience the URL defined in the service is available as a global variable baseUrl`. This can be useful for creating custom checks that can monitor multiple cloud services with the same code.

As discussed in Authenticating Requests, running a custom check on a Cloud Service autopopulates the defined authentication method in any normal web API request. This is done internally by replacing the Authorization header and any header passed manually to the API is ignored.

However, if you need to authenticate differently or override this behaviour, you can do so by using the newClient method to create a fresh web client object that has no preset authentication. Requests created on this new client will ignore the default authentication however the original web global variable is unaffected.

For example:

// Try with credentials -
const res1 = web.request(baseUrl + "/service/requires/auth")
                .fetch();

console.log("Response 1 Status = " res1.status); // 200 as credentials passed


// Try with no credentials -
const noAuthClient = web.newClient();

const res2 = noAuthClient.request(baseUrl + + "/service/requires/auth")
                         .fetch();

console.log("Response 2 Status = " res2.status); // 401 as no credentials

Legacy HTTP Client API

In versions of Avantra prior to 21.11, a legacy HTTP client API was available to make HTTP requests in custom checks and automations however this was more complex to use but it allowed for more complex requests to be made.

This API is still available in Avantra to allow existing scripts to continue to function however we recommend using the new API for new requirements as we believe it will be easier to use. If there is a feature that isn’t supported and you’d like to use in your scripts, feel free to reach out to our support channels.

Examples

Example 1. Call an XML based OData Service

This example calls the Northwind OData service (requesting XML) and prints the response to the console.

const url = "https://services.odata.org/V3/Northwind/Northwind.svc"
            + "/Categories?$select=CategoryID,CategoryName";
const res = web.request(url)
                .fetch();

/* This request returns something like:

<feed>
    <id> ... </id>
    ...
    <entry>
        ...
        <content>
            ...
            <m:properties>
                <d:CategoryID m:type="Edm.Int32">1</:CategoryID>
                <d:CategoryName>Beverages</:CategoryName>
            </m:properties>
        </content>
    </entry>
    ...
    <entry>
        ...
    </entry>
    ...
</feed>

And we need all the entry's, content's, properties tags.
*/

const categories = res.xml.__children
            // Find the entry tags in the children of the root
            .filter(tag => tag.__name === "entry")
            // For each entry, retrieve the content tag's m:properties tag
            // as this contains the information we need
            .map(entry => entry.content["m:properties"]);


categories.forEach(cat => {
    // Prints "1 - Beverages"
    // Note that the d:CategoryID has an attribute so we can't use it directly
    console.log(`${cat["d:CategoryID"].__value} - ${cat["d:CategoryName"]}`);
});
Example 2. Call a JSON based OData Service

This example calls the same Northwind OData service as the previous example but request the JSON format and prints the response to the console.

Note the &$format=json additional query parameter to the URL. This is something that this particular OData service supports, generally this also supported for SAP OData services too and it makes it much easier to use this service in our script.

const url = "https://services.odata.org/V3/Northwind/Northwind.svc"
            + "/Categories?$select=CategoryID,CategoryName&$format=json";
const res = web.request(url)
                .fetch();


res.json.value.forEach(cat => {
    // Prints "1 - Beverages"
    console.log(`${cat["CategoryID"]} - ${cat["CategoryName"]}`);
});

API Reference

Global Variable baseUrl

Global variable baseUrl is a string containing the URL defined in a Cloud Service’s configuration in Avantra.

This variable is only available if the JavaScript based custom check (RUN_JS) is defined for system type Cloud Service.

Check developers can use this variable to make custom checks more generic and work across multiple APIs.

const url = baseUrl + "/api/v2/example/service";
const res = web.request(url).fetch();

if (res.status != 200) check.status = CRITICAL;

Global Variable web

Global variable web is an instance of WebClient. Use this variable to call methods of this class directly in your code, e.g

const myValue = web.proxy(..);

See WebClient for further documentation on these methods.


Class WebClient

The web client object provides a method to make HTTP calls to web based APIs using the request method.

Additionally, this object provides utility methods for use in making API calls.

decodeBase64(..)

decodeBase64(input: string): string

Provides a utility to base 64 decode strings. Use encodeBase64 to reverse.

Parameters
  • input: string

    a base64 string to decode

Returns

string

the decoded string


encodeBase64(..)

encodeBase64(input: string): string

Provides a utility to base 64 encode strings. Use decodeBase64 to reverse.

Parameters
  • input: string

    a string to base64 encode

Returns

string

the base64 string


newClient( )

newClient(): WebClient

Create a new web client removing any pre-set authentication or proxy settings.

Not usually needed unless you need to make calls through different proxies or need to ignore the default authentication provided.

Returns

WebClient

a new web client object with no proxies/authentication pre-set


proxy(..)

proxy(hostname: string, port: number): WebClient

Return a WebClient configured with a proxy setting. Requests on this WebClient will utilise the proxy, requests on the original WebClient or web will not utilise the proxy.

Parameters
  • hostname: string

    the hostname of the proxy

  • port: number

    the port of the proxy

Returns

WebClient

a new web client object with a proxy set


request(..)

request(url: string): WebRequest

Create a request for a specified URL. This WebRequest object can be supplied headers or body data before it is eventually requested.

For example, performing a GET request and inspecting the results.

const res = web.request("https://service.domain.com/api/123")
             .fetch();

console.log(res.status); // prints the HTTP status code, e.g. '200'

// res.json is a JSON object of the response (if the API returns JSON)
// e.g. if the request returns '{ value: "hello" }'
const greeting = res.json.value; // greeting has the value 'hello'

// If the response is not JSON, we can inspect the full value
const body = res.body; // body has the value '{ value "hello" }'

res.headers; // A JSON object of headers in key/value pairs
// e.g
res.headers["Content-Type"]; // could be 'application/json; charset=utf-8'
Parameters
  • url: string

    the URL to request

Returns

WebRequest

the request to modify before fetching


DELETE

readonly DELETE: WebRequest.Methods

Constant for creating DELETE HTTP requests.

For DELETE requests, a body is optional and can be supplied either through body or json methods before fetch is called.

const req = web.request("https://service.domain.com/api/123")
             .method(web.DELETE)
             .json({
                 property: "value"
             });

req.fetch();
Type

WebRequest.Methods

Modifiers

Readonly


GET

readonly GET: WebRequest.Methods

Constant for creating GET HTTP requests. This is the default.

For GET requests, a body cannot be specified through body or json methods or an error is thrown when fetch is called.

const req = web.request("https://service.domain.com/api/123");
             // .method(web.GET) This is the default and not required

req.fetch();
Type

WebRequest.Methods

Modifiers

Readonly


PATCH

readonly PATCH: WebRequest.Methods

Constant for creating PATCH HTTP requests.

For PATCH requests, a body is mandatory either through body or json methods or an error is thrown when fetch is called.

const req = web.request("https://service.domain.com/api/123")
             .method(web.PATCH)
             .json({
                 property: "value"
             });

req.fetch();
Type

WebRequest.Methods

Modifiers

Readonly


POST

readonly POST: WebRequest.Methods

Constant for creating POST HTTP requests.

For POST requests, a body is mandatory either through body or json methods or an error is thrown when fetch is called.

const req = web.request("https://service.domain.com/api/123")
             .method(web.POST)
             .json({
                 property: "value"
             });

req.fetch();
Type

WebRequest.Methods

Modifiers

Readonly


PUT

readonly PUT: WebRequest.Methods

Constant for creating PUT HTTP requests.

For PUT requests, a body is mandatory either through body or json methods or an error is thrown when fetch is called.

const req = web.request("https://service.domain.com/api/123")
             .method(web.PUT)
             .json({
                 property: "value"
             });

req.fetch();
Type

WebRequest.Methods

Modifiers

Readonly


Class WebRequest

This object represents a request that can be made against an HTTP API.

Use the methods on the object to configure the request, specifying any necessary headers or body content before calling fetch to execute the call and retrieve a response.

body(..)

body(data: string): WebRequest

Set the body data for this request. If this method is used, the user must also specify the Content-Type header manually using the header or headers methods.

Parameters
  • data: string

    the body data to set

Returns

WebRequest

the web request object to allow for method chaining


fetch( )

fetch(): WebResponse

This method is called to make the web call to the URL. It uses the saved configuration created by previous calls on this object to generate the request. If the call was not made, an exception is thrown.

Returns

WebResponse

an object representing the response of the web call


formBody(..)

formBody(formData: object): WebRequest

Utility method to set the request data for this method to be form data.

No need to specify a Content-Type header when using this method, but you do need to change the HTTP method from the default web.GET to a method that supports sending request bodies.

const res = web.request("https://example.com/formapi")
             .formBody({
                 "grant_type", "urn:...",
                 "other_param", "value"
             })
             .method(web.POST)
             .fetch();

Always ensure that the values are strings when you call this method and repeated calls to this method erase the data in previous calls.

Parameters
  • formData: object

    an object of form data to set as the request body

Returns

WebRequest

the web request object to allow for method chaining


formParts(..)

formParts(partData: object): WebRequest

Utility method to set the request data for this method to be multi-part form data.

No need to specify a Content-Type header when using this method, but you do need to change the HTTP method from the default web.GET to a method that supports sending request bodies.

const res = web.request("https://example.com/formapi")
             .formParts({
                 "name", "partValue",
                 "otherName", "value"
             })
             .method(web.POST)
             .fetch();

Always ensure that the values are strings when you call this method and repeated calls to this method erase the data in previous calls.

Parameters
  • partData: object

    an object of data parts to set as the request body

Returns

WebRequest

the web request object to allow for method chaining


header(..)

header(name: string, value: string): WebRequest

Add a header to the request with specified name and value. Use headers to specify multiple at the same time.

Parameters
  • name: string

    the header name

  • value: string

    the header value

Returns

WebRequest

the web request object to allow for method chaining


headers(..)

headers(headers: object): WebRequest

Add multiple headers to the request. Headers are specified as a JavaScript object with the key being the header name. To specify a single header use the header method.

Parameters
  • headers: object

    an object of headers

Returns

WebRequest

the web request object to allow for method chaining


json(..)

json(data: object): WebRequest

A utility method to set JSON data to be sent in the body of the request. This method also sets the Content-Type header to be application/json; charset=utf-8.

Parameters
  • data: object

    the JSON data to set

Returns

WebRequest

the web request object to allow for method chaining


method(..)

method(method: Methods): WebRequest

Sets the HTTP method to use for the request. Valid options are web.GET, web.PUT, web.POST, web.PATCH, web.DELETE.

Parameters
  • method: Methods

    the HTTP method to use

Returns

WebRequest

the web request object to allow for method chaining


useBasicAuth(..)

useBasicAuth(username: string, password: string): WebRequest

This method sets the Authorization header for basic authentication for a web request.

const func = web.request("https://service.domain.com/api/123")
             .useBasicAuth("username", "password");

func.fetch();
Parameters
  • username: string

    the username for basic authentication

  • password: string

    the password for basic authentication

Returns

WebRequest

the web request object to allow for method chaining


Class WebResponse

This class represents a response from a web request. This object provides utilities to convert the content to readable formats that can be interpreted in JavaScript, for example, if the response was JSON.

body

readonly body: string

The raw string response from the server

Type

string

Modifiers

Readonly


headers

readonly headers: object

A JSON object of the headers in key/value pairs.

Type

object

Modifiers

Readonly


json

readonly json: any

If the response is a JSON document this property is automatically parsed to be used directly in your scripts.

It is not mandatory for the response to have a JSON content type. An attempt is always made to interpret the response as JSON and if it fails, an empty object is returned.

Note if the response is not JSON, this property will not be filled.

Type

any

Modifiers

Readonly


request

readonly request: WebRequest

A link back to the request object that created this response.

Type

WebRequest

Modifiers

Readonly


status

readonly status: number

The HTTP status code of the response.

Type

number

Modifiers

Readonly


xml

readonly xml: any

If the response is an XML document this property contains a JSON conversion of the XML to allow for easier consumption of XML documents within JavaScript.

It is not mandatory for the response to have an XML content type. An attempt is always made to interpret the response as XML and if it fails, an empty object is returned.

Because an XML document doesn’t necessarily come with a schema, to convert the XML to a JSON structure we follow a few rules:

  • The name of the tag is available through the __name property.

  • If the element has multiple children with the same name they are accessible by the property __children.

  • If the element has children with different names (and it doesn’t clash with our internal naming) the children can be accessible as properties with their own name.

  • If the element has a single value, it will be available through the __value property.

  • Any attributes of the XML tag are available through the __attributes property and this is a key value pair map.

  • If a node has no attributes and a single value child, it is available directly.

The below XML document with its corresponding JSON converted version:

<catalog>
    <book id="bk101">
        <author>A, Name</author>
        <title>A book</title>
    </book>
    <book id="bk102">
        Simple Book
    </book>
</catalog>
{
    "__name": "catalog",
    "__attributes": {},
    "__children": [
        {
            "__name": "book",
            "author": "A, Name",
            "title": "A book",
            "__attributes": {
                "id": "bk101"
            },
            "__children": [
                {
                    "__name": "author",
                    "__value": "A, Name"
                },
                {
                    "__name": "title",
                    "__value": "A book"
                }
            ]
        },
        {
            "__name": "book",
            "#text": "Simple Book",
            "__value": "Simple Book",
            "__attributes": {
                "id": "bk102"
            },
            "__children": [
                {
                    "__name": "#text",
                    "__value": "Simple Book"
                }
            ]
        }
    ]
}
Type

any

Modifiers

Readonly