Do you know how to recompile your service programs without having to recompile all programs using the service program?
I have to admit that the binding language was a late discovery for me, and I've struggled for quite awhile working around it. But I've found the binding language to solve the problem of having to recompile your service programs without having to recompile all the programs that are using it. Without the binding language, service programs are very similar to modules due to the need to recompile the programs every time they change. This article will show you how to overcome this problem with the use of the binding language.
I wrote an article called "How to Create, Compile, and Use Service Programs," which is about the basics of how to create a service program. The process of creating a service program is very similar to creating an RPG program, except that there's no need for a main procedure and you use a different command (CRTSRVPGM) to compile your service program.
The service program that was created in the article contained two procedures that are used to encode and decode special HTML characters called htmlEncode and htmlDecode, respectively. These procedures could be very useful and are generic enough to be used in multiple programs. We'll be using the code from that article to illustrate the usefulness of the binding language to easily support service program modifications.
Modifying an Existing Service Program
Suppose you have a bunch of programs now using your service program. It's working well, but you've decided to modify its functionality perhaps to prevent SQL injections or to filter profanity from being entered into your database. This would simply be a logic change and would not impact the attributes of your procedure's name or parameter list.
You might expect that, once you make your logic changes and recompile the service program, this change would be transparent to your calling programs as would be with a standalone program. But anyone who has experience with service programs will be familiar with the signature violation error when you try to use the service program after it's been recompiled. With the use of the binder language, we can assign a signature to the service program when it is compiled and recompiled.
A Realistic Service Program Modification
For our coding portion, we will modify our program to replace the offending code during the database input and also send out a notification for appropriate staff to investigate the potentially abusive or offensive intent.
There are a lot of potential SQL commands that you could monitor for when parsing your input. For this example, we'll look for only one, which is "DROP TABLE". When the program finds these words within the input data, the words will be replaced and reported with a blank.
For our profanity example, I'll pick a random word to represent the obscenity so as not to actually post profanity in this article. As a purely random selection, I'll designate "Microsoft" to be considered a bad word. This word will be looked for, replaced with a constant value of "<PROFANITY>", and reported.
Retrieving the Binder Source
You can create the binder language source from an existing service program using the RTVBNDSRC command. You could use this as your initial binder language source and add procedures to the binder language source as you add new procedures to your service program. Or you could simply retrieve the binder source each time you add a procedure to your program. To retrieve the binder source from our MCPSRVPGM, you could execute the following statement:
RTVBNDSRC SRVPGM(MYLIB/MCPSRVPGM) SRCFILE(MYLIB/QSRVSRC) MBROPT(*ADD)
When you prompt the RTVBNDSRC and CRTSRVPGM commands, you will see that the default name expected for your binder source is QSRVSRC, so that is what I've used. When you retrieve the source, there is no source type assigned, but if you assign the BND source type, then you could use the source entry utility (SEU) and the Rational development tool to recognize the syntax as seen in Figure 1 below.
Figure 1: Use the source entry utility (SEU) and the Rational development tool to recognize the syntax. (Click images to enlarge.)
The binder language source has three commands:
- STRPGMEXP indicates the beginning of the binder language source.
- EXPORT is the symbol name to be exported from the service program or module.
- ENDPGMEXP indicates the end of the binder language source.
I mentioned above that you could simply retrieve the binder source each time that you modify your service program, but then what's the benefit of using a binder source? You might as well not use one at all. My objective in this article is to make it so that you don't have to recompile all programs using the service program. The way I'm going to do that is to keep the signature from changing. So, after you retrieve your original binder source, you can either keep the original default signature or assign your own. My preference is to use the service program name as the signature.
In the binding source in Figure 1, the symbol names are surrounded with quotes, which are unnecessary. The quotes indicate that the name is case-sensitive, but all RPG procedure names are seen as uppercase, so the quotes are not required.
Displaying Service Program Information
If you execute the DSPSRVPGM command on your service program, you can see that the signature is equal to what is currently assigned to the service program.
Figure 2: Execute DSPSRVPGM.
Manually Modified Binder Source
The following binder source is the result of what my binder source would look like, but this is purely preferential for what I believe to be more readable. You're going to need to touch the binder source anyway if you want to keep the same signature as you add more procedures, so why not make it as readable as possible?
Figure 3: Keep your binder source readable.
I like to use camel case naming for my procedures and variables for code readability, so I would typically remove the quotes to have the export names match my procedure names, but this is optional.
Compiling the Service Program
Because we're not using any embedded SQL in our service program yet, we can save the source file as RPGLE and use the CRTRPGMOD command to create our RPG module.
CRTRPGMOD MODULE(MyLib/MCPSRVPGM) SRCFILE(MyLib/QRPGSRC)
After we have our module created, we can create the service program from the module with the CRTSRVPGM command, specifying the SRCFILE as QSRVSRC to utilize the binder source during the compile.
CRTSRVPGM SRVPGM(MyLib/MCPSRVPGM) SRCFILE(MyLib/QSRVSRC) ACTGRP(MCPSRVPGM)
You can verify that the binder source was applied to the service program by using the DSPSRVPGM command, which will reveal that the signature is equal to MCPSRVPGM (in EBCDIC) followed by some spaces. This signature will remain the same upon successive recompiles, if you reference the binder source during the compile, until you change the signature in the binder source.
Figure 4: DSPSRVPGM verifies that the binder source was applied to the service program.
The New Code
At this point, we know that we can recompile the service program as many times as we want.
Now let's finish up by modifying the code for the htmlEncode procedure to filter some profanity and SQL injection threats mentioned above.
P B EXPORT
D PI 65535A varying
D* Passed Parameter List
D argIn 65535A const varying
D* Local Variables
D svPosi S 10I 0
D svOut S 65535A
D svOut2 S 65535A
svOut = argIn;
// Scan for Profanity
svPosi = %scan('MICROSOFT':svOut);
if svPosi > 0;
svOut = %scanRpl('MICROSOFT':
// Send email notification
// Scan for SQL Threat
svPosi = %scan('DROP TABLE':svOut);
if svPosi > 0;
svOut = %scanRpl('DROP TABLE':
' ': svOut);
// Send email notification
svOut = %scanRpl('&': '&': svOut);
svOut = %scanRpl('"': '"': svOut);
svOut = %scanRpl('''': ''': svOut);
svOut = %scanRpl('<': '<': svOut);
svOut = %scanRpl('>': '>': svOut);
P htmlEncode E
This is a minimal example program. If I were to really check for these keywords, I would convert the incoming text to uppercase to compare against the specific matches. I would also strip any extra spaces from the incoming text because SQL can work with multiple spaces in between DROP and TABLE. But this is just a high-level conceptual example. You could take it much further.
Now we test out the new code with some special content:
D inBytes S 100A
D outBytes S 100A
D displayBytes S 52A
D posi S 10I 0
inBytes = '(MICROSOFT DROP TABLE)';
displayBytes = 'Original: ' + %trim(inBytes);
outBytes = htmlEncode(inBytes);
displayBytes = 'Enc: ' + %trim(outBytes);
outBytes = htmlDecode(outBytes);
displayBytes = 'Dec: ' + %trim(outBytes);
*inlr = *ON;
After modifying and compiling the program, you get the following output. You can then recompile the service program multiple times, and the main program will continue to work without a need to recompile it.
> call mcp045rpg
DSPLY Original: (MICROSOFT DROP TABLE)
DSPLY Enc: (<PROFANITY> )
DSPLY Dec: (<PROFANITY> )
You can download the code for this article here.
This was the second article in a three-part series on using service programs. In my next article, I'll close the series with a discussion on binding directories.