In an entry in the iSpeak Blog (www.iSeriesNetwork.com/iSNblogs/iSpeak/ -- see the "The Perception" entry), I mentioned that I've been using RPG to write Web applications, including using it to consume Web services without any need for Java. This simple declaration on my part has generated quite a bit of interest. Can it be so? Has Scott lost his mind, or can he really consume Web services from RPG?
In this article, I give you a brief introduction to Web services and walk you through a sample of using my open-source HTTPAPI utility to consume a Simple Object Access Protocol (SOAP) Web service.
What in the World Is a Web Service?
We've all written computer programs that don't interact with a user at all. You know the kind that I mean? You pass them parameters and they do some calculations and return a result that's also in parameters. They're utility programs designed to be run by other programs.
Now, imagine if you could call programs like that across the Web. If your branch office in Bombay wanted to see whether you were getting low on inventory, a program on the Bombay system could call a program on your system to return the stock level -- all in the blink of an eye while a user waits in an order-entry application!
It'd be useful for more than communicating with other offices of your own company, too. You could pass a tracking number to a program running on a United Parcel Service (UPS) computer to get information about that package's status. Or you could send a credit card number to your bank to make a charge. Or you could check the performance of a stock. Or any other service available on any other business anywhere in the world. The possibilities are endless!
That's what Web services are all about. The same HTTP protocol that you use to surf the Web can be used to make calls from program to program anywhere in the world.
What's HTTPAPI?
Several years ago, I started an open-source project that uses ILE concepts to let an RPG program act as an HTTP client. The result of this project is a service program that provides APIs for HTTP programming. I named it, appropriately enough, HTTPAPI.
An HTTP client is different from the HTTP server that comes with OS/400. The easiest way to remember the difference is to think in terms of surfing the Web. The browser that you run on your desktop acts as the HTTP client. It, at your instruction, connects to a server and requests documents. It downloads those documents and does something with them. On the other hand, the server doesn't have a user sitting in front of it. Instead, it's a "never-ending" process that sits and waits for requests.
The difference between HTTPAPI and CGIDEV2 is a good example of the difference between client and server. HTTPAPI takes the client role. By contrast, CGIDEV2 assists you with writing server applications and works together with the HTTP server that IBM provides. They are opposite sides of the same coin.
What Is SOAP?
SOAP is a standardized format for passing parameters to and from a Web service. Before SOAP was created, people were using quite a few different methods to pass parameters to Web programs. You could encode the parameters like you would encode input from an HTML form. You could upload a file with the input parameters. That file could be formatted as text, CSV, or XML -- or as virtually any format. And just as many ways existed to code the output!
With SOAP, however, the format of parameters is standardized. A SOAP document (or "SOAP message") is an XML document that has been standardized and blessed by the World Wide Web Consortium (W3C) for Web services.
There's also a related XML document called Web Services Description Language (WSDL). WSDL contains a list of the Web services that you offer and provides information about the data types, lengths, and formats of the parameters that need to be passed to each Web service. When you know this information, you can pick a service from the list and use the information in the WSDL to create a SOAP message.
After you format the SOAP message, you use HTTPAPI to send it to the server. The server interprets it while you wait and sends back another SOAP message in response. You can then interpret that message to get the parameters that the service returns.
Currency Exchange Example
To find a simple, but interesting, example, I started by pointing my browser at www.xmethods.net, a Web site devoted to Web services. On that site, I found an example in the "XMethods Demo Services" section. The example that I chose is called "Currency Exchange Rate," and I picked it because it's simple and free of charge.
If you click the "Currency Exchange Rate" link, it takes you to a page that tells you all about the currency exchange service. One of the very first things it gives you is a link to the WSDL XML document that describes this Web service along with all its technical details.
If you scroll down the page, you see the "Endpoints" section, which contains the URL for the Web service. This tells you the Web URL that you need to give to HTTPAPI to connect to the service. (You could also get this URL by examining the <soap:address> element of the WSDL document.)
Scroll farther down, and you find the "Detailed Description" section, which explains what the Web service does and also lists the different countries' currencies that you can get exchange-rate information for. You need to know these country names when it's time to run the example.
Finally, scroll down to the bottom of the page, and you see a section that shows a sample request envelope and a sample response envelope. These XML documents make up the request that you send to the server and the response that you get back, respectively.
To try out this Web service from RPG, you need HTTPAPI installed on your system. Instructions for downloading and installing it can be found on my Web site at the following link:
http://www.scottklement.com/httpapi/
To consume the Web service, you first need to get parameters containing the two countries to convert between, and the amount of money to be converted. I did that by coding a prototype and program interface for my sample program. The following code works like *ENTRY PLIST does in fixed-format RPG:
D EXCHRATE PR ExtPgm('EXCHRATE')
D Country1 32A const
D Country2 32A const
D Amount 15P 5 const
D EXCHRATE PI
D Country1 32A const
D Country2 32A const
D Amount 15P 5 const
Now that I know the countries to convert between, I can create a SOAP document to send to the Web service. The following is copied from the sample one on xmethods.net, except that I used the parameters to fill in the two countries. Note that the following is nothing but one big, long free-format EVAL statement:
D SOAP s 32767A varying
/free
SOAP =
'<?xml version="1.0" encoding="US-ASCII" standalone="no"?>'
+ '<SOAP-ENV:Envelope'
+ ' xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"'
+ ' xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"'
+ ' xmlns:xsd="http://www.w3.org/1999/XMLSchema">'
+ '<SOAP-ENV:Body>'
+ ' <ns1:getRate'
+ ' xmlns:ns1="urn:xmethods-CurrencyExchange"'
+ ' SOAP-ENV:encodingStyle='
+ '"http://schemas.xmlsoap.org/soap/encoding">'
+ ' <country1 xsi:type="xsd:string">'
+ %trim(Country1) + '</country1>'
+ ' <country2 xsi:type="xsd:string">'
+ %trim(Country2) + '</country2>'
+ ' </ns1:getRate>'
+ '</SOAP-ENV:Body>'
+ '</SOAP-ENV:Envelope>';
Now that I have a SOAP document in a variable in my RPG program, I can use HTTPAPI to send it as a request to the xmethods.net server. I do that with the following API call:
D rc s 10I 0
D rate s 8F
.
.
rc = http_url_post_xml( 'http://services.xmethods.net:80/soap'
: %addr(SOAP) + 2
: %len(SOAP)
: *NULL
: %paddr(Incoming)
: %addr(rate)
: HTTP_TIMEOUT
: HTTP_USERAGENT
: 'text/xml'
: *blanks );
This call uses the HTTP POST method (which is what almost all Web services require) to send the SOAP document to services.xmethods.net. HTTPAPI knows which server to send it to because I specified the URL in the first parameter.
The second and third parameters tell the API where the SOAP document is located in memory and how long the document is.
The fourth parameter tells HTTPAPI which subprocedure it should call in my program when an XML start element is received from the server. Because I'm uninterested in start elements for this example, I pass *NULL to tell HTTPAPI to ignore the start elements.
The fifth parameter tells HTTPAPI which subprocedure to call when it receives an XML end element as well as the value of that element. I tell it to call a subprocedure called Incoming. Each time it receives an end element, such as </Result>, HTTPAPI calls that subprocedure automatically. The Incoming subprocedure is something that I need to write and include in this EXCHRATE program.
The sixth parameter is a variable that HTTPAPI passes to my Incoming subprocedure when it calls it. I tell it that I want it to pass a variable called Rate because this is where I store the exchange rate that I receive from the Web service.
For the seventh and eighth parameters, I pass special values that tell HTTPAPI to use the defaults for the number of seconds before giving up (HTTP_TIMEOUT) and the user agent name reported to the server (HTTP_USERAGENT).
The ninth parameter is the data type of the document that I send to the server. The value of "text/xml" tells the server that it's an XML document.
The final parameter is the "SOAP Action" parameter. The WSDL document on the xmethods.net site shows this parameter as being blank, so I pass blanks in this parameter.
If the HTTP request is successful, the HTTP_url_post_xml() API returns 1. Otherwise, an error has occurred, and I can call the HTTP_error() API to get an error message that describes the failure.
For the sake of example, I use the DSPLY opcode to display either an error message or the currency rate. In a real (i.e., production) application, you wouldn't use DSPLY. Instead, you would display the result to the user with a display file.
if (rc <> 1);
msg = http_error();
else;
Result = %dech(Amount * rate: 12: 2);
msg = 'Result = ' + %char(Result);
endif;
dsply msg ' ' wait;
*inlr = *on;
/end-free
The one task remaining is to write that Incoming subprocedure. As I mentioned earlier, it is called when the end element is received for each XML element in the document. All I need to get from it, however, is the value of the <Result> element, so I use an IF statement to ignore everything else:
P Incoming B
D Incoming PI
D rate 8F
D depth 10I 0 value
D name 1024A varying const
D path 24576A varying const
D value 32767A varying const
D attrs * dim(32767)
D const options(*varsize)
/free
if (name = 'Result');
rate = %float(value);
endif;
/end-free
P E
That's it! The program is ready to go. Here's a screen shot of what I see when I call the EXCHRATE program:
Command Entry
Request level: 1
Previous commands and messages:
> call exchrate parm('us' 'japan' 10.00)
DSPLY Result = 1183.80
Bottom
Type command, press Enter.
===>
F3=Exit F4=Prompt F9=Retrieve F10=Include detailed messages
F11=Display full F12=Cancel F13=Information Assistant F24=More keys |
You can download the sample code for the currency converter Web service consumer from the following link:
http://www.pentontech.com/IBMContent/Documents/article/52099_57_ExchRate.zip