Document Home


Previous - Restructuring the Entry Process Using the Apache API

Samples for this section


The Menu and Global State Management


My starting point here will be the base module I used in the previous section on stacked handlers. (BTW, I tracked down the resolution of the problems associated with the use of external files through filehandles to print html back to the browser. I'm going to hold that back until the next major section, in large part because having the menu html visible within this module will make examples easier to see. If you are impatient for that particular little tidbit you should of course feel free to skip ahead, but I would not recommend it.) From here on out, most of the interaction between the server and the browser, both incoming and outgoing, will take place through the Apache request object. In the current set of examples, the directives specified in the <Location></Location> sections of the httpd.conf file represent the instructions to Apache regarding how to pass that object to perl, as I briefly discussed in the previous section. In this section I am going to begin discussing what the module does when it gets that object.


In this version of the BB_STACKED module, the beginning of the module behaves much as does the initial section of any module, establishing the environment that is pertinent for the execution of the subroutines within that module. In this circumstance, the modules incorporated include Apache, which provides the methods used to interact with the request object, and Apache::Constants, which includes the http status messages. Following these two are the familiar BB_UTIL and BB_APP_INTERFACE modules, although it is appropriate to recognize that the subroutines used in these modules have been modified somewhat from their previous versions, as you will see. The FileHandle::Deluxe module is incorporated because I used it to output stuff that I was using to debug and diagnose as I set up the structure, as I will illustrate shortly. The final module specified, CGI::Cookie, is used for certain aspects of state maintenance. Following module incorporation, the hash references used to create select boxes are created as before, and I initialize a set of variables, and I initialize a set of variables that I wanted to be visible at the module level rather than scoped to the subroutine level.


The header() and footer() subroutines remain much the same as in the introductory stacked handlers example in the previous section, with the exception of some additional stuff stuck onto the end of the URI representing the option associated with any given menu item. This is key to the first-level navigation of the site, that additional information is extracted in the body() subroutine and used to determine which option has been selected. After initializing the request object,

	my $r=shift;
the $path scalar is assigned the output from the request object's path_info method.
	my $path=$r->path_info;
(If you've extracted the module from the samples file and are reading along, pay no attention to the intervening steps for now. BTW, congratulations on your initiative.) What is now stored in the scalar is referred to as "additional path information", and represents the information following what apache assumes to be the name of the requested file, which is whatever follows the last directory actually present in the real directory referenced by the virtual directory specified in the pertinent alias directive in the httpd.conf file. Now that sounds confusing as all get out, so the best way to look at this is with an example. I'll use the option for League Description, specified as follows
			<a href="/bb_app/league/2" /href> League Description </a>	
in the menu html table. Now when Apache parses the URI, it follows the actual directory referenced by the alias
Alias /bb_app/	/var/www/perl/bb_app/
until it reaches the first element not actually in the path. In this instance that is the component "league". Apache assumes that component is the requested file, regardless of the presence or absence of any files in the directory. (In fact, you can actually define specialized handlers for files of a given type in a given directory, but that's not what I'm doing here. At this point, the presence or absence of file file has no bearing on anything.) What is key is that any additional information following that file name is considered by Apache to simply represent additional information, or as termed, "additional path information". To take yet another cut at this ....
"/bb_app/league/2"
is dealiased to
"/var/www/perl/bb_app/league/2"
Everything through bb_app is present in the directory structure, so Apache assumes "league" is the filename. The filename method of the request object ($r->filename) will thus return "league", and the pathinfo method ($r->pathinfo) will return "/2".


As a sidenote that might broaden your perspective on this just a bit, I was verifying my understanding of the interplay between these components before writing this when I noticed what seemed to me to be an anomaly. You may have noticed that early in this version of the body() subroutine I write a number of lines to the $log2 filehandle.

	my $log2=new FileHandle::Deluxe('/home/www/log2.txt',append=ɭ,safe_dirs=>['/home/www']);	
	print $log2 $r->as_string;
	print $log2 $ENV{PATH}."\n";
	print $log2 $path."\n";
	print $log2 $file."\n";
As I said earlier, I left these lines in at this point primarily to illustrate the kinds of things that are available script execution passes a given point. It would not be a bad idea to run it a few times as it is, and look at the output in that file. In particular, the as_string method returns the contents of the request object as a string, one invocation of which looks like this ...
GET /bb_app/league/2 HTTP/1.1
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-excel, application/msword, application/vnd.ms-powerpoint, application/x-shockwave-flash, */*
Accept-Encoding: gzip, deflate
Accept-Language: en-us
Connection: Keep-Alive
Host: ralphzilla
Referer: http://ralphzilla/bb_app
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)
Selecting a few options from the menu will result in the same number of object instances being written to the log file, and reading through them can be a very enlightening endeavor. Regardless, in this circumstance I was primarily interested in the $path and $file values. While $path held an appropriate string (e.g., "/2"), allowing the page to be generated appropriately, $file held strange things like "/var/www/perl/bb_appleague". (Obviously, the path and file without the last slash.) After thinking about this for a bit and browsing through the documentation something triggered the idea of looking at the alias directive in the httpd.conf. At that point, the directive looked like this ...
Alias /bb_app/	/var/www/perl/bb_app
I changed it to the current state
Alias /bb_app/	/var/www/perl/bb_app/
and, sure enough, the file name came out as I would have expected. Clearly, at some level Apache is simply substituting the actual path specified in the alias statement for the path incorporated in the URI sent by the browser. A good thing to know.


In any event, as I mentioned earlier, the first level of site navigation is controlled by strings stuck onto the end of the URI and matched in the body() subroutine.

	if (($path=~/1/) or (!$path))	{
		

		$r->print("<body bgcolor='#c0c0c0' text='#0000ff' link='#0000ff' vlink='#800080' alink='#ff0000'>");
		$r->print('This is the home page for the Legendary Lizards Baseball Association');
	
	}

	if ($path=~/logged/)	{
		
		&save_state(\%params,$r);
	}
		
		
	if ($path=~/2/)	{
		
		$r->print('<body bgcolor="#c0c0c0" text="#0000ff" link="#0000ff" vlink="#800080" alink="#ff0000">');
		$r->print('			2222');
	
	}
	

	if ($path=~/3/)	{
		
		$r->print('<body bgcolor="#d0d0d0" text="#0000ff" link="#0000ff" vlink="#800080" alink="#ff0000">');
		$r->print('3333');
	
	}
		
		
	if ($path=~/4/)	{
		
		$r->print('<body bgcolor="#c0c0c0" text="#0000ff" link="#0000ff" vlink="#800080" alink="#ff0000">');
		if (! $user_name)	{
			
			$r->print("You must log in before records can be entered");
		}

		elsif ($user_name)	{
			
			$r->print($user_name);
			$r->print($num_forms);
			
		} 
	}
		
	if ($path=~/5/)	{
	
		$r->print('<body bgcolor="#c0c0c0" text="#0000ff" link="#0000ff" vlink="#800080" alink="#ff0000">');
		&sel_form(\$r,$entry_people,$games);			
		
	}
There are lots of ways I can get fancier than that, and at some point I will probably employ some of them, but for now I just want to get the whole thing hanging together. As you can see, most of the menu choices here do nothing more than print a string of numbers, allowing for confirmation that the page is being generated properly. Note that I could as easily have constructed navigation around the string held in $file, but as I might actually want to use that to reference a file somewhere down the road I considered it inappropriate to create a potential conflict.


At this point it is appropriate to think a little bit about the manner in which I have envisioned the page being used. The model I am following at this point is that anyone should be able to access the application and do certain things, but to enter data and perform the more advanced tasks that will be added the user will have to be logged in. Obviously, to do that I am going to have to have some way to maintain that status, otherwise known as maintaining state. In the cgi version of the entry script I did that with hidden fields, and though it didn't seem that way at the time that was relatively straightforward. However, using stacked handlers to generate a page that uses this kind of menu implies the kind of structure that would render the use of hidden fields both difficult and clunky. If you think about it just a little the reasons for that will be clear. First, there is no natural form present on the base screen. Now that's not a big deal, there is no reason I could not construct a form comprised entirely of hidden elements. Think, however, of what is being displayed at the client end. Right-click and view source, just to reinforce it. For all that I am getting into stacked handlers and header menus, what is being generated here is one page at a time. What happens when a menu item involves the generation of a form? Then I would have an inner form, and an outer form, and never the twain shall meet <grin>. While it would be possible to construct a structure that would handle form coordination, it is not something that has to be done. It makes far more sense to handle outer level state maintenance with a completely different tool, and then use forms and hidden fields on localized looping constructs like the entry process. Beyond the fact that such an implementation is far cleaner, it also follows the time-honored tradition of not making things more difficult than is needed. Nested complex applications have a way of stepping on each other. Quite beyond being more time-consuming to construct and maintain, they also tend not to work as well as those that avoid unnecessary complexity.


Given that preamble, you may already have jumped ahead and gone looking through things to see what I was talking about, when you should have known all along that I was going to start messing about with cookies, using the methods included as part of the CGI::Cookie module. (Obviously, the module is not a prerequisite for using cookies, but it does substantially ease the task of cookie maintenance.) Before I get into the cookie end of things, however, I should trace through the login process itself. Currently, when the user selects the login option, the server will generate a page using the sel_form() routine. (This is one of the things I am likely to change as time goes on, probably adding some form of authentication, but I'll use the current version to get started.) The subroutine is called much as it was in the cgi script, with the obvious exception that I pass it a reference to the request object rather than a reference to a cgi object, along with the scalars holding references to the hashes of entry prople and games. (Come to think of it, it really doesn't make a whole lot of sense to have a game selection widget here. I'll take care of that next time around.)


If you look at the sel_form subroutine you will note that the only real change has been to conform to the form of the print statement used by the Apache module

		$$forms->print("<H1>LOGIN</H1>");
In this implementation, of course, I pass a reference to the request object to the subroutine in place of the reference to a cgi object I passed previously. (When I first hooked this subroutine into the framework I changed from passing by reference to passing the actual object. Once I confirmed that everything worked equally well either way, I changed back to using references simply to maintain that level of compatibility between the two versions.) Once the submit button is clicked, of course, the form entries are sent to the server.


When the form is sent to the server, it carries with it a string that signals the server that the login process is involved. I do that by specifying a URI in the action parameter of the form definition, as in:

		$$forms->print("<p><form action='http://ralphzilla/bb_app/login/logged'>"); 
As a result, when Apache parses the request string "login" will be interpreted as the file name, and the string "/logged" assumed to be additonal path information. This is trapped in the body() subroutine, triggering the save_state() routine and passing it a reference to the %params hash and the request object. (The %params hash is populated early in the subroutine with the output from the args method.)


In the save_state() subroutine the cookies are created. As discussed above, I use the CGI::Cookie module to handle cookie management. In this circumstance the $user_name and $num_forms scalars are populated from the %params hash, and cookies created that hold those values.

sub save_state	{
	
	my ($params,$r)=@_;
	$user_name=$$params{'user_name'};
	$num_forms=$$params{'num_forms'};
	
	$user_cookie=new CGI::Cookie(-name=>	'user',
					 -value=>$user_name);

	$forms_cookie=new CGI::Cookie(-name =>	'forms',
					  -value=>$num_forms);
								  
	$r->header_out('Set-Cookie',$user_cookie); 	
	$r->header_out('Set-Cookie',$num_forms);
}
Note that the cookies are created with no parameters other than the name of the cookie and the value held; that no expiration date is set on the cookie. As a result, the cookie will persist only so long as the browser session remains active, thus requiring another login when another browser session is started. This is the behavior I desire for the way I have envisioned the page being used. After creating the cookies, the header_out method of the request object is used to add the cookie information to the html header. In this version, with nothing else executing beyond the save_state() subroutine, the page drawn will be nothing more than the header() menu and the footer() banner will be returned to the browser, as shown in the figure to the right. I could easily have put in a "You are now logged in ..." message, but I thought that this is one of those circumstances that ultimately help to give you an instinctive understanding of what is going on at one level above the nitty-gritty with which we now deal. Once the login form has been submitted, Apache submits the request object to header(), drawing the menu at the top; to body(), which recognizes that a login has been accomplished, creates the cookies and places them into the html header but doesn't actually print anything; and to footer(), which prints the footer banner and signals the end of the html page.



Now the next time an option is selected, the values in the cookie are available for retrieval.

	my %cookies = fetch CGI::Cookie;

	if (%cookies)	{
		
		$user_name=$cookies{'user'}->value;
		$num_forms=$cookies{'forms'}->value;

		}
As the %cookies hash will now receive results from the CGI::Cookie fetch method, the lines inside the if statement will execute, assigning the appropriate values to the $user_name and $num_forms scalars. This can be verified by selecting "Game Entry and System Maintenance", which at this point will simply print the user id code and the number of forms selected.


Now that I have a structure on which I can start hanging functionality, I'll start doing that. In the next section I'll drop the data-entry loop into this thing.



Next - Dropping in the Entry Process