Someone has asked how to move from registry scripts to perl handlers, this
is my attempt to show in details the process. Comments are welcome.

=head1 Transitioning from Apache::Registry to Apache handlers

Even if you are a CGI script die-hard at some point you might want to
move a few or all your scripts to Apache Perl handlers. Actually this
is an easy task, since we saw already what C<Apache::Registry> does to
our scripts so they will appear as Perl handlers to Apache.

Now when you are no longer need a backward mod_cgi compatibility you
can benefit from the Perl libraries working only under
mod_perl. We will see why in a moment.

Let's see an example. We will start with a mod_cgi compatible CGI
script running under C<Apache::Registry>, transpose it into a Perl
content handler and then convert it to use C<Apache::Request> and
C<Apache::Cookie>.

=head2 Starting with mod_cgi Compatible Script

This is the original script's code we are going to work with:

  cookie_script.pl
  ----------------
  use strict;
  use CGI;
  use CGI::Cookie;
  use vars qw($q $switch $status $sessionID);
  
  init();
  print_header();
  print_status();
  
  ### <-- subroutines --> ###
  
  # the init code
  ###########
  sub init{
    $q = new CGI;
    
    $switch = $q->param("switch") ? 1 : 0;
    
      # try to retrieve the session ID
              # fetch existing cookies
    my %cookies = CGI::Cookie->fetch;
    $sessionID = exists $cookies{'sessionID'} 
       ? $cookies{'sessionID'}->value : '';
    
      # 0 = not running, 1 = running
    $status = $sessionID ? 1 : 0;
    
      # switch status if asked to
    $status = ($status+1) % 2 if $switch;
    
    if ($status){
        # preserve sessionID if exists or create a new one
      $sessionID ||= generate_sessionID() if $status;
    } else {
        # delete the sessionID
      $sessionID = '';
    }
    
  } # end of sub init
  
  #################
  sub print_header{
      # prepare a cooke
    my $c = CGI::Cookie->new
      (-name    => 'sessionID',
       -value   => $sessionID,
       -expires => '+1h');
    
    print $q->header
      (-type   => 'text/html',
       -cookie => $c);
  
  } # end of sub print_header
  
  
  # print the current Session status and a form to toggle the status
  #################
  sub print_status{
    
    print qq{<HTML><HEAD><TITLE>Cookie</TITLE></HEAD><BODY>};
  
      # print status
    print "<B>Status:</B> ",
          $status
            ? "Session is running with ID: $sessionID"
            : "No session is running";
    
    
      # change status form
    my $button_label = $status ? "Stop" : "Start";
    print qq{<HR>
         <FORM>
           <INPUT TYPE=SUBMIT NAME=switch VALUE=" $button_label "> 
         </FORM>
           };
    
    print qq{</BODY></HTML>};
  
  } # end of sub print_status
  
  # A dummy ID generator
  # Replace with a real session ID generator
  ########################
  sub generate_sessionID {
    return scalar localtime;
  } # end of sub generate_sessionID


The code is very simple. It creates a session if you've pressed the
I<'Start'> button or deletes it if the I<'Stop'> button has been
pressed. The session is stored and retrieved using the cookies
technique.

Note that we have split the obviously simple and short code into a
three logical units, by putting the code into three subroutines.
init() to initialize global variable and parse incoming data,
print_header() to print the HTTP headers including the cookie header
and finally print_status() to generate the output. Later we will see
that this logical separation will allow us an easy conversion to Perl
content handler code.

We have used global variables for a few variables since we didn't want
to pass them from function to function. In a big project you should be
very restrictive about what variables should be allowed to be global
if any at all. In any case, the init() subroutine makes sure all these
variables are re-initialized for each code reinvocation.

Note that we have used a very simple generate_sessionID() function
that return a date string (i.e. S<Wed Apr 12 15:02:23 2000>) as a
session ID. You want to replace this one with code which generates a
unique session every time it was called. And it should be secure,
i.e. users will not be able to forge one and do nasty things.



=head2 Converting into Perl Content Handler

Now let's convert this script into a content handler. There are two
parts to be done; the first one is to configure Apache to run this new
code as a Perl handler, the second one is to modify the code itself.

First we add the following snippet to I<httpd.conf>:

  PerlModule Test::Cookie
  <Location /test/cookie>
    SetHandler perl-script
    PerlHandler Test::Cookie
  </Location>

After we restart the server, when there is a request whose URI starts
with I</test/cookie>, Apache will execute C<Test::Cookie::handler()>
subroutine as a content handler.  We made sure to preload the
C<Test::Cookie> module at the server start-up, with the C<PerlModule>
directive.

Now we are going to modify the script itself. We copy the content to
the file I<Cookie.pm> and place it into one of the directories listed
in C<@INC>. For example if I</home/httpd/perl> is a part of C<@INC>
and since we want to call this package C<Test::Cookie>, we can put
I</Cookie.pm> into the I</home/httpd/perl/Test/> directory.

So this is a new code. Notice that all the subroutines were left
unmodified from the original script, therefore we choose not to repeat
them here to make the differences clear.

  Test/Cookie.pm
  --------------
  package Test::Cookie;
  use Apache::Constants qw(:common);
  
  use strict;
  use CGI;
  use CGI::Cookie;
  use vars qw($q $switch $status $sessionID);
  
  sub handler{
    my $r = shift;
    Apache->request($r);
  
    init();
    print_header();
    print_status();
  
    return OK;
  }
  
  ### <-- subroutines --> ###
  # all subroutines as before
  
  1;

As you see there are two lines added to the beginning of the code:

  package Test::Cookie;
  use Apache::Constants qw(:common);

The first one declares the package name and the second one imports a
commonly used in Perl handlers status return code symbols.

  use strict;
  use CGI;
  use CGI::Cookie;
  use vars qw($q $switch $status $sessionID);

This code is left unchanged just as before.

  sub handler{
    my $r = shift;
    Apache->request($r);
  
    init();
    print_header();
    print_status();
  
    return OK;
  }

Each content handler (and any handler as well) should be started with
a subroutine by default called handler(). This subroutine is called
when a request's URI starts with I</test/cookie> per our
configuration. Of course you can choose a different name, like
execute() but than you should explicitly use it in the configuration
directives in the following way:

  PerlModule Test::Cookie
  <Location /test/cookie>
    SetHandler perl-script
    PerlHandler Test::Cookie::execute
  </Location>

But we will use the default one. 

The handler() subroutine is just like any other subroutinem, but
generally it has the following structure:

  sub handler{
    my $r = shift;
   
    # the code
  
    return STATUS;
  }

First we get the request object by shifting it from C<@_> and
assigning it to the C<$r> variable.

Second we write the code that does the processing of the request.

Third we return the status of the execution. There are many possible
statuses, the most used are C<OK> and C<DECLINED>, which tells the
server whether they have completed a request phase the handler was
assigned to do or not, and there is a need to call another handler to
complete the processing. C<Apache::Constants> imports these two and
other commonly used status codes.

So in our example all we had to do is to wrap the three calls:

    init();
    print_header();
    print_status();

inside:

  sub handler{
    my $r = shift;
    Apache->request($r);
  
    return OK;
  }

There is one line we didn't discuss it:

    Apache->request($r);

Since we use <CGI.pm>, it relies on the fact that C<$r> was set in the
C<Apache> module. C<Apache::Registry> did that behind the scenes, and
since we don't use C<Apache::Registry> here we have to do that
ourselves.

The only last thing we should do is to add C<1;> at the end of the
module, just like with any Perl module, so C<PerlModule> will not fail
when it tries to load C<Test::Cookie>.

So if we summarise, we took the original script's code and added the
following eight lines:

  package Test::Cookie;
  use Apache::Constants qw(:common);
  
  sub handler{
    my $r = shift;
    Apache->request($r);
  
    return OK;
  }
  1;


and now we have a full fledged Perl Content Handler.

=head2 Converting to use Apache Perl Modules

So now when we have a ready PerlHandler let's convert it to use the
Apache Perl modules, break the backward compatibility, but gain a
better performance since we are going to use Perl modules whose
internals are written in C, therefore we should get a significant
improve in the speed. The sections L<"Benchmarking Apache::Registry
and Perl Content Handler"|performance/Benchmarking_Apache_Registry_an>
and L<"Benchmarking CGI.pm and
Apache::Request"|performance/Benchmarking_CGI_pm_and_Apache_> compare
the three approaches.

What we are going to do is to replace C<CGI.pm> and C<CGI::Cookie>
with C<Apache::Request> and C<Apache::Cookie> respectively.  The two
modules are written in C with XS interface to Perl, which makes your
code much faster if it utilizes any of these modules a lot.
C<Apache::Request> uses an API similar to the one C<CGI> uses, the
same goes for C<Apache::Cookie> and C<CGI::Cookie>. This allows an
easy porting process. Basically we just replace:

  use CGI;
  $q = new CGI;

with:

  use Apache::Request ();
  my $q = Apache::Request->new($r);

and

  use CGI::Cookie ();
  my $cookie = CGI::Cookie->new(...)

with

  use Apache::Cookie ();
  my $cookie = Apache::Cookie->new($r, ...);

This is the new code for C<Test::Cookie2>:

  Test/Cookie2.pm
  --------------
  package Test::Cookie2;
  use Apache::Constants qw(:common);
  
  use strict;
  use Apache::Request;
  use Apache::Cookie ();
  use vars qw($r $q $switch $status $sessionID);
  
  sub handler{
    $r = shift;
  
    init();
    print_header();
    print_status();
  
    return OK;
  }
  
  ### <-- subroutines --> ###
  
  # the init code
  ###########
  sub init{
  
    $q = Apache::Request->new($r);
    $switch = $q->param("switch") ? 1 : 0;
    
        # fetch existing cookies
    my %cookies = Apache::Cookie->fetch;
      # try to retrieve the session ID
    $sessionID = exists $cookies{'sessionID'} 
       ? $cookies{'sessionID'}->value : '';
    
      # 0 = not running, 1 = running
    $status = $sessionID ? 1 : 0;
    
      # switch status if asked to
    $status = ($status+1) % 2 if $switch;
    
    if ($status){
        # preserve sessionID if exists or create a new one
      $sessionID ||= generate_sessionID() if $status;
    } else {
        # delete the sessionID
      $sessionID = '';
    }
    
    
  } # end of sub init
  
  
  #################
  sub print_header{
      # prepare a cooke
    my $c = Apache::Cookie->new
      ($r,
       -name    => 'sessionID',
       -value   => $sessionID,
       -expires => '+1h');
  
      # Add a Set-Cookie header to the outgoing headers table
    $c->bake;
  
    $r->send_http_header('text/html');
    
  } # end of sub print_header
  
  
  # print the current Session status and a form to toggle the status
  #################
  sub print_status{
      
    print qq{<HTML><HEAD><TITLE>Cookie</TITLE></HEAD><BODY>};
    
      # print status
    print "<B>Status:</B> ",
          $status
            ? "Session is running with ID: $sessionID"
            : "No session is running";
    
    
      # change status form
    my $button_label = $status ? "Stop" : "Start";
    print qq{<HR>
         <FORM>
           <INPUT TYPE=SUBMIT NAME=switch VALUE=" $button_label "> 
         </FORM>
           };
    
    print qq{</BODY></HTML>};
  
  } # end of sub print_status
   
  # replace with a real session ID generator
  ########################
  sub generate_sessionID {
    return scalar localtime;
  }
  
  1;

The only other changes are in the print_header() function, where
instead of, passing the cookie code to the C<CGI>'s header() that
returns a proper HTTP header:

    print $q->header
      (-type   => 'text/html',
       -cookie => $c);

we do it in two stages.

    $c->bake;

Adds a C<Set-Cookie> header to the outgoing headers table, and:

    $r->send_http_header('text/html');

sends out the header itself. We have also eliminated:

    Apache->request($r);

since we don't rely on C<CGI.pm> any more and in our case we don't
need it.

The rest of the code is unchanged.

Of course we add the following snippet to I<httpd.conf>:

  PerlModule Test::Cookie2
  <Location /test/cookie2>
    SetHandler perl-script
    PerlHandler Test::Cookie2
  </Location>

So now the magic URI that will trigger the above code execution will
be the one starting with I</test/cookie2> and at this time we save the
code at I</home/httpd/perl/Test/Cookie2.pm> since we have called this
package as C<Test::Cookie2>.


=head2 Conclusion

If your took care to write the original plain CGI script's code in a
clean and modular way, you can see that the transition is a very
simple one and doesn't take a lot of effort and almost no code was
modified.


______________________________________________________________________
Stas Bekman             | JAm_pH    --    Just Another mod_perl Hacker
http://stason.org/      | mod_perl Guide  http://perl.apache.org/guide 
mailto:[EMAIL PROTECTED]  | http://perl.org    http://stason.org/TULARC/
http://singlesheaven.com| http://perlmonth.com http://sourcegarden.org
----------------------------------------------------------------------

Reply via email to