Rolling Your Own MultiValue Web Connector - Part 1
If you've been following these articles for the past few months, you may be sensing that I'm a bit stoked about all that's happening in the world of web technology. And why not? With new versions of HTML, CSS, and Javascript fresh out of the oven, plus an abundance of new web-enabled tablets and phones hitting the market, there is truly MUCH to be excited about!
All the cool web technology available right now doesn't mean much if we can't make it work with our MultiValue systems, however. Our information, the valuable lifeblood of our companies, lives in those systems, so if the web can't talk to our databases then … why bother, right? Well my friends, prepare yourself to bother because I'm about to show you that a browser CAN talk to MultiValue databases, and it's not nearly as difficult or expensive as you might have been led to believe.
Before we get too far down this rabbit hole, allow me to first acknowledge that there are a number of vendors who have spent a good deal of time, money, and effort creating solutions to make the web accessible to MultiValue programmers. My point here is not to discount those efforts or to undermine them in any way. Rather, for those who don't mind a bit of bare metal programming and diving deep into the dirty bits, I'd like offer you something interesting to think about, something fun to "research," and if all goes to plan, this exercise might just shift your perception a little.
In an effort to keep this simple, we'll be building this connector as a series of incremental steps. Today we'll create the MultiValue parts needed for our connector. Later we'll create some code to connect our MultiValue solution to a web server. Finally, we'll tie it all up with one more script that will give us secure and controlled access to our MultiValue data and programming from a typical website.
Ready to jump into the dirty bits? Let's do it!
Most MultiValue systems have a shell program that is launched from the host operating system command line. For example, Unidata has "udt", QM has "qm", Universe has "uv", etc. While these shell programs are usually launched from a login script when someone makes a telnet connection, these commands can just as easily be launched from an ssh session, cron job, or even from a web server. This is this foundation on which we will build our connector.
On the MultiValue side, our connector is nothing more than a small Basic program that accepts some input and does something practical. Of course, "something practical" could mean just about anything, so we'll build our routine as a simple wrapper that can call a subroutine. With this foundation in place, your team of geniuses can create subroutines for whatever functionality your web solution may require.
Before we create this program, however, we need to figure out how to ask our connector to do something. Most MultiValue systems provide some way of reading environment variables from the underlying operating system, so let's start there. We'll set an environment variable called WEBREQ with a request, start up our MultiValue database, and then grab the response when the shell finishes.
Another thing to consider is how we will launch our Basic routine. Most systems have some way of starting the shell and launching a specific program, but they don't all work the same way. Therefore for portability we'll leverage the login procedure (named "LOGIN" on Unidata or may be named according to the account on other platforms) to check for our environment variable and do something accordingly. If our environment variable is not set (as would be the case for a normal telnet connection) our program can stop and allow the login procedure to continue normally.
Figure 1 shows how this might be accomplished using a program named RUN.WEB.REQUEST. This program was written for Unidata but could be easily translated to a number of MultiValue platforms.
* * Program Name: RUN.WEB.REQUEST * Written By: Kevin King * Project: Spectrum Demo * Date: 26 Nov 2011 * Description: This routine will look for an environment variable WEBREQ and * will call a subroutine as requested when found. * WEBREQ = GETENV('WEBREQ') IF (WEBREQ NE '') THEN OPEN 'WEBOUT' TO F.WEBOUT THEN SUBR.NAME = FIELD(WEBREQ,',',1) OUT.NAME = FIELD(WEBREQ,',',2) DATA.IN = WEBREQ[COL2() + 1,999999] DATA.OUT = '' CALL @SUBR.NAME(DATA.IN,DATA.OUT) WRITE DATA.OUT ON F.WEBOUT,OUT.NAME CHAIN 'OFF' END END * STOP END
In this program we read the environment variable WEBREQ and expect it to contain three comma-delimited pieces: 1) the subroutine to be called, 2) the name of a file where the output will be written, and 3) the input data for the routine. Once we've parsed the three parameters from the environment variable, we use a CALL @ to call the subroutine as named and then write whatever result was returned to a directory file named WEBOUT (which I usually map to the _HOLD_ directory on Unidata or $HOLD on QM).
Despite the simplicity of this routine, there are many details to explore. For example, you might wonder why we're passing the input in an environment variable but writing the output to disk. Good question!
The input from the web might contain confidential information (i.e. credit card numbers) so we don't want that information EVER written to disk. Of course, with the output written to disk — the only portable way to get information out of the database and into the underlying operating system where it can be picked up by an external program — we must be careful to avoid including sensitive information in our output or find another way to get the information out of the database shell and into the calling operating system.
(Printing the output to the screen and capturing it in a calling script can work too. However, I like to use CRTs and COMO for debugging and having debugging output mixed with the real output data is a little too inflexible for my tastes. Also note that this routine leaves basic error handling as an exercise for the reader.)
Rather than only calling subroutines, we could expand upon this concept to include fundamental database operations like reading, writing, deleting, returning a select list, executing a TCL command, etc. However, because these operations necessitate more advanced security considerations, we will be focusing only on calling subroutines. These subroutines can then be written as securely or insecurely as you prefer.
Speaking of security, you've probably also noticed that our routine allows a subroutine to be called with absolutely zero authentication or additional security. Many MultiValue systems don't ask for additional authentication once someone has logged in to the box, which may be fine for telnet users, but should be given additional consideration for web-based connections.
Figure 2 is a very brief example of a subroutine that might be called from our connector. Note that this subroutine takes two formal arguments, one for input and the other for output. In this simple example we're converting the input string to lower case, but as was mentioned earlier, this subroutine could do absolutely anything it wants with the input in order to produce the output.
SUBROUTINE TEST.SUB(DATA.IN,DATA.OUT) * DATA.OUT = OCONV(DATA.IN,'MCL') * RETURN
With nothing more than these two routines and a minuscule edit to the login procedure to call our starting program, we now have a way of invoking a MultiValue subroutine from the outside world. Next time we'll take a look at how we can use the open source Apache web server and a remarkably small bit of PHP to provide this functionality to a larger audience!