From 4719cc03837490ed4bf1b9725d75a686e56e5a6a Mon Sep 17 00:00:00 2001 From: claw0ry Date: Wed, 11 Dec 2024 13:56:52 +0100 Subject: fresh start --- content/posts/servicenow-http-client.md | 180 ++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) create mode 100644 content/posts/servicenow-http-client.md (limited to 'content/posts/servicenow-http-client.md') 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", "")` 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://.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 `:`. + +```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://.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("", ""); + + 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://.service-now.com/stats.do"); + client.setHttpMethod("get"); + + client.setRequestHeader("Authentication", "Bearer "); + + var result = client.execute(); + gs.info(result.getBody()); +})(); +``` -- cgit v1.2.3