summaryrefslogtreecommitdiff
path: root/content/posts/servicenow-http-client.md
diff options
context:
space:
mode:
Diffstat (limited to 'content/posts/servicenow-http-client.md')
-rw-r--r--content/posts/servicenow-http-client.md180
1 files changed, 180 insertions, 0 deletions
diff --git a/content/posts/servicenow-http-client.md b/content/posts/servicenow-http-client.md
new file mode 100644
index 0000000..acfb3ae
--- /dev/null
+++ b/content/posts/servicenow-http-client.md
@@ -0,0 +1,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());
+})();
+```