summaryrefslogtreecommitdiff
path: root/content/posts/servicenow-http-client.md
blob: acfb3ae84fd7ccc8466e51b4fcfbce53f37f8b80 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
---
title: 'ServiceNow HTTP client'
description: 'A look at the built-in sn_ws.RestMessageV2 HTTP client in ServiceNow'
date: '2024-11-22T00:00:00+02:00'
tags: ['servicenow', 'http', 'javascript']
draft: true
---

For the past few months I've been building a REST API integration between ServiceNow and another ticketing system. So I thought I write about some of my epxeriences.

## The HTTP client

The first thing to know is that the HTTP client in ServiceNow is not called somewhere near that intuitive. It's called `sn_ws.RESTMessageV2`. At first look it seems to be a class for initiating Outbound REST Messages (System Web Services->Outbound->REST Message), but we can also use it "raw" without setting up a REST Message in the first place. 

A simple GET request looks like this. It will fetch you external IP and print the HTTP status code along witht the response body.

```javascript
(function() {
    var client = new sn_ws.RESTMessageV2();
    client.setEndpoint("http://ip.claw0ry.net");
    client.setHttpMethod("get");

    var result = client.execute();
    gs.info(result.getStatusCode());
    gs.info(result.getBody());
})();
```

After running this as a background script you should get the following output (though with a different ip ofcourse):

```plaintext
*** Script: 200
*** Script: 148.139.0.8
```

## Sending POST/PATCH requests

To send a POST/PATCH request we change the `setHttpMethod` argument to one of the other methods we want to use. These HTTP methods are often expected to send a request body along with them. We can specify the content type of the request body with `setRequestHeader("Content-Type", "<our type>")` and then specify the raw text with `setRequestBody`. This means that if you have a Javascript object, you need to call `JSON.stringify` in it before passing into the `setRequestBody` function.

```javascript
(function() {
    var reqBody = {
        title: "Hello, World",
        message: "Yo, my dude!"
    };

    var client = new sn_ws.RESTMessageV2();
    client.setEndpoint("https://www.postb.in/1732268930451-4059148549567");
    client.setHttpMethod("post");

    // we tell the webserver what type our content is exptected to be in
    client.setRequestHeader("Content-Type", "application/json");

    // setRequestBody cant magically convert types based on content-type,
    // so we must stringify our js object first
    client.setRequestBody(JSON.stringify(reqBody));

    var result = client.execute();
    gs.info(result.getStatusCode());
    gs.info(result.getBody());
})();
```

## Parsing response body

As we had to stringify our JSON request body in the last example, we need to `JSON.parse` the response (if we exptect a JSON result back). If we are qurious about what the response content type is, we can look into the response headers with `getHeader("Content-Type")`.

```javascript
(function() {
    var client = new sn_ws.RESTMessageV2();
    client.setEndpoint("https://jsonplaceholder.typicode.com/todos/1");
    client.setHttpMethod("get");

    var result = client.execute();

    // check if there are any internal ServiceNow errors
    if (result.haveError()) {
        gs.error(result.getErrorCode());
        gs.error(result.getErrorMessage());
        return;
    }

    // if the response body is not JSON format the JSON.parse will fail
    // therefor its a good practice to check on it. Since the header may
    // also contain character encoding, we split at ';'
    var content_type = result.getHeader("Content-Type");
    if (content_type.split(';')[0] !== "application/json") {
        gs.error("Unexpected response body from API");
        return;
    }

    // we now know the response body should be valid JSON so we can parse it
    var todos = JSON.parse(result.getBody());
    gs.info(todos.title);
})();
```

The result should be:

```plaintext
*** Script: delectus aut autem
```

## Error Handling

```javascript
(function() {
    var client = new sn_ws.RESTMessageV2();
    client.setEndpoint("https://<instance>.service-now.com/stats.do");
    client.setHttpMethod("get");

    var result = client.execute();

    // check if there are any internal ServiceNow errors
    if (result.haveError()) {
        gs.info(result.getErrorCode());
        gs.info(result.getErrorMessage());
        return;
    }

    // http status codes in the range 200-299 is considered a success
    // any higher is considered an error
    if (result.getStatusCode() > 299) {
        var errMessage = [];
        errMessage.push("ERROR:");
        errMessage.push(result.getStatusCode() + " - ");
        errMessage.push(result.getStatusText() + ":");
        errMessage.push(result.getBody());
        gs.info(errMessage.join(" "));
    }
})();
```

## Authentication

Often when dealing with external REST API's we need to authenticate. There are several authentication mechanism but most REST API's implements either Basic Authentication or tokens.

### Basic Authentication

I would say most API's today implements some kind of token, but there are some that still allows you to use Basic Authentication (like ServiceNow for example).

Using Basic Authentication in raw form means that you have to set a HTTP Authentication header with a base64 encoded string in the format `<username>:<password>`.

```plain
// base64encoded("username:password")
Authentication: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
```
Thankfully the sn_ws.RESTMessageV2 class has a built-in function for does this for you.

```javascript
(function() {
    var client = new sn_ws.RESTMessageV2();
    client.setEndpoint("https://<instance>.service-now.com/stats.do");
    client.setHttpMethod("get");

    // this will automatically convert our username and password into a valid
    // HTTP Basic Authentication format and attach it to our request
    client.setBasicAuth("<username>", "<password>");

    var result = client.execute();
    gs.info(result.getBody());
})();
```

### Tokens

There are several ways to obtain tokens depending on the system, but common for them all is that when you have obtained a token you must pass the it in a HTTP Authentication header when making further calls.

```javascript
(function() {
    var client = new sn_ws.RESTMessageV2();
    client.setEndpoint("https://<instance>.service-now.com/stats.do");
    client.setHttpMethod("get");

    client.setRequestHeader("Authentication", "Bearer <token>");

    var result = client.execute();
    gs.info(result.getBody());
})();
```