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

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

A representation of a cookie for the web API

#### domain

    domain: string

The domain this cookie is valid for.

Type

  string

Since

  25.1

#### expiresAt

    expiresAt: number

The expiry of the cookie in milliseconds past epoch. If this is less than the current time, the cookie has expired.

Type

  number

Since

  25.1

#### hostOnly

    hostOnly: boolean

If true, the domain property is to be treated as a single host name or if false, it will be interpreted as a pattern.

Type

  boolean

Since

  25.1

#### httpOnly

    httpOnly: boolean

If this cookie is accessible only through requests and not available in browser JavaScript.

Type

  boolean

Since

  25.1

#### name

    name: string

The name of the cookie.

Type

  string

Since

  25.1

#### path

    path: string

The paths this cookie is valid for.

Type

  string

Since

  25.1

#### persistent

    persistent: boolean

If this cookie does not expire at the end of the current session.

Type

  boolean

Since

  25.1

#### secure

    secure: boolean

If this cookie should only be used in encrypted requests (https).

Type

  boolean

Since

  25.1

#### value

    value: string

The value of the cookie.

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 a new cookie to the current session.

Since

  25.1

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 should only be available for secure (HTTPS) requests

  - `httpOnly`: boolean

    whether the cookie should be available in browser JavaScript

  - `hostOnly`: boolean

    whether the domain should be treated as a single host (true) or a pattern (false)

#### 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 from the current session.

Since

  25.1

Parameters

  - `name`: string

    the name of the cookie

  - `domain`: string

    the domain of the cookie

  - `path`: string

    the path of the cookie

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

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

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

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

| 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