custometools

Please Note: The functionality described in this post is no longer necessary for those using the “Geneva” (or later) release of ServiceNow. We highly recommend people use the “Scripted REST APIs” feature introduced in that release.

ServiceNow boasts a wide range of capabilities in their out-of-box SOAP and REST web services. There are times, however, where you need something that is totally custom, and not found out of the box. With SOAP, you can always leverage the “Scripted Web Services” feature of the product to create your own SOAP based web service within the platform. However, most people don’t know you can do something similar for REST based web services as well!

The place I like to go to in order to develop my own REST based web service, is to create my own Scripted Processor.

Types of Processors

There are two types of processors, Script based and Java based. Java based processors leverage Java code built in the platform. This is something that is core to the ServiceNow platform and not available for users to create on their own. Scripted processors will execute user-specified Javascript code.

Processors are accessed either by Path, or by URL parameter. If you create a Path processor, then it is accessed by a specific, static URL (eg. https://myinstance.service-now.com/my_scripted_processor.do). If you create a Parameter specific processor, you can leverage the processor within any standard ServiceNow page by adding a URL parameter to the URL (eg. https://myinstance.service-now.com/incident.do?MYPROCESSOR).

Global Variables and Functions

Every scripted processor has the following four global variables/objects that you can leverage:

  • g_request – This is the HTTP Request object. It contains information about the request itself.
  • g_response – This is the outgoing HTTP Response object that will be posted back to the client.
  • g_target – If you are using a Parameter-based processor, this is the table that the processor is acting on. This does not apply for Path-based processors
  • g_processor – This is the actual processor object.

For our example, of a custom REST based Web Service, we are going to leverage the g_request, g_response, and g_processor objects. The following are the methods that will be handy for us:

g_request.getMethod()
This returns a string value of the HTTP Method used in the call. REST-based systems will typically be one of four methods: GET, POST, PUT, DELETE.

g_request.getHeaderNames()
This will return a Java Enumerator on a list of HTTP Header Names that were sent with the request. This is helpful if you want to use any header information that is sent from the client.

g_request.getHeader({headerName})
Given a header name, this will return the value of that HTTP Request Header.

g_request.getParameterNames()
This will return a Java Enumerator on a list of the various URL Parameters used in the Request URI. You can leverage URL parameters that were submitted with the request by using this function.

g_request.getParameter({parameterName})
Given a parameter name, this will return the value of the URL Parameter used in the request.

g_request.getInputStream()
This function will return a Java InputStream that will stream the body content of the request to your script. You can use the GlideStringUtil.getStringFromStream({inputStream}) java API call to convert the stream into a string variable.

g_response.setHeader({headerName}, {headerValue})
This function on the g_response object will set a new Header name and value on the response. This is useful if you have header data that you need to provide back to the client that is calling your web service.

g_processor.writeOutput({outputString})
Use this method to set the body of the content that you will be returning to the client with the response. This will typically be XML or JSON data, but can be anything (text-based) you need it to be.

Creating a Sample Processor

We are going to take the information above to create a REST based web service that will expect a string that represents a person and the cars that they drive. Based on the Content-Type HTTP Header sent in the request, we will determine whether the string representing the person is a JSON string or an XML string.

We will then build a custom response for the client that called the web service, displaying all of the headers we received from them as well as any URL parameters. Then give them a message of what we are going to do with the data they provided. We will also let the client know that the message we are giving back to them is in plain text format by setting our response Content-Type HTTP Header appropriately.

The Processor

Name: SampleWebService1
Type: script
Active: true
Description: Demonstrate how to handle an inbound REST based request
Path: SampleWebService1
Script:

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
/*
 * We could create logic around what to do if this request is
 * a GET, PUT, POST, or DELETE request.  To get that HTTP Method,
 * we use the getMethod() function on the request.
 * In this example, we just log it to a string variable
 */

var methodMsg = "Method string: "+g_request.getMethod()+"\n";

/*
 * We could iterate through all of the headers to make
 * intelligent decisions about how to handle this request,
 * but in this example, we will just write each header
 * and its value to a logging string
 */

var headerList = g_request.getHeaderNames();
var headerMsg = ""; //we're going to log header info here
while(headerList.hasMoreElements()){
    var header = headerList.nextElement();
    var value = g_request.getHeader(header);
    headerMsg += ("Header: ["+header+"] has a value of: "+value+"\n");
}

/*
 * We could iterate through all of the URL parameters to make
 * intelligent decisions about how to handle this request,
 * but in this example, we will just write each URL parameter
 * and its value to a logging string
 */

var urlParamList = g_request.getParameterNames();
var paramMsg = ""; //we're going to log parameter info here
while(urlParamList.hasMoreElements()){
    var param = urlParamList.nextElement();
    var value = g_request.getParameter(param);
    paramMsg += ("Parameter: ["+param+"] has a value of: "+value+"\n");
}

/*
 * Grab the input stream from the request and store the stream contents
 * into a variable, "sb" in this case, so that we can read it easily
 */

var inputStream = g_request.getInputStream();
var is = g_request.getInputStream();
var sb = GlideStringUtil.getStringFromStream(is);

var contentType = g_request.getHeader("Content-Type");
if( contentType == "application/xml" ){
    /*
     * The body is supposed to be in XML format, convert it to an object
     * using ServiceNow XML libraries
     */

    var helper = new XMLHelper(""+sb);
    var person = helper.toObject();
} else {
    /*
     * The body is not XML, so we are going to assume it is in JSON format.
     * Use ServiceNow libraries to covert the JSON string to an object
     */

    var parser = new JSONParser();
    var person = parser.parse(sb);
}


/*
 * Now we would normally process the request given the data posted to
 * us.  However, for this example, we are going to simply build out
 * a text response that is tailored to the information given to us
 */

var returnMessage = "";
returnMessage += "We received a Request from you with the following information:\n";
returnMessage += methodMsg;
returnMessage += "\nWITH INCOMING HEADERS\n";
returnMessage += headerMsg;
returnMessage += "\nWITH URL PARAMETERS OF\n";
returnMessage += paramMsg;
returnMessage += "\n\n";
returnMessage += "We will process the following vehicles for ";
returnMessage += person.first + " " + person.last + ":\n";
if( person.cars.car ){
  //XML case
    var listOfCars = person.cars.car;
} else {
  //JSON case
    var listOfCars = person.cars;
}
for(key in listOfCars){
    returnMessage += listOfCars[key].year + "-";
    returnMessage += listOfCars[key].make + "-";
    returnMessage += listOfCars[key].model + "\n";
}

/*
 * We can set headers in our response to the client.  In this case, we will
 * send a "Content-Type" header to tell the client
 * that our response is just plain text.  Normally we would respond
 * in JSON or XML format.  But, we will keep it simple.
 */

g_response.setHeader("Content-Type", "text/plain");

/*
 * If we want to send information back to the client, we can
 * do so through the writeOutput function on the processor
 */

g_processor.writeOutput(returnMessage);

Testing the Processor using JSON content

Using my favorite REST Client, I am going to post some test parameters and headers to my web service. I am going to make sure to set my Content-Type header to “application/json” to tell the processor that my data is coming in as JSON formatted data.

Postman

My client will get the following response from the web service:

Incoming Headers

Content-Encoding →gzip
Content-Length →870
Content-Type →text/plain
Date →Tue, 03 Sep 2013 17:30:08 GMT
Server →ServiceNow
X-TRANSACTION-TIME →0:00:00.018
X-TRANSACTION-TIME-MS →18

Body of response

We received a Request from you with the following information:
Method string: POST

WITH INCOMING HEADERS
Header: [host] has a value of: MINSTANCE.service-now.com
Header: [connection] has a value of: keep-alive
Header: [content-length] has a value of: 248
Header: [authorization] has a value of: Basic YWRtaW46YWRtaW4=
Header: [cache-control] has a value of: no-cache
Header: [origin] has a value of: chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm
Header: [user-agent] has a value of: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36
Header: [content-type] has a value of: application/json
Header: [accept] has a value of: */*
Header: [accept-encoding] has a value of: gzip,deflate,sdch
Header: [accept-language] has a value of: en-US,en;q=0.8
Header: [cookie] has a value of: tomato_menu_status=overview.asp; tomato_status_overview_refresh=3; tomato_menu_forward=basic.asp; atlassian.xsrf.token=BLX9-YBA9-J0XM-8QS5|6d54b8deee7f3fdd6744ae62c0f7a964f888996d|lout; JSESSIONID=8E09B5D54088B641DB27D243FB2E4086; glide_user="U0N2MjpiZTdiM2Q5YTg4MmZjMTAwZjhlNjNhZDc5NzE5YzA5NDo2ODE2Zjc5Y2MwYTgwMTY0MDFjNWEzM2JlMDRiZTQ0MQ=="; glide_user_session="U0N2MjpiZTdiM2Q5YTg4MmZjMTAwZjhlNjNhZDc5NzE5YzA5NDo2ODE2Zjc5Y2MwYTgwMTY0MDFjNWEzM2JlMDRiZTQ0MQ=="; glide_user_route=glide.e37cb7a788a9996b305ac28d6d4a9d8a

WITH URL PARAMETERS OF
Parameter: [MySecondParam] has a value of: IsNotAwesome
Parameter: [MyFirstParam] has a value of: IsAwesome


We will process the following vehicles for John Andersen:
1999-ford-F-150
2008-chevrolet-Suburban

Testing the Processor using XML content

Using my favorite REST Client, I am going to post some sample parameters and headers to my web service. I am going to make sure to set my Content-Type header to “application/xml” to tell the processor that my data is coming in as XML formatted data.

Postman-2

My client will get the following response from the web service:

Incoming Headers

Content-Encoding →gzip
Content-Length →864
Content-Type →text/plain
Date →Tue, 03 Sep 2013 18:29:32 GMT
Server →ServiceNow
X-TRANSACTION-TIME →0:00:00.056
X-TRANSACTION-TIME-MS →56

Body of response

We received a Request from you with the following information:
Method string: POST

WITH INCOMING HEADERS
Header: [host] has a value of: MYINSTANCE.service-now.com
Header: [connection] has a value of: keep-alive
Header: [content-length] has a value of: 284
Header: [authorization] has a value of: Basic YWRtaW46YWRtaW4=
Header: [cache-control] has a value of: no-cache
Header: [origin] has a value of: chrome-extension://fdmmgilgnpjigdojojpjoooidkmcomcm
Header: [user-agent] has a value of: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36
Header: [content-type] has a value of: application/xml
Header: [accept] has a value of: */*
Header: [accept-encoding] has a value of: gzip,deflate,sdch
Header: [accept-language] has a value of: en-US,en;q=0.8
Header: [cookie] has a value of: tomato_menu_status=overview.asp; tomato_status_overview_refresh=3; tomato_menu_forward=basic.asp; atlassian.xsrf.token=BLX9-YBA9-J0XM-8QS5|6d54b8deee7f3fdd6744ae62c0f7a964f888996d|lout; JSESSIONID=8E09B5D54088B641DB27D243FB2E4086; glide_user="U0N2MjpiZTdiM2Q5YTg4MmZjMTAwZjhlNjNhZDc5NzE5YzA5NDo2ODE2Zjc5Y2MwYTgwMTY0MDFjNWEzM2JlMDRiZTQ0MQ=="; glide_user_session="U0N2MjpiZTdiM2Q5YTg4MmZjMTAwZjhlNjNhZDc5NzE5YzA5NDo2ODE2Zjc5Y2MwYTgwMTY0MDFjNWEzM2JlMDRiZTQ0MQ=="; glide_user_route=glide.e37cb7a788a9996b305ac28d6d4a9d8a

WITH URL PARAMETERS OF
Parameter: [MySecondParam] has a value of: IsNotAwesome
Parameter: [MyFirstParam] has a value of: IsAwesome


We will process the following vehicles for Amber Andersen:
2002-Honda-Rebel
2000-Toyota-Avalon