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.
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.
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
Hi John,
Thank you so much for the documentation. I have been really looking forward to this integration.
I would also have few question on Integration with JBOSS and ServiceNow, kindly please mail me so I can ask in detail.
Regards
Ashish
This is great stuff, John – as usual 😉
Hi John,
In the above example i can see that you have used Basic Authorization in the header. But my client wants a public URL.
Is it possible to accept POST data using Path based processor without authorization?
I had created a similar custom REST based Web Service few months back. But i had used Parameter speicific Processor since the client wanted a public endpoint to POST data. So i had created a public page and added the processor as the URL paramter.
Please let me know if authorization can be skipped using Path Based Processor.
Thanks,
Durga
@Durga – Great question…I put the solution in the next post today: How to make a ServiceNow Processor Public
Do ServiceNow support methods other than GET and POST.
I wrote a processor to output the method used and it only worked for GET and POST, not other method seem to work.
Another excellent piece John.
I need to build a custom REST service, but I do not want to rush to upgrade to pre-GA Eureka to get the latest REST API support. I should be able to use the information here to build my service.
John,
We are trying to implement custom REST web services with Vordel authentication gateway authenticating the requests and passing the unencrypted header to ServiceNow. However, seems like in order to access a processor basic authentication is a must and unencrypted header is not accepted.
Is there a workaround for this where we want to authenticate externally?
Thanks,
Amit
@Amit, actually I found a video by this same gentleman who writes this blog which describes how to do this. https://www.youtube.com/watch?v=n2a96TNlrMo
Hi John,
Thank you very much for this article, it was incredibly helpful for my implementation!
Hey! Would there be a way to “override” the default JSONv2Processor in Java? It would be nice if you could create a new Javascript processor, call the JSONv2Processor, then mess with the results.
In my case I’m trying to stop it from sending back every record in the response on an insertMultiple.
HI How can I create a service request on a SNOW instance from an external application using REST API via catalog API . I want to be able to create a REST API that will post the request into Request and then RITM and then a task. The request body will have the following; correlation id from the external app, the request item name such loaner laptop and some corresponding variables in a description field and a stage field that identifies if it is a new or existing request. There is also a REQUEST ID field. if stage is new then that mean the REQUEST ID field is blank. if it is existing then there is a REQUEST ID field, which means I have to create a PUT rest Api to update an existing REQUEST ID