Skip to main content

# 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​

Performing 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 utilize 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.

### Using sessions​

By default, the web API doesn't use session handling. That is, if you make subsequent requests, these requests will not carry over any cookies, authentication or headers sent in previous requests. This is useful in some scenarios however doesn't allow interaction with stateful APIs.

To call stateful APIs and send or manipulate cookies on future requests, you need to use the createSession(..) method to create a new WebClient object and perform the stateful requests using this new client.

    const session = web.createSession();

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

    // this request has the same cookies as the above
    const res2 = session.request("https://service.domain.com/api/endpoint")
                  .fetch();

    // this request didn't reuse the cookies of the first 2
    const res3 = web.request("https://service.domain.com/api/other")
                  .fetch();

Cookies can also be manipulated using the cookies() method to list out the current cookies and addCookie(..) and removeCookie(..) methods to make changes to the cookies used in subsequent requests on that session.

    const session = web.createSession()

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

    // For debugging purposes we can inspect the cookies returned
    log.debug(JSON.stringify(session.cookies()))

    // Add a new cookie with some defaults:
    session.addCookie("SESSION_ID", "TEST", 253402300799999, "site.example.com", "/", true, true, true)

    // Remove an existing cookie:
    session.removeCookie("ANOTHER_ID", "service.domain.com", "/")

    // Log the cookies again to check:
    log.debug(JSON.stringify(session.cookies()))

    // Perform additional requests...
    // ...

### 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 auto-populates 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 behavior, 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 were 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: 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: 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 `web`​

Global variable `web` is an instance of WebClient.

Use this variable directly in your scripts, for example:

    const result = web.proxy(...);

See WebClient for the full API.

## Class Cookie​

A cookie

### `domain`​

    domain: string

domain

Type: string

Since: 25.1

### `expiresAt`​

    expiresAt: number

expiresAt

Type: number

Since: 25.1

### `hostOnly`​

    hostOnly: boolean

hostOnly

Type: boolean

Since: 25.1

### `httpOnly`​

    httpOnly: boolean

httpOnly

Type: boolean

Since: 25.1

### `name`​

    name: string

name

Type: string

Since: 25.1

### `path`​

    path: string

path

Type: string

Since: 25.1

### `persistent`​

    persistent: boolean

persistent

Type: boolean

Since: 25.1

### `secure`​

    secure: boolean

secure

Type: boolean

Since: 25.1

### `value`​

    value: string

value

Type: string

Since: 25.1

## 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.

### `addCookie`​

    addCookie(name: string, value: string, expiresAt: number, domain: string, path: string, secure: boolean, httpOnly: boolean, hostOnly: boolean): void

add new cookie to the session.

Parameters

- `name` (string) - the name of the cookie
- `value` (string) - the value of the cookie
- `expiresAt` (number) - the time in milliseconds when the cookie expires, can be null
- `domain` (string) - the domain of the cookie
- `path` (string) - the path of the cookie
- `secure` (boolean) - whether the cookie is secure
- `httpOnly` (boolean) - whether the cookie is httpOnly
- `hostOnly` (boolean) - whether the cookie is hostOnly

### `cookies`​

    cookies(): Cookie[]

Get the cookies that are currently stored in the session.

Since: 25.1

Returns: Cookie[] - a list of cookies

### `createSession`​

    createSession(): WebClient

Create a new web client that enables cookie session management between WebRequest calls so that stateful APIs can be used.

    const session = web.createSession();

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

    // this request has the same cookies as the above
    const res2 = session.request("https://service.domain.com/api/endpoint")
                  .fetch();

    // this request didn't reuse the cookies of the first 2
    const res3 = web.request("https://service.domain.com/api/other")
                  .fetch();

Since: 24.2

Returns: WebClient - a new web client object that saves cookies between requests

### `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

### `removeCookie`​

    removeCookie(name: string, domain: string, path: string): void

Remove a cookie.

Since: 25.1

Parameters

- `name` (string) - No description.
- `domain` (string) - No description.
- `path` (string) - No description.

### `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.

### `addHeader`​

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

Add a header to the request with the specified name and value but will not replace existing headers of the same name, only add to them. Use `headers(..)` to specify multiple at the same time.

Since: 24.2

Parameters

- `name` (string) - the header name
- `value` (string) - the header value

Returns: WebRequest - the web request object to allow for method chaining

### `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();

tip

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();

tip

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. Replacing any existing header with the same name.

tip

Since 24.2, multiple header values are supported and use of this method replaces existing headers with the same name instead of adding them. To add multiple headers of the same name, use `addHeader(..)`.

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.

tip

Since 24.2, multiple header values are supported and use of this method replaces existing headers instead of adding to them.

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: WebRequest.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` (WebRequest.Methods) - the HTTP method to use

Returns: WebRequest - the web request object to allow for method chaining

### `timeout`​

    timeout(seconds: number): WebRequest

Change the timeout for this request, default is 10 seconds. Set 0 for no timeout.

    const response = web.request("https://service.domain.com/api/slow/task")
                        .timeout(2280) // 2280 seconds or 38 minutes
                        .fetch();

    console.log(response.body);

Since: 21.11.6

Parameters

- `seconds` (number) - the number of seconds before a timeout or 0 for infinite

Returns: WebRequest - the web request object to allow for method chaining

### `trustAllCertificates`​

    trustAllCertificates(): WebRequest

Allow this request to connect to systems with invalid HTTPS certificates.

warning

Turning off certificate validation allows the script to become vulnerable to man-in-the-middle attacks. Only set this option if you have control on the network and validate the response.

    const response = web.request("https://incomplete-chain.badssl.com/")
                        .trustAllCertificates()
                        .fetch();

    console.log(response.body);

Since: 21.11.6

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

### `useCredentials`​

    useCredentials(credentials: any): WebRequest

This method sets a credential object as Authorization header for basic authentication for a web request. The credential object can be defined in an automation step and can be used for web request authentication

    const func = web.request("https://service.domain.com/api/123")
                 .useCredentials(action.input.someCredentials);

    func.fetch();

Since: 21.11.5

Parameters

- `credentials` (any) - a credential object

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 be `null`.

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, this property will be `null`.

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