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
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"]}`);
});
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.
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
-
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
: stringa 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
: stringa 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
-
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
: stringthe hostname of the proxy
-
port
: numberthe port of the proxy
-
- Returns
-
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
: stringthe URL to request
-
- Returns
-
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
: stringthe header name
-
value
: stringthe header value
-
- Returns
-
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
: stringthe body data to set
-
- Returns
-
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
-
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
: objectan object of form data to set as the request body
-
- Returns
-
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
: objectan object of data parts to set as the request body
-
- Returns
-
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 |
- Parameters
-
-
name
: stringthe header name
-
value
: stringthe header value
-
- Returns
-
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
: objectan object of headers
-
- Returns
-
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
: objectthe JSON data to set
-
- Returns
-
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.Methodsthe HTTP method to use
-
- Returns
-
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
: numberthe number of seconds before a timeout or 0 for infinite
-
- Returns
-
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
-
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
: stringthe username for basic authentication
-
password
: stringthe password for basic authentication
-
- Returns
-
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
: anya credential object
-
- Returns
-
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.
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
- 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