--- 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()); })(); ```