Mod_Perl 2.0 - A Real World Guide - part I
Right now there is a shortage of really easy to understand documentation on using mod_perl to write web applications. There are a lot of examples on using it to rewrite URI's, redirect output, run CGI apps unaltered and even turning Apache into an email over http protocol server. Those are all really wonderful uses, but I want to build a website. So how do you go about doing that? Especially if you aren't using CGI.pm on the backend. This article and others after will focus on that topic. The tutorial is for mod_perl 2 on Apache2. It works equally well on windows or Linux as far as I can tell. I shall be assuming you already have or can find out how to get mod_perl 2 and apache2 on your server. Basically you will be following me as I peek into the internals of making mod_perl useful. Lets start with a simple script to take a look at the internals of what mod_perl lets you do. Here is our script:
You will need to save it into a file called mod_perl_report.pm and then tell mod_perl where it is. To do that you will need some configuration directives in your apache2 httpd.conf file. Now in my case I created a folder in my apache server's root directory (whatever you set ServerRoot to in the conf file) called mod_perl. Then I created a file called mod_perl_prep.pl in the apache configuration directory with my common use statements. In this file I also had the linepackage mod_perl_report; use strict; use warnings; use Apache2::RequestRec (); use Apache2::RequestIO (); use Apache2::Const -compile => qw(OK); sub handler { my $r = shift; my $report; $r->content_type('text/plain'); $report .= "server: ".$r->server()."\n\n"; $report .= "hostname: ".$r->hostname()."\n"; $report .= "user: ".$r->user()."\n"; $report .= "unparsed uri: ".$r->unparsed_uri()."\n"; $report .= "uri: ".$r->uri()."\n"; $report .= "filename: ".$r->filename()."\n"; $report .= "pathinfo: ".$r->path_info()."\n"; $report .= "request time: ".$r->request_time()."\n"; $report .= "request method: ".$r->method()."\n"; $report .= "request string: ".$r->args()."\n\n"; $report .= "cookies: ".$r->headers_in->{Cookie}."\n"; $report .= "status: ".$r->status()."\n"; $report .= "status line: ".$r->status_line()."\n"; $report .= "notes: ".$r->notes()."\n"; $report .= "\n\npost data: ->|".read_post($r)."|< -"; # $report .= "\n\nmodifying variables now: \n\n"; print "mod_perl 2.0 Debugging output:\n\n"; print $report; return Apache2::Const::OK; } sub read_post { my $r = shift; my $buffer; my $data; while ($r->read($buffer, 1000)) { $data .= $buffer; } return $data; } 1;
use lib qw(mod_perl);
so that mod_perl will know where my handler is. I call the mod_perl_prep.pl script from the apache configuration file like so: and finally since this is a development machine I save myself some time by adding this line directly underneath:PerlRequire conf/mod_perl_prep.pl
That line tells mod_perl to reload any modules I use when they change so I don't have to keep restarting apache to see my changes. Believe me you will want this on your development machine cause the restarting gets really old really fast. Now we are ready to tell Apache when to call our debugging report handler. At the bottom of your configuration file add the following section:# comment out the following line for production use. PerlInitHandler Apache2::Reload
Now Apache knows that when it sees the /report URI after the hostname it needs to call my mod_perl_report handler. So lets take a look at that handler right now and see what it does. This module illustrates all the most useful pieces of the Apache2::RequestRec object that I have so far been able to figure out. It starts out with our modules package declarations and any use statements we will need to do the work we are planning to do.<location /report > SetHandler perl-script PerlResponseHandler mod_perl_report
This is all the code we will need in order to use the variouse Apache2 mod_perl interfaces for our web app. Now to be a full fledged working http request handler we need one last detail. All mod_perl handlers require a handler subroutine like so = "package mod_perl_report; use strict; use warnings; use Apache2::RequestRec (); use Apache2::RequestIO (); use Apache2::Const -compile => qw(OK);
There are several pieces to this handler that are important. First the name. It has to be called handler. If mod_perl can't find the handler sub in your module it can't use it. Second the my $r = shift; This is our mod_perl Apache2::RequestRec object for the request we are handling. Lastly the return statement. You will in almost all cases be returning one of two Apache2::Const constants. OK or DECLINED. OK tells mod_perl that your handler is accepting responsibility for this request. DECLINED tells mod_perl that your handler is declining responsibility for this request. You can import all of these constants and many more with the use Apache2::Const -compile ->qw(); statements. See the Apache2::Const documentation for a complete list. Now lets get down to the real nitty gritty. I wanted this script basically to be my "hello world" for mod_perl. It needed to demonstrate how to set up and use mod_perl for a real web app. To that end there were several things I needed it to do. I needed it to be called when I hit a particular url. I needed to output something my browser would understand. And finally I wanted it to be useful information. As I said before there is somewhat of a lack of truly useful information online for this kind of thing. For instance if I have a form that POST's data to a page how do I retrieve it. CGI.pm handles it for you of course. But I really like to know exactly what kind of environment I'm working in. Just chalk it up to that hacker spirit coming out. After paging through a great deal of documentation on the perl website I finally narrowed it down to the pieces that looked the most useful.sub handler { my $r = shift; my $report; $r->content_type('text/plain'); #handler code goes here # print "mod_perl 2.0 Debugging output:nn"; print $report; return Apache2::Const::OK; }
- The Apache2::RequestRec object
- $r->hostname()
The Hostname our server is being called by. - $r->unparsed_uri()
The unparsed URI of our request. This is the whole shebang including any GET strings and path information - $r->uri()
The URI our handler is handling. This should correspond to whatever was in our location section of the httpd conf file. In this case /report - $r->filename()
This is the exact path our URI maps to on the server harddrive. - $r->path_info()
This is the portion of the URI that comes after our uri from above with the get string excluded if there was one - $r->request_time()
This is the timestamp for the request - $r->method()
This is the Request method it could be any one of GET, POST, PUT... and so on. Even one you made up if you have a client that can send it. And your handler takes over the request at the correct time. - $r->args()
This is the request string. Basically anything that appears after a question mark in the URI. If your handler was called at the right spot in Apache's request handling then you could potentially capture both GET and POST data sent from a client in the same request. I'll go into that more later. - $r->headers_in->{Cookie} This is the string from the cookie headers. headers_in basically gives you a hash like tied interface to the headers from the client.
- $r->status()
This is the current response code for the request (eg. 200, 404, 500). You can also set the response code here. - $r->status_line ?? undetermined yet. If you know more then leave a comment
- $r->notes() ?? undetermined yet. If you know more then leave a comment
- $r->read($buffer, $len) read from the request buffer. Used to retrieve any posted data. Use is much like perls core read() function. It is used in our read_post subroutine to read any post data that might be present.