Web Programming in RPG, Part 1

Article ID: 51135

You hear a lot from IBM about Java as a Web development language, but what you don't hear is that RPG can also be used in this capacity. For smaller or simpler projects, RPG can often be the right tool for the job -- especially when you have a lot of RPG experience on your staff.

RPG developers typically use toolkits like CGIDEV2, CGITOOLS, or eRPGSDK to write Web pages. However, I think it's helpful to first understand how RPG programs interact with the Web server and the Web browser. Once you understand how things work, it's easier to understand how the toolkits work and how they make your job easier.

Back when the Web was new, HTTP servers served static documents. A browser would send a request to retrieve a particular document in a particular directory. This request is called a GET request. The HTTP server would open the file that the browser requested, read the contents of that file, and send it over the network to the browser.

Before long, it became clear that it would be useful to be able to insert program logic into this process so that the documents could be generated dynamically. For example, if you wanted to display a list of products that you sell, it would be nice to be able to have a program that read that information from your database files, rather than have an HTML file that you have to update each time a product changes.

To make that possible, the Common Gateway Interface (CGI) was developed. The idea behind CGI is that you designate certain document paths as "script directories." When the browser requests something in one of those directories, instead of opening a file and reading its contents, the HTTP server runs a program and reads that program's output. That output, instead of the contents of a file, is what is copied over the network to the Web browser.

On the iSeries, we have had two different HTTP servers, and both of them have been included on the OS/400 CDs and are available for you to use, free of charge. The original one was called the IBM HTTP Server for AS/400, which was later renamed the IBM HTTP Server for iSeries. This server is no longer available as of V5R3. It was replaced by the IBM HTTP Server (Powered by Apache) for iSeries.

To set up the original HTTP server to take advantage of CGI, you need to insert an EXEC directive in the server's configuration file. For example, consider the following line from the configuration:

Exec /cgi-bin/* /QSYS.LIB/MYLIB.LIB/*

With that line of configuration, when the browser requests a document with /cgi-bin in its path, it will actually try to run a program in a library called MYLIB. For example, http://www.scottklement.com/cgi-bin/test.pgm would run a program called TEST in library MYLIB.

A word about Apache: When IBM switched us over to Apache, a lot of people were skeptical. Is this really where we want to be? The answer is absolutely. Apache is a much more versatile server than the original. It's a cross-platform program available for every major operating system, and therefore has been tested for versatility, security, and robustness by millions of users. In fact, according to NetCraft Ltd., Apache is the number one Web server on the planet, hosting over 69% of the Web servers out there. No other server comes close. Microsoft's IIS comes in a distant second place with around 20% of the market.

To configure Apache to direct CGI requests to MYLIB, you'll need a directive such as the following one:

ScriptAlias /cgi-bin   /QSYS.LIB/MYLIB.LIB

Although you can set this up by entering directives like the ones above directly into the configuration file, it's not necessary to do it manually. The HTTP server comes with a GUI configuration tool that you can use to configure it. To do so, start the admin HTTP server with the following command:

STRTCPSVR SERVER(*HTTP) HTTPSVR(*ADMIN)

Then, point your Web browser at port 2001 of your iSeries to access the GUI configuration tools.

More information on configuring your HTTP server can be found in the Information Center. In V5R3, you'll find it under Networking -> HTTP Server.

Once you've told it how to get to your programs, you'll need to know how to write them. As I mentioned earlier, most people use a toolkit to help them generate the data that gets sent to the browser. However, I'll start by showing you how to do it manually.

The first thing that you need to consider is that the Web server needs to know the document type. With a static document, it knows the type based on the file's extension. For example, if the filename ends in .HTML, it knows that the document is an HTML document; likewise, if the file ends in .JPG, it knows that it's a JPEG picture. However, when you write a program to generate the output, the program will always end in .PGM, no matter what type of data it outputs. Therefore, your program needs to inform the server what type of data it's sending.

To do that, you're given a space to send HTTP keywords from your program to the HTTP server. Although there are many keywords that you can potentially send, the vast majority of CGI programs will only send the one that identifies the type of content that you're providing.

These keywords are sent as lines of text at the start of the data that you want to send. The first blank line (indicated by carriage return and linefeed characters on a line by themselves) denotes the end of your keywords and indicates that the rest of the document should be sent as-is to the Web browser.

In order to send this information to the Web server (either Apache or Original), you have to use an API. This is not a system API, such as the ones that are usually discussed in this newsletter; instead, it's an API that comes with the HTTP server. To make these APIs easy to call, I like to set up a binding directory that tells the system how to find the HTTP server APIs. To do that, execute the following commands:

CRTBNDDIR BNDDIR(mylib/CGIPGM)
ADDBNDDIRE BNDDIR(mylib/CGIPGM) OBJ((QHTTPSVR/QZHBCGI))

Here's an RPG program that uses this API to send data to the Web server:

      *  To compile:
      *       CRTBNDRPG PGM(HELLOWEB) SRCFILE(xxx/QRPGLESRC) DBGVIEW(*LIST)
      *
      *
     H DFTACTGRP(*NO) BNDDIR('CGIPGM')

     D QtmhWrStout     PR                  extproc('QtmhWrStout')
     D   DtaVar                   32767A   options(*varsize) const
     D   DtaVarLen                   10I 0 const
     D   ErrorCode                 8000A   options(*varsize)

     D ErrCode         ds                  qualified
     D   BytesProv                   10I 0 inz(0)
     D   BytesAvail                  10I 0

     D CRLF            c                   x'0d25'
     D data            s           1000A   varying

      /free

         data = 'Content-Type: text/html' + CRLF + CRLF;
         QtmhWrStout(data: %len(data): ErrCode);

         data = '<html>'
              + '  <head>'
              + '    <title>Hello Web World!</title>'
              + '  </head>'
              + '  <body bgcolor="black" text="white">'
              + '     <h1>Hello Web World!</h1>'
              + '  </body>'
              + '</html>' + CRLF;

         QtmhWrStout(data: %len(data): ErrCode);

         return;
      /end-free

How does it work? At the start of the program is the definition for the QtmhWrStout() API. As I mentioned, this is the API that's used to send data from the RPG program to the Web server. It accepts 3 parameters: The first one is the data to be written. The second one is the length of the data that you supplied in the first parameter. The final parameter is an error code data structure that allows errors to be returned to your program. Since I don't expect any errors to occur, I set the length of that error code to zero. If an error does occur, the system will send my program an escape message instead of using the data structure.

The first line of the free-format calculations is the HTTP keyword that I mentioned earlier. The content-type keyword that I provided is used to tell the Web server that the data I'm sending is human-readable text in HTML format. The CRLF constant is then sent to denote the end of a line of data. There should always be only one keyword per line. The second CRLF specifies a blank line that will tell the server that I'm done sending HTTP keywords.

I put this data into a VARYING variable in RPG so that I don't have to waste CPU cycles trimming off the extra blanks at the end or keep track of how long the string is. The program will automatically do that for me because I used VARYING.

I then call the QtmhWrStout() API to send that piece of information to the HTTP server. As you can see, the %len() BIF makes it easy to figure out the length.

I then create an HTML document in a variable. This document is just a simple one that puts the words "Hello Web World" on the screen in white letters on a black background. Since it's small, I put the whole thing into one variable and then sent that to the server with the same API.

It should be noted that it does not matter which data is sent in which call of the QtmhWrStout() API. All of the data that I send will be interpreted as one long stream, so you can split the data across as many calls to the API as you like. For example, if you're cycling through a list of records in a database, it would be easier to write one record's worth of data at a time. To illustrate this, I'll show you a program that displays a price list. The price information comes from a file defined as follows:

     A          R PRICELISTR
     A            ITEMNO         5P 0
     A            DESC          25A
     A            PRICE          5P 2

You read this file in the RPG program, just as you would any other file. However, instead of writing the output to a subfile or a report, you write it to the Web server using the QtmhWrStout API. The following program demonstrates this:

      *  To compile:
      *    CRTBNDRPG PGM(SHOWPRICES) SRCFILE(xxx/QRPGLESRC) DBGVIEW(*LIST)
      *
     H DFTACTGRP(*NO) BNDDIR('CGIPGM')

     FPRICELIST IF   E             DISK    BLOCK(*YES)

     D QtmhWrStout     PR                  extproc('QtmhWrStout')
     D   DtaVar                   32767A   options(*varsize) const
     D   DtaVarLen                   10I 0 const
     D   ErrorCode                 8000A   options(*varsize)

     D ErrCode         ds                  qualified
     D   BytesProv                   10I 0 inz(0)
     D   BytesAvail                  10I 0

     D CRLF            c                   x'0d25'
     D data            s           1000A   varying

      /free

         data = 'Content-Type: text/html'          + CRLF
                                                   + CRLF
              + '<html>'                           + CRLF
              + '  <head>'                         + CRLF
              + '    <title>My Price List</title>' + CRLF
              + '  </head>'                        + CRLF
              + '  <body>'                         + CRLF
              + '    <h1>My Price List</h1>'       + CRLF
              + '    <table border="1">'           + CRLF;

         QtmhWrStout(data: %len(data): ErrCode);

         setll *start PriceList;
         read  PriceList;

         dow not %eof(PriceList);
            data = '<tr>'                            +CRLF
                 + ' <td>' + %char(ItemNo) + '</td>' +CRLF
                 + ' <td>' + Desc          + '</td>' +CRLF
                 + ' <td>' + %char(Price)  + '</td>' +CRLF
                 + '</tr>'                           +CRLF;
            QtmhWrStout(data: %len(data): ErrCode);
            read PriceList;
         enddo;

         data = '    </table>' + CRLF
              + '  </body>'    + CRLF
              + '</html>'      + CRLF;
         QtmhWrStout(data: %len(data): ErrCode);

         return;

      /end-free

Once compiled, this program will be run by the user who points his Web browser at http://iseries.example.com/cgi-bin/showprices.pgm. The browser will send a GET request to the Web server that tells it that it wants to retrieve a document called /cgi-bin/showprices.pgm.

Since the Web server is configured to see /cgi-bin as as a CGI script directory, it will run the program entitled SHOWPRICES in the library called MYLIB. When it does that, the RPG program code above will read the file and write out the HTML to the Web server.

The Web server will send it on to the browser, where the user can see it.

Hopefully that helps you understand the basics of CGI programming as it pertains to RPG on the iSeries. In a future newsletter, I'll show you how to take input from the browser so that you can use that input to influence the page that's written out. I'll also demonstrate how to use the CGIDEV2 toolkit to simplify the process of creating the HTML output.

ProVIP Sponsors

ProVIP Sponsors