don’t like CGI programming because it is unstructured?though programming in general has been structured for many, many years. Web developers have powerful IDEs, formidable application frameworks like Enterprise Java Beans, and various code-generation tools, but still much Web development is based on CGI, or something very much like it, and CGI remains unstructured.
But what if you tried to apply all the structuring techniques that have been developed since the 1950s to CGI development? Not to denigrate the tried-and-true CGI development paradigm, but rather to imagine what kinds of powerful Web programs you could create if you had the time?and to create more time by making Web development faster, easier, and less error-prone.
An Example
Suppose you’re developing a shopping cart for an e-commerce Web site, and you’ve already implemented credit-card payments. Now, you want to add payment by some other method?PayPal, for example.
You add another form:
Of course, you’ve already written completeOrder.cgi, which looks like this (in a random Hypothetical Web Language):
if ($method == "CreditCard") {...} else if ($method == "Offline") {...} else ...}
To this conditional you must add a case for PayPal. Or, rather, you should add this case, because otherwise the form won’t work right. But the language doesn’t check this for you, and you could very easily leave this out, or misspell “PayPal”, or misspell “method.” Any number of things could go wrong.
Most programming languages check this sort of thing for you?they can tell you when you’ve misspelled a variable, and many even check the types for you. These “sanity checks” have been developed over the course of decades to take some of the guesswork out of programming, and to relieve the burden of memory that falls on the programmer’s shoulders.
These details may easy for a good programmer to handle, but not very conveniently. CGI is just plain inconvenient. It doesn’t check types or variable declarations and it doesn’t even have function calls.
These flaws crop up, in one form or another, in almost any application framework. Some of the more sophisticated (re: complicated and expensive) frameworks have workarounds, but they’re varied, inconsistent, and clumsy.
Something Better
If you’ve been Web programming for a while, you may be used to these problems. However, there are alternatives. While it’s beyond the scope of this article to consider all the possible solutions (see the related resources), this article covers one possibility?something I call “active links.”
An active link is a kind of function call that has been encoded as a Web link on a Web page. When the user clicks on it, the function is called. It’s as simple as that.
Here is how I’d like an active link to look?again, in Hypothetical Web Language:
$link = activeLink( processOrder( $account, $method ) );...Click to process order!
In the above example, you’re not calling processOrder(). Instead, you’re freezing a call to processOrder() and stuffing it in a Web link. When the user clicks on that link, the order is processed.
Most commercial languages use convenient syntax like this. Programmers have to juggle things around a little bit to get it to work. But the syntax above is the ideal?after all, succinctness is power.
Figure 1. The User Interface of the Mult Program: Clicking on a link will give you the product of the two numbers. |
The following sections, describe a single, simple program, written in multiple languages?Java, JavaScript, Perl, and PHP. In each case, it will be as convenient as possible to create and use an active link, and you’ll develop the libraries necessary to make it work.
The Example Application
In order to focus on programming-language issues, the example application is very simple?it’s a multiplication table, called Mult. Figure 1 shows the user interface.
If you click on any of the links, you get the answer:
Figure 2. Click the Links: The result of clicking on a multiplication link in the Mult program. |
In each language, the implementation consists of three parts:
- The multiplication routine, called mult(), which takes two integers and returns the answer.
- The main page, which renders the multiplication table filled with active links.
- The infrastructure, which “freezes” a call to mult(), and then “thaws” it when the user clicks on a link.
The idea here is to make the first two parts very convenient, because they comprise the bulk of all Web programming. The third part involves a bit of trickery, but that’s okay?you only have to do it once. In a sense, you’re trying to take the messiness described above in the shopping-cart example and remove it from the day-to-day code you have to write. To do this, it must be hidden in the infrastructure.
The following sections demonstrate how this is implemented in the four languages.
Mult in PHP
PHP is very flexible, and this allows active links to be used with great succinctness.
Since this is the first you’ll be looking at, we’ll spend some time looking at this appin some detail.Here’s what mult() looks like:
function mult( $a, $b ){ $prod = $a * $b; print " $a x $b = $prod
Simple enough?it takes two numbers, multiplies them, and returns the result, formatted nicely with HTML.
Now, take a look at the multiplication table:
print( "" );print( "" );print( "" );print( "X " );for ($c=0; $c<10; ++$c) { print( "$c " );}print( " " );for ($r=0; $r<10; ++$r) { print( "" ); print( "$r " ); for ($c=0; $c<10; ++$c) { $link = alLink( "mult", $r, $c ); print( " $r x $c " ); } print( " " );}print( "
" );print( " " );
Again, it's pretty simple. It just iterates through all pairs of numbers and makes a link for each one.
Here's a closer look at the creation of the link:
$link = alLink( "mult", $r, $c );
alLink() is the function that creates an active link. This call is like the ideal example I showed above, except not quite as perfect. The arguments to alLink are as follows:
- The function to be called?mult()
- The first number
- The second number
In other words, the line above is a clumsy way to specify a function call. It would be more elegant to say something like this:
$link = alLink( mult( $r, $c ) );
But instead of freezing the call, the above would invoke it right then and there, which is not what you want. If you can't have the syntax you love, you have to love the syntax you have.
That takes care of the easy parts?the construction of the link, and the routine that it invokes. Now, take a look at how the link invokes the routine. The muliplication table is generated in index.php. If you look at the source, you'll see that each link looks something like this:
http://hostname/devx/activelinks/php/alinvoke.php?_alinfo=a%3A3%3A%7B.....
I've left out a lot of junk at the end?the actual link is much longer. In this implementation, every link looks the same. They all send the browser to a PHP script called alinvoke.php, and there's always one parameter, called _alinfo, which is bound to a really long string of junk.
alinvoke.php contains mult(). It also contains the following line at the top:
require_once( "activeLink.php" );
activeLink.php contains the active link library. It also contains the call that invokes mult(). When the user clicks on the link, the browser is sent to alinvoke.php. This in turn reads in activeLink.php, which defines a bunch of functions and then calls alExtractAndApply(). This function takes the junk in the _alinfo parameter, decodes it into a function name and a list of arguments, and then calls the function, which is also defined in alinvoke.php.
This is what that means for the programmer: to call a function from an active link, you just put the function into alinvoke.php, and the build the link in your Web page. Or, if you don't want to put all of your code into one file, you can put it in another file, and use require() to pull that code into alinvoke.php. In other words, it's just like creating a regular function, except that it gets called when the user clicks on a link.
Freezing and Thawing in PHP
It's time to take a closer look at how the function call is frozen and thawed. The function call is defined by an array containing the name of the function and the arguments to that function. This array is turned into a string using the serialize method. This string is no good for use in a link, so you must first encode it with the urlencode() function. Here's the function alLink() which takes care of this:
function alLink(){ global $alFile; $info = func_get_args(); $enc = urlencode( serialize( $info ) ); return "$alFile?_alinfo=$enc";}
When the user clicks on the link, the frozen function call must be thawed:
$info = unserialize( $einfo );alApply( $info );
);print qq( Here's the call to alLink(): This is more or less the same as the PHP, except for the syntax change. Note that you have to specify the package that the mult() routine lives in-in this case, 'main'. This is because the ActiveLink code lives in its own package, and needs to know what package the target routine is defined in. Freezing and Thawing in Perl To thaw the function call, call eval() on the frozen string, which then provides a list: As before, alApply() takes the first element in the list and applies to the rest of the elements in the list: Mult in Javascript Again, the multiplication routine is as simple as can be: Likewise, the generation of the multiplication table is straightforward: The link is defined in more or less the same way as in PHP and Perl: One difference with the JavaScript implementation is that, in alinvoke.html, you must include the active link library after the definition of the mult() routine: Otherwise, the mult() routine will not be defined when alExtractAndApply() is called. Freezing and Thawing in JavaScript This code, when evaluated, will return the original list: Next, apply the first element of the list to the rest of the elements of the list: Mult in Java In this application, the Java version is implemented as a JSP combined with a supporting class. The supporting class, called Mult, contains the multiplication routine: The generation of the multiplication table lives in index.jsp, and is straightforward: Note that the creation of the link is different than in the previous applications. This is because Java is a strongly-typed language, and one that does not have functions that take a variable number of arguments. Thus, the link must be constructed in steps. This line creates the link object: The first argument to the ActiveLink constructor is the class name?Mult lives in a package called mult. The second argument is the name of a static method in that class. Next, add the parameters, i.e. the two numbers to be multiplied: alinvoke.jsp is also a bit different. It contains a single line of active code: This gets the _alinfo parameter and passes it to ActiveLink.call() for invocation. Freezing and Thawing in Java Encode this array as string (using the ISO-8859-1 character encoding), and further encode it for inclusion in a link: To thaw the function call, call the static method ActiveLink.call(), passing in the encoded string. This in turns calls ActiveLink.createFromEncoded(), which reads the function call information and returns an ActiveLink object. Calling the call() method of this object calls the method. It does this by using a helper class, ExecuteCall, which loads the specified class using Class.forName(), and then calls the specified method using the java.lang.reflect package. Towards a Strcutured CGI The only exception to this is the Java version. The techniques used in PHP, Perl, and JavaScript don't readily apply to Java. I mentioned above that this has to do with the fact that Java is strongly-typed, but this isn't the real reason. The real reason is that Java doesn't provide any kind of introspection at the call-site?it's not possible to write code that iterates through the list of parameters to a function. This is really a problem with lacking a hygenic macro facility more than it is a problem with being typed.);print qq( X );for (my $r=0; $r<10; ++$r) { print qq($r );}for (my $r=0; $r<10; ++$r) { print qq(); print qq( );print qq();print qq();$r ); for (my $c=0; $c<10; ++$c) { my $link = alLink( "main::mult", $r, $c ); print qq( $r x $c ); }}print qq(my $link = alLink( "main::mult", $r, $c );
To freeze the function name and arguments, use the Data::Dumper package to turn them into a string, and the uri_escape() function to encode it for inclusion in a link. This is done in alLink():sub alLink{ my @info = @_; local $Data::Dumper::Indent = 0; local $Data::Dumper::Purity = 1; my $enc = uri_escape( Dumper( @info ) ); return "$alFile?_alinfo=$enc";}
my $info = eval( $src );alApply( $info );
sub alApply( $ ){ my $fun = shift @$info; $fun->( @$info );
Syntactically, JavaScript is fairly similar to the PHP and Perl implementations. However, this implementation of Mult runs entirely on the client side.function mult( a, b ){ var prod = a * b; document.write( "
" ); document.write( a+" x "+b+" = "+prod ); document.write( "
" ); document.write( "document.write( "
" );document.write( "
" );document.write( "" );document.write( " " );for (var r=0; r<10; ++r) { document.write( "X " );for (var c=0; c<10; ++c) { document.write( ""+c+" " );}document.write( "" ); document.write( " " );}document.write( ""+r+" " ); for (var c=0; c<10; ++c) { var link = alLink( "mult", r, c ); document.write( " "+r+" x "+c+" " ); } document.write( "var link = alLink( "mult", r, c );
To encode the function call in JavaScript, take a list containing the function named and arguments and call toSource() on it, which returns a piece of Javascript source code. Escape this code with the escape() function:var enc = escape( info.toSource() );
var info = eval( unescape( results[1] ) );alApply( info );
function alApply( info ){ var fun = this[info[0]]; var funargs = new Array(); for (var i=1; i
The Java implementation is rather different than the previous implementations. This is understandable?PHP, Perl, and Javascript are often considered scripting languages, which can be more flexible and loose than more formal languages like Java.public class Mult{ static public String mult( int a, int b ) { int prod = a * b; return "
"+a+" x "+b+" = "+prod+"
<% for (int r=0; r<10; ++r) { out.println( "X <% for (int c=0; c<10; ++c) { out.println( ""+c+" " ); }%> " ); }%>"+r+" " ); for (int c=0; c<10; ++c) { ActiveLink link = new ActiveLink( "mult.Mult", "mult" ); link.addParam( r ); link.addParam( c ); out.println( " "+r+" x "+c+" " ); } out.println( "ActiveLink link = new ActiveLink( "mult.Mult", "mult" );
link.addParam( r );link.addParam( c );
<% out.println( ActiveLink.call( request.getParameter( "_alinfo" ) ) ); %>
To create the link, write a list containing the function name and arguments to an ObjectOutputStream which is writing to a byte array:ByteArrayOutputStream baos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream( baos );oos.writeObject( info );byte bs[] = baos.toByteArray();
String s = new String( bs, "ISO-8859-1" );return URLEncoder.encode( s, "ISO-8859-1" );
static public Object call( String info ) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, IOException { ActiveLink al = createFromEncoded( info ); return al.call();}
As you can see, it's possible to radically alter the way you program an application simply by building the proper plumbing behind the scenes. This can be done in a variety of languages, using techniques that work similarly in each of those languages.