Building REST Web Services With RPG
This month’s article begins the process of answering one of the most common questions we’re asked lately: How do I create a Web service? Many people are now turning to RESTful Web services.
By Jon Paris
This month’s article begins the process of answering one of the most common questions we’re asked lately: How do I create a Web service?
Our original answer was to suggest using the system’s built-in Web Services Wizard to deploy RPG (or COBOL, etc.) programs as Web services on the built-in Application Server. While this is an excellent tool, it deploys your program as a SOAP¹ service and while SOAP is very popular, its complex structure and reliance on XML renders it overkill for many simple tasks.
Recognizing this, many people are now turning to RESTful Web services. Technically speaking, REST stands for REpresentational State Transfer—but that doesn’t really tell you anything. You may find as we did that the further you dig the more confused you get.
The confusion arises from the fact that the full definition of REST encompasses the capability to perform all of the tasks required of a conventional CRUD application (i.e., Create, Read, Update, Delete). This is all well and good, but most people we’ve talked to are just looking to build a Web service to permit information retrieval (i.e., the Read part). So for the purposes of this article, we’ll restrict ourselves to that area which in the formal definition of REST would be an HTTP GET.
- SOAP stands for Simple Object Access Protocol—not terribly helpful. And just as the “S” in XML SAX, it adds new meaning to the word “simple.” Rather than try to describe SOAP to you here, we’ll satisfy ourselves with simply referencing Wikipedia on the topic, which should provide enough links to keep the curious among you reading for hours.
- Scott Klement’s HTTPAPI is an excellent tool to let RPG programs consume both REST and SOAP Web services. His website provides links to the software and articles and presentations that describe its use.
While it may not be technically correct in a purist sense, a definition of this type of RESTful service that we particularly like was proposed by RJS’ Richard Schoen in a midrange.com discussion. Richard succinctly defined it this way:
“RESTful is anything where I can pass a formatted URL with parms of some sort and get data back in XML, JSON or whatever format I desire.”
Since REST services use the same HTTP protocol used by browsers, another way to think about it is that a RESTful service is a kind of “Web page” designed to be “read” by a program rather than by a human. That program could be anything from PHP to Java, to C# to RPG².
Now that we all know the scope of this discussion, let’s look at the two most common ways in which REST service requests are formed. Both use an HTTP GET request, but the method by which parameters are defined is quite different. There may be special names for the two types, but if so we haven’t encountered them.
- Those where the complete request is in the form of a URL. That is to say that both the function requested and its parameters are part of the URL. Parameters are simply identified by their relative position. An example might be: http://xyz.com/price/P2058/50 where “price” indicates the type of information required, “P2058” is the part number in question and “50” is the quantity to be priced. You can logically view this in the way it reads. For example, a directory named “price” contains a directory for every part we sell. Each of those directories in turn contains a file for each possible quantity and that file contains the relevant price. Of course, it’d be completely impractical to actually implement the system this way, but it may help to give you a mental model of why the request is formed the way it is.
- Those where the type of information requested (i.e., the action) is contained within the URL but the specifics are parameterized in the query string. Parameters are identified by name. For example: http://xyz.com/price?part=P2058&quantity=50
REST Service Programming Examples
As we noted, a REST service used for inquiry (GET) purposes can be considered simply as a Web page designed to be “viewed” by a computer program. Because of this, it can be built using the same tools and techniques we might use to build any other Web page. For the purpose of our examples, we’re going to use the CGIDEV2 toolkit, which should be familiar to many of you.
Our first sample is a simple REST service that accepts a request in the form: http://www.mysystem.com/restsrv1/part1/part2/.../partn
The service required (restsrv1) is identified at the beginning of the URL, followed by a list of part numbers. It then returns a simple XML document that contains the available quantity and description for each of the requested parts. Something like this:< XMLDATA >
< PARTDATA >
< PARTNUMBER >part1< /PARTNUMBER >
< DESCRIPTION >Description for part1< /DESCRIPTION >
< QUANTITY >1.33< /QUANTITY >
< /PARTDATA >
< PARTDATA >
< PARTNUMBER >partn< /PARTNUMBER >
< DESCRIPTION >Description for partn< /DESCRIPTION >
< QUANTITY >9.99< /QUANTITY >
< /PARTDATA >
< /XMLDATA >
This example is only intended to demonstrate the basic principles so it doesn’t even do a part lookup—it just fakes out the data. We just want to show how to obtain the URL data and parse it.
Let’s look at the pertinent parts of the code:(A) GetHTMLIFS( ‘/Partner400/RESTSRV1.xml’: ‘< !-- ‘: ‘ -- >‘);
WrtSection(‘ResponseHeader’: *off: ‘‘);
// Use getEnv to grab the back end of the URL
(B) pathInfo = getEnv(‘PATH_INFO’: qusec );
// parse the path string to extract the individual pieces
(C) elementCount = ParsePath( pathInfo: elementList);
// Loop through resulting list of elements
(D) For i = 1 to elementCount;
WrtSection(‘responseBody’: *off: ‘‘);
// All completed - add trailer to buffer and send response
(E) WrtSection(‘responseTrailer *fini’: *off: ‘‘);
(A) This first section deals with preparing to send the response. The file RESTSRV1.xml contains the skeleton for the XML response document we’ll be generating and the write adds the standard HTTP response header to the buffer. Think of it as writing the page headers for a report before you start the main processing. If you want to know more about generating XML in this way, see “Using CGIDEV2 for Generating XML.”
(B) Next we used the getEnv function to retrieve the environment variable PATH_INFO. This contains the portion of the URL after the part used by Apache to route the message to the program. If the original URL was http://www.mysystem.com/restsrv1/part1/part2 then PATH_INFO would contain “/part1/part2”.
(C) The next order of business is to parse the path to break out the various part numbers. This is the purpose of the function ParsePath, which returns a count of the number of parts it encounters. We’ll look at the code in a moment.
(D) Having identified the individual part numbers, we then loop through, processing each of them in turn and adding their data to the buffer.
(E) Once all parts have been processed, we simply write the response trailer and the special section *fini to cause the response to be sent.
The ParsePath function is a very simple process that breaks the string into its individual components using the forward slash as the separator. The only twist is that the initial scan starts at position 2 because we know that the first character in PATH_INFO will be a forward slash. We loop through the string storing each of the elements extracted in the array elements for later processing. The code is shown below.DoU position = 0; // Quit when no more to process
count += 1;
position = %Scan(‘/’: path: start);
If position > 0;
elements(count) = %Subst(path: start: ( position -
start = position + 1;
// Last element has no trailing / so grab last piece
elements(count) = %Subst(path: start);
Even though the data is intended for consumption by a program, because the request is in the form of a URL, you can test it in a browser—something you can’t do with a SOAP Web service. You can try out our sample service for yourself by using this URL: http://systemideveloper.com:1241/restsrv1/part1/Part2/part3. Add a few more part numbers to the URL and your browser should show you the XML that’s returned. Don’t expect it to look like much. Remember, it’s intended for consumption by a program, not a human.
Our next example is of the second type—namely that the function required is specified in the URL but the specific parameters are in the query string. Our example supports such requests as: http://systemideveloper.com:1241/restsrv2/quantityquery?partnum=0000011&quantity=5
Notice the function required (quantityquery) is in the URL but the parameters (partnum and quantity) are named parameters in the query string and not simply positional in the URL as in the first example. In fact, this program currently supports two types of requests—a quantity query (quantityquery) and a price query (pricequery). Anything else will be rejected with an error message. Don’t take our word for it—test it yourself by using the link above. We’ve listed a few valid part numbers at the end of the article if you want to explore a little further. Note that this service only supports requests for one part at a time.
Having told you what this second version does, let’s take a quick look at the underlying code. The first steps in the process (obtaining the skeleton and writing the response header) are the same as in the first example so we’ve omitted them here.(F) pathInfo = getEnv(‘PATH_INFO’: qusec );
service = %Subst(pathInfo: 2);
(G) inputCount = ZhbGetInput(savedQuery: QUSEC);
// Assume the part number is present and go get it
partNum = ZhbGetVar(‘partnum’);
When service = QUANTITY_QUERY;
When service = PRICE_QUERY;
(I) Other; // Unknown request - build error response
request = getEnv(‘QUERY_STRING’: qusec );
(F) As before, we’re using getEnv to obtain the path information because that will give us the name of the requested service.
(G) Next we call ZhbGetInput, which parses the query string, extracts the parameters and stores them for subsequent retrieval by ZhbGetVar. In this program, we’re not using the count returned by the API but it can be useful in determining how many parameters are present.
(H) All that remains is to direct the processing based on the specific service being requested.
(I) If the service name requested is unknown, then the complete query string is retrieved to assist in error reporting. You see the result of this for yourself by modifying the URL we gave you earlier.
The only other part of the code we want to comment on is in the CheckQuantity function because it deals with optional parameters. To demonstrate how these can be handled, we decided to make the quantity parameter optional and have the program use a quantity of 1 if nothing was supplied. Here’s the part of the code that deals with this:(J) If ( ZhbGetVarCnt(‘quantity’) > 0 ); // Quantity supplied?
quantity = %Int( ZhbGetVar(‘quantity’) );
On-error; // Any error set quantity to default
quantity = 1;
quantity = 1; // No quantity - set to default
(J) We use the ZhbGetVarCnt API to determine if a quantity has been supplied. If it was, then ZhbGetVar is used to obtain its value. If not (or the supplied value is non-numeric) then a default value of 1 is used. ZhbGetVarCnt is useful when handling optional parameters or when creating services that allow, for example, multiple parts to be queried in a single request as we did in our earlier example.
Scratching the Surface
We’ve only scratched the surface of what can be done with RESTful Web services. We could, for example, have the service return a JSON string in response to an Ajax request from a browser. If there’s enough interest in JSON and Ajax, we’ll delve into the topic in the future.
In the meantime, you can find the complete source code for these examples, together with additional details on the Apache configuration directives, etc., on our website.
For those who’d like to explore the technical aspects of REST in more detail, a search with Google will identify many explanations. We recommend though that you start with one we encountered recently on IBM’s developerWorks website. It’s definitely one of the best we’ve seen.