Web Programming in RPG, Part 2

Article ID: 51145

The last article in this series discussed the basics of how an RPG program can be used to generate dynamic Web pages. This article will build on that by providing information about how to receive input from a Web browser.

As mentioned in part 1 of this series, the first revision of the HTTP protocol only included the ability to retrieve (or "GET") a document. If you're limited to that framework, how can you provide input to a program that runs on the server?

In that day and age, the only information that the browser would send to the server was the name of the document that it was requesting. Therefore, the only way to provide input to a program running on the server is to somehow encode the input into the document name itself.

Later, they extended the HTTP protocol to include a new method of sending data called a POST request. In a POST request, the browser actually uploads a document to the server, which passes it on to the program that's handling the request. More about that, later.

This ability to encode variables into a GET request is still widely used today. Because the parameters are part of a URL, they can be used in Internet shortcuts, bookmarks, links from a search engine, and so on.

The way that the HTTP server sends information about each request to a program is through the use of environment variables. In RPG, the easiest way to retrieve an environment variable is with the getenv() API. Your RPG program can distinguish between the GET and POST methods by examining the REQUEST_METHOD environment variable. The following program demonstrates how you'd do that:

     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 getenv          PR              *   extproc('getenv')
     D   envvar                        *   value options(*string)

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

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

      /free

          Method = %str(getenv('REQUEST_METHOD'));

          data = 'Content-type: text/html' + CRLF + CRLF
               + '<html>'
               + '  <head><title>Request Method</title></head>'
               + '  <body>';
          QtmhWrStout(data: %len(data): ErrCode);

          select;
          when Method = 'GET';
             data = '<h1>You used the GET method.</h1>';
             QtmhWrStout(data: %len(data): ErrCode);

          when Method = 'POST';
             data = '<h1>You used the POST method.</h1>';
             QtmhWrStout(data: %len(data): ErrCode);

          other;
             data = '<h1>Unrecognized request method.</h1>';
             QtmhWrStout(data: %len(data): ErrCode);
          endsl;

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

          *inlr = *on;

      /end-free

With the GET method, data is encoded into the URL by inserting a question mark character to separate the data that goes to the program from the program name itself. Consider the following URL:

http://www.example.com/cgi-bin/order.pgm?showorder

The HTTP server recognizes the ? character and knows that it's not part of the program name. Everything after this character will be put in the QUERY_STRING environment variable prior to calling the program called ORDER. The following code snippet demonstrates one way that this might be used:

          action = %str(getenv('QUERY_STRING'));

          select;
          when action = 'showorder';
             exsr ShowOrder;
          when action = 'deleteorder';
             exsr DeleteOrder;
          when action = 'sendorder';
             exsr SendOrder;
          endsl;

In a more typical scenario, several fields will be submitted in a GET request. The fields that are sent to the program are separated by & symbols. Each field has a name and a value that are separated from each other by an = symbol. Consider the following example:

http://www.example.com/cgi-bin/order.pgm?action=showorder&ordno=12345&custno=4321

In that example, there are three fields. These fields are named action, ordno, and custno. They have values of showorder, 12345, and 4321, respectively.

Naturally, you wouldn't want the user to have to type a URL like that each time he wanted to display an order. Instead, you'd use a Web form. The following one, when executed by a browser, would generate the same URL as the one above:

<html>
 <body>
   <form method="GET" action="/cgi-bin/posttest.pgm">
     <input type="hidden" name="action" value="showorder">
     Order No: <input type="text" name="ordno" size="5" maxlength="5">
     <p>
     Cust No: <input type="text" name="custno" size="4" maxlength="4">
     <p>
     <input type="submit">
   </form>
 </body>
</html>

To handle them in your program, you'd once again retrieve the QUERY_STRING environment variable. It would contain the entire string "action=showorder&ordno=12345&custno=4321", and you'd have to write a routine that extracted the important parts from the data.

At first glance, this doesn't seem that difficult. But wait, there's more!

Spaces are not allowed in URLs. To make it possible to put spaces in data that's sent this way, they're translated into plus symbols by the browser. In other words, if I keyed the string "Anna Brown is cool" into a form on a Web page, the browser would send it as follows:

http://www.example.com/cgi-bin/ap100r4.pgm?msg=Anna+Brown+is+cool

If I wanted my program to handle that, I'd have to translate them to spaces. But what if I wanted to send a plus symbol? I can't send that because the application will think it's supposed to be a space. Likewise, I can't send an ampersand because it'll think it's the end of the field.

In fact, there's a whole bunch of characters that have special meanings in a URL. To make it possible to send any of these characters as part of the field data, they have to be specified by their hex value. This is done by inserting a percent character, followed by the hex value of the character. For example, the plus sign is x'2b' in ASCII, so if you key this symbol into a field in a browser, it will send %2b to the server. The phrase "Tracy + Scott" would therefore be sent as follows:

http://www.example.com/cgi-bin/ap100r4.pgm?who=Tracy+%2b+Scott

The plus signs in that parameter denote spaces, and the %2b denotes a plus sign.

If you wanted to write a routine that properly decodes all of this data, you'd have to take all of these different things into account. It can be done, but doing it properly isn't as trivial as it may have originally seemed. Fortunately, it's not necessary to write your own routine to do this. IBM provides APIs that can be used to parse this query string.

As I said in the last article, most people will end up using toolkits like CGIDEV2, CGITOOLS, or eRPGSDK for their RPG Web development. These toolkits also contain utilities for decoding the input that's received from a browser. These utilities make it even easier than calling the IBM APIs.

When not using a toolkit, I prefer to use the QtmhCvtDB API. The other API that IBM provides is QzhbCgiParse. Although this latter API is both faster and more powerful than QtmhCvtCB, it's much more complicated to use, so I won't provide examples in this article.

One nice feature of RPG is its ability to define data structures based on the record format of an externally defined file. The QtmhCvtDB API uses this capability to great effect. What you do is create an empty physical file with all of the fields that you want to be parsed from the data you receive from the browser. Then you call the API, and it puts it all in an externally defined data structure for you.

For example, to retrieve the 3 fields that I used in the example above, I could define the following PF, which I've named WWWORDER:

     A          R ORDERREC
     A            ACTION        15A
     A            ORDNO          5A
     A            CUSTNO         4A

I'll bring this PF into my program as an externally defined data structure that I'll call WEBFORM. This is easily done with one line of code in RPG:

     D WEBFORM       E DS                  Extname(WWWORDER)

I'll retrieve the QUERY_STRING environment variable and then use the QtmhCvtDB API to load the WEBFORM data structure from the conents of the QUERY_STRING:

      .
      .
     D getenv          PR              *   extproc('getenv')
     D   envvar                        *   value options(*string)

     D QtmhCvtDB       PR                  ExtProc('QtmhCvtDb')
     D   QualFile                    20A   const
     D   InpString                32767A   const options(*varsize)
     D   InpStringLen                10I 0 const
     D   RespVar                  32767A   options(*varsize)
     D   RespVarSize                 10I 0 const
     D   DataAvail                   10I 0
     D   RespCode                    10I 0
     D   ErrorStruct               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
     D query_string    s           8000A   varying
     D avail           s             10I 0
     D respcode        s             10I 0
      .
      .
           //
           //   Get input from the browser
           //

            query_string = %str(getenv('QUERY_STRING'));

            QtmhCvtDB( 'WWWORDER  *LIBL'
                     : query_string
                     : %len(query_string)
                     : webform
                     : %size(webform)
                     : avail
                     : respcode
                     : ErrCode           );

At this point in my program, the WEBFORM data structure should contain the values from the GET request. Now I can use them to do something in my program:

           //
           //  Print the top of the output
           //

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

            data = '<html>'
                 + '  <head>'
                 + '    <title>Order Information</title>'
                 + ' </head>'
                 + ' <body>'
                 + CRLF;
            QtmhWrstout(data: %len(data): ErrCode);
           //
           // Load the order into memory
           //

            chain (CustNo: OrdNo) ORDERFILE;

           //
           //  If the order couldn't be found, show an error message
           //

            if not %found;
                data = '<h1>Order not found!</h1>'
                     + '</body></html>'
                     + CRLF;
                QtmhWrstout(data: %len(data): ErrCode);
               return;
            endif;

           //
           //  Otherwise, show the order...
           //
             .
             .

At this point, I'd insert more calls to QtmhWrStout that would send the HTML to the browser so that the user would see the order when the program runs successfully.

The beauty of this is that it's a regular RPG program, so I can write the business logic the same way that I would with a green screen or report program. In the example above, I used a CHAIN opcode to get the order data from a database on my system.

The GET method has its limitations, of course. Browsers and servers vary widely on the maximum length of a URL; therefore, the amount of data that you can send in a GET request also varies. Fortunately, there's an alternative: the POST request method.

The POST method was added to solve the problems of submitting data via GET. The way it works is by allowing the browser to send a large chunk of data to the server in a manner that's very similar to a file upload. As the server receives this data, it creates a pipe between the server and your program and forwards the uploaded data to your program across that pipe.

In order for your program to read that data, you need to read from your "standard input." IBM provides an API for doing just that; it's called QtmhRdStin.

To change the Web form to use POST instead of GET is very easy to do. You just change the METHOD parameter of the <FORM> tag. For example, the following sample form will use the POST method:

<html>
 <body>
   <form method="POST" action="/cgi-bin/posttest.pgm">
     <input type="hidden" name="action" value="showorder">
     Order No: <input type="text" name="ordno" size="5" maxlength="5">
     <p>
     Cust No: <input type="text" name="custno" size="4" maxlength="4">
     <p>
     <input type="submit">
   </form>
 </body>
</html>

The only thing you need to do differently in your program is read the data from standard input and use that instead of the QUERY_STRING. The following code demonstrates this:

     H DFTACTGRP(*NO) BNDDIR('CGIPGM')
     FORDERFILE IF   E           K DISK

     D QtmhRdStin      PR                  extproc('QtmhRdStin')
     D   RcvVar                   32767A   options(*varsize)
     D   RcvVarLen                   10I 0 const
     D   LenAvail                    10I 0
     D   ErrorCode                 8000A   options(*varsize)

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

     D QtmhCvtDB       PR                  ExtProc('QtmhCvtDb')
     D   QualFile                    20A   const
     D   InpString                32767A   const options(*varsize)
     D   InpStringLen                10I 0 const
     D   RespVar                  32767A   options(*varsize)
     D   RespVarSize                 10I 0 const
     D   DataAvail                   10I 0
     D   RespCode                    10I 0
     D   ErrorStruct               8000A   options(*varsize)

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

     D WEBFORM       E DS                  Extname(WWWORDER)

     D CRLF            c                   x'0d25'
     D data            s           1000A   varying
     D post_data       s           8000A
     D post_len        s             10I 0
     D avail           s             10I 0
     D respcode        s             10I 0

      /free

           //
           //   Get POST data
           //

            QtmhRdStin( post_data
                      : %size(post_data)
                      : post_len
                      : ErrCode );

            //
            //  Convert to externally defined DS
            //

            QtmhCvtDB( 'WWWORDER  *LIBL'
                     : post_data
                     : post_len
                     : webform
                     : %size(webform)
                     : avail
                     : respcode
                     : ErrCode           );

           // The ORDNO, CUSTNO and ACTION fields now
           // contain the info from the Web form!

           //
           //  Print the top of the output
           //

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

            data = '<html>'
                 + '  <head>'
                 + '    <title>Order Information</title>'
                 + ' </head>'
                 + ' <body>'
                 + CRLF;
            QtmhWrstout(data: %len(data): ErrCode);
            .
            .

Other than where you get the data from, you don't have to do anything differently to handle basic POST requests than you do to handle GET requests. In fact, they're so similar, that it's easy to code your program so that it'll handle either one:

            method = %str(getenv('REQUEST_METHOD'));

            if (method = 'POST');
                QtmhRdStin( rawdata: %size(rawdata): rawlen: ErrCode );
            else;
                query_string = %str(getenv('QUERY_STRING'));
                rawdata = query_string;
                rawlen  = %len(query_string);
            endif;
            //
            //  Convert to externally defined DS
            //

            QtmhCvtDB( 'WWWORDER  *LIBL'
                     : rawdata
                     : rawlen
                     : webform
                     : %size(webform)
                     : avail
                     : respcode
                     : ErrCode           );
            .
            .

With code like that, you can call the same application using either a GET or POST request, and it'll work either way. Back when I was still coding Web applications without using a toolkit, I did it this way, since it was the most flexible.

Hopefully, this has helped you understand how Web programming in RPG works. It's relatively easy to read input from a Web browser and use it to generate a dynamic Web page. However, once you get working on some serious projects, you'll find that a lot of time is spent creating the output documents so that they're easy to use and look nice. Changing an RPG program repeatedly to get everything looking nice and working properly becomes very cumbersome.

Toolkits like CGIDEV2 simplify the processes of reading data from a Web browser and writing the HTML output so that you can concentrate your efforts on the business logic. In the next article in this series, I'll demonstrate the use of CGIDEV2.


		
      	  	
      	
   	       		 
 		 	
	

ProVIP Sponsors

ProVIP Sponsors