Creating Web Services With Integrated Web Services
By providing safe and reliable data access for Windows and other systems, web services give new life to your RPG code.
By Jon Paris
Back in 2012 we published the article "Need a REST?" where we described the basics of developing a simple REST** Web service in RPG. At the time we noted that although IBM's Integrated Web Services (IWS) could have been used for this task, it only generated SOAP Web services and that this was overkill for the type of simple service we were implementing.
Things have changed over the years, and as IBM's Tim Rowe has pointed out to us on a number of occasions, the IWS has been significantly updated over the intervening period. It now additionally supports not only REST Web services, but also allows for the request and response to be made in a number of different formats ranging from raw text, to XML to JSON.
The RPG Subprocedure
Before we get into the details of how to use the IWS wizard to set up the service, we need to make a few points about the RPG code that we will be using. Some of these points are just of a "you might not know this" nature, but one technique in particular is important to understand specifically when it comes to using existing routines as Web services.
The source shown here represents a simple subprocedure that when passed a Customer Number (CustNumber) retrieves the basic name and address information for that customer. If no such customer exists, then the name is set to *** Not found ***.
Starting at (A) we’ve defined the data structure custInfo that will be used to build the response. Note that we have simply listed the field names that we require from the CustMast file. No length or other definition is required; the compiler will pick them up from the file definition. The other benefit of coding it in this way is that when a successful CHAIN occurs, all of the fields in our response DS will be automagically populated. No copying required. If you’re unfamiliar with this technique and would like to know more we suggest you read our article "D-spec-Discoveries" for more information.
We're highlighting (B) because this addition is what makes it possible for us to use this routine in our regular RPG as well as in the Web service. The "magic sauce" is the RTNPARM keyword. To best explain how this helps, let's look at how we would call this routine in a regular RPG program. It would typically look something like:
CustInfoData = GetCustInfo( custNum );
The problem is that the Web services wizard only allows the use of a 4-byte integer as a return value. We have a multifield data structure way bigger than 4 bytes! We won't go into it in detail but this is a limitation imposed by the underlying Java tooling.The magic of the RTNPARM keyword is that it takes the return value and converts it into a parameter (the first one) instead. RPG introduced this feature as a performance boost for large return values. But it also has the delightful side effect of allowing such routines to be usable by the Web services wizard, which now sees this routine as having two parameters and no return value. And it does this without impacting the ability to use it in regular RPG programs just the way we would normally do. Now that is a nice unexpected benefit! For more details see "Big Changes for RPG in IBM i 7.1.”
You can not only apply this technique to subprocedures that are going to be repurposed as Web services, but also as a simple way to allow them to be used from Java and other facilities with the same basic limitation. We'd like to see this information added to the IWS documentation as it isn’t an obvious option.
h NoMain Option(*SrcSTmt : *NoDebugIO) fCustMast IF E K DISK (A) d CustInfo ds Inz d Cust# d Name d Addr d City d State d Zip (B) p GetCustInfo b Export d PI LikeDS(CustInfo) d RTNPARM d CustNumber 5s 0 d CustNotFound c '*** Not found ***' (C) Chain ( CustNumber ) CustMast; If Not %Found(CustMast); (D) Reset CustInfo; // Clear out all data Cust# = CustNumber; // Set customer number requested Name = CustNotFound; // and error code EndIf; Return CustInfo; p GetCustInfo e
(C) Is simply the CHAIN that retrieves the customer data. We have highlighted it here because it allows us to mention a quick tip that we came upon recently. Notice that the key field is in parentheses? By doing this we don't have to ensure that the data type, size, etc., of the key matches the file. The compiler will take care of any differences for us. For reasons we can't quite explain it also seems to make the statement easier to read by making the key stand out.
(D) Tests whether the CHAIN was successful. If it was, all of the required data is already loaded in the data structure and there is nothing left to do but return the result. If the customer wasn’t found, however, we need to clear out the results of the previous operation (RESET) and set the requested customer number and error message into the appropriate spots in the structure.
When compiling the code be sure to specify that you wish to generate Program Call Markup Language (PCML). We suggest you use the PGMINFO (Program Information) option to embed this data in the module object by specifying PGMINFO(*YES *MODULE). The PCML provides the IWS wizard with the information it needs to interface to the RPG.
Once this code is compiled as a module, we add it to a Service Program and it is that Service Program that will be used by the Web service. It is worth noting here that IWS can also use program objects, but in this case we wanted to demonstrate the re-use of an existing subprocedure.
Setting Up the Web Service
We will not be going into the details here because IBM has several Web pages describing the process. We have however produced a short video of the process which you can view here.
These are the main IBM pages that we have found useful:
- Integrated Web Services for IBM i. This is the basic home page for all things IWS.
- Integrated Web Services for IBM i. Yep - same page title! This series of pages takes you through the process of creating a Web Services Server and deploying a program as a Web service.
- Integrated Web services server for IBM i updates. This page describes the latest updates (July 2015) and identifies the required PTF levels. It also gives examples of the new features supported such as nested arrays in the results.
- Last but not least "Building a REST service with integrated web services server for IBM i" Parts 1 and Part 2. These pages take you step by step through the process of setting up a REST Web service.
Testing the Web Service
Unlike the SOAP Web service implementation, IBM doesn’t supply a built-in test facility for REST Web services. To be honest we're pleased with that decision; we always found the SOAP tester to be difficult to use and completely non-intuitive. We always use the free SOAPUI tool ( https://www.soapui.org/downloads/soapui.html ) to test Web services. Luckily for us this has been updated to allow for testing of REST Web services in addition to its support for SOAP services.
However, there is a much simpler way of testing REST Web services. You can just use your browser! Most REST services (including those created by IWS) are able to use the HTTP protocol used by Web browsers. So you can simply enter into the URL the request you wish to make (the IWS wizard will have shown you the basic format for the message) and the response will show up in the browser. In our test of our new service we chose to have it return its response in XML format because if we were consuming the service from an RPG program it would be easy to process the results via XML-INTO. It also, as you can see in Figure 1, makes it easy to see the results. Note that the XML element containing the results is named _RTNPARM - that should sound familiar to you. Within that element are the fields in the returned data structure. Had the subprocedure returned its value in a regular parameter the element would have the parameter's name.
In our example the Web service to use (custinfo) and the Customer number (00345) were both specified in the base URL. We could also have set it up to have the Customer number be passed as part of the query string in which case the URL would have looked like this:
Which to use? We'll let you make your own decision. Our take is that it makes sense to have the information that will always be required (Customer number) in the URL but that we would use parameters in the query string to identify individual fields if we were, for example, to want to extend the web service to allow for updating of the Customer information.
The Good, the Annoying and …
We still have a long way to go in exploring these new capabilities and will report back as we increase our understanding of how it all hangs together. But we do have a few thoughts to share. It is possible that some of these issues are already addressed, but we couldn't find anything.
- The performance of the service is excellent—much faster than we had expected. How it fares under high loads we're not in a position to say but the reports we've read seem to indicate that it performs well. It is certainly easier, from the programming perspective, to create a service than the method we outlined before. You don't have to concern yourself with actually handling and routing the requests, just write code to accept parameters and return a result.
- The facilities provided in the wizard to map the parameters, etc., are clear and straightforward.
This is going to seem a long list, which in some ways seems unfair because on the whole this set of tooling is great, but it could easily be so much better.
- The biggest single issue we have is documentation. From our understanding, one of the purposes of IWS is to make it possible for people who know nothing about Web services to create them. Problem is that the terminology used in the documentation is, for the most part, a sub-dialect of UNIX-ese and almost demands that you already understand something about Web services.
For example, in the step-by-step guide referenced earlier, at step 10 it says "Specify what transport information related to the client request is to be passed to the Web service implementation code." Huh? Because we are familiar with basic Web programming in RPG and PHP, the terminology was not completely alien to us, but many RPGers in the target audience for IWS may well find it a barrier.
Admittedly it’s perhaps unfair to lay this at the door of IWS. This is an ongoing problem we have seen with much of IBM's recent additions to the OS. If IBM really wants to have these facilities in widespread use, this is a problem that really needs to be addressed. The problem is not one of volume, but rather an inability of the writers to describe the product in terms the target audience can relate to.
- The wizard allows you to use a regular expression, to limit the content of a particular field to (say) numerics. But when your input breaks the rules there's no error message. Just no output. That makes the validation a bit pointless—it might be better to force validation in the RPG code—that way at least an error response could be returned.
- While on the subject of numerics, for some reason the tool appears to return numeric values stripped of leading zeros. You can see this is the example above. Because the tool knows the actual length of the field, we don't quite understand why it is done this way.
- The URLs generated by the wizard are case sensitive. As a result if you enter .../web/ services/CustInfo/... instead of .../web/services/custinfo/... you will get a rather ugly Not Found error. When we build regular Web pages on IBM I, the URLs aren’t case sensitive as indeed they are not on most Web sites. This can probably be tweaked somewhere in the server configuration, but we couldn't see any reference to it.
There are other idiosyncrasies we could mention but we really don't want to put you off trying what is a great tool. But we do think these are things you need to know or, like us, you will end up wasting a lot of time looking at blank screens and wondering if you have configured the service incorrectly.
There's only one thing that we really felt was worth the label "Bad" and that is the fact that there’s no way that we can see to edit the definition of the service using the wizard.
If you need to change a parameter, or to switch from just supporting XML to adding JSON, the only way to do it is to delete the existing service and recreate it. If there are a lot of parameters and other settings involved this is not just annoying but error prone.
Wrapping It All Up
Despite the annoyances we encountered, we are very impressed by what the latest incarnation of IWS can do and strongly encourage you to give it a try. Web services are a much better way to provide safe and reliable data access for your WIndows and other systems than ODBC/JDBC and give new life to your RPG code.
As we explore more of what IWS can do we'll be back with more articles. In the meantime, we'd be interested in hearing about your experiences in the comments section. Are you making use of IWS? Are you investigating other ways of supplying Web services? If so what is your preferred approach?
** For those of you who don't know. REST stands for Representational State Transfer. There, now you're much wiser aren't you! If you want a better definition we suggest that you read the article that we referenced earlier.