home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / tsw / TSW_3.4.0.exe / Apache2 / perl / ApacheHandler2.pm < prev    next >
Encoding:
Text File  |  2004-01-30  |  29.6 KB  |  1,104 lines

  1. # Copyright (c) 1998-2003 by Jonathan Swartz. All rights reserved.
  2. # This program is free software; you can redistribute it and/or modify it
  3. # under the same terms as Perl itself.
  4.  
  5. use strict;
  6.  
  7. #----------------------------------------------------------------------
  8. #
  9. # APACHE-SPECIFIC REQUEST OBJECT
  10. #
  11. package HTML::Mason::Request::ApacheHandler2;
  12.  
  13. use Apache::Const -compile => qw( REDIRECT );
  14.  
  15. use HTML::Mason::Request;
  16. use Class::Container;
  17. use Params::Validate qw(BOOLEAN);
  18. Params::Validate::validation_options( on_fail => sub { param_error( join '', @_ ) } );
  19.  
  20. use base qw(HTML::Mason::Request);
  21.  
  22. use HTML::Mason::Exceptions( abbr => [qw(param_error error)] );
  23.  
  24. use constant OK         => 0;
  25. use constant DECLINED   => -1;
  26. use constant NOT_FOUND  => 404;
  27.  
  28. if ( $mod_perl::VERSION < 1.99 )
  29. {
  30.     error "you must use mod_perl 2 (version >= 1.99)", __PACKAGE__, "\n";
  31. }
  32.  
  33. BEGIN
  34. {
  35.     __PACKAGE__->valid_params
  36.     ( ah         => { isa => 'HTML::Mason::ApacheHandler2',
  37.               descr => 'An ApacheHandler2 to handle web requests',
  38.               public => 0 },
  39.  
  40.       apache_req => { isa => 'Apache::RequestRec', default => undef,
  41.               descr => "An Apache request object",
  42.               public => 0 },
  43.  
  44.       cgi_object => { isa => 'CGI',    default => undef,
  45.               descr => "A CGI.pm request object",
  46.               public => 0 },
  47.  
  48.       auto_send_headers => { parse => 'boolean', type => BOOLEAN, default => 1,
  49.                  descr => "Whether HTTP headers should be auto-generated" },
  50.     );
  51. }
  52.  
  53. use HTML::Mason::MethodMaker
  54.     ( read_write => [ map { [ $_ => __PACKAGE__->validation_spec->{$_} ] }
  55.               qw( ah apache_req auto_send_headers ) ] );
  56.  
  57. # A hack for subrequests
  58. sub _properties { qw(ah apache_req), shift->SUPER::_properties }
  59.  
  60. sub new
  61. {
  62.     my $class = shift;
  63.     my $self = $class->SUPER::new(@_);  # Magic!
  64.  
  65.     unless ($self->apache_req or $self->cgi_object)
  66.     {
  67.     param_error __PACKAGE__ . "->new: must specify 'apache_req' or 'cgi_object' parameter";
  68.     }
  69.  
  70.     return $self;
  71. }
  72.  
  73. # Override flush_buffer to also call $r->rflush
  74. sub flush_buffer
  75. {
  76.     my ($self) = @_;
  77.     $self->SUPER::flush_buffer;
  78.     $self->apache_req->rflush;
  79. }
  80.  
  81. sub cgi_object
  82. {
  83.     my ($self) = @_;
  84.  
  85.     error "Can't call cgi_object() unless 'args_method' is set to CGI.\n"
  86.     unless $self->ah->args_method eq 'CGI';
  87.  
  88.     if (defined($_[1])) {
  89.     $self->{cgi_object} = $_[1];
  90.     } else {
  91.     # We may not have created a CGI object if, say, request was a
  92.     # GET with no query string. Create one on the fly if necessary.
  93.     $self->{cgi_object} ||= CGI->new('');
  94.     }
  95.  
  96.     return $self->{cgi_object};
  97. }
  98.  
  99. #
  100. # Override this method to return NOT_FOUND when we get a
  101. # TopLevelNotFound exception. In case of POST we must trick
  102. # Apache into not reading POST content again. Wish there were
  103. # a more standardized way to do this...
  104. #
  105. sub exec
  106. {
  107.     my $self = shift;
  108.     my $r = $self->apache_req;
  109.     my $retval;
  110.  
  111.     if ( $self->is_subrequest )
  112.     {
  113.         # no need to go through all the rigamorale below for
  114.         # subrequests, and it may even break things to do so, since
  115.         # $r's print should only be redefined once.
  116.     eval { $retval = $self->SUPER::exec(@_) };
  117.     }
  118.     else
  119.     {
  120.         # ack, this has to be done at runtime to account for the fact
  121.         # that Apache::Filter changes $r's class and implements its
  122.         # own print() method.
  123.         my $real_apache_print = $r->can('print');
  124.  
  125.     # Remap $r->print to Mason's $m->print while executing
  126.     # request, but just for this $r, in case user does an internal
  127.     # redirect or apache subrequest.
  128.     local $^W = 0;
  129.     no strict 'refs';
  130.  
  131.         my $req_class = ref $r;
  132.     local *{"$req_class\::print"} = sub {
  133.         my $local_r = shift;
  134.         return $self->print(@_) if $local_r eq $r;
  135.         return $local_r->$real_apache_print(@_);
  136.     };
  137.     eval { $retval = $self->SUPER::exec(@_) };
  138.     }
  139.  
  140.     if ($@) {
  141.     if (isa_mason_exception($@, 'TopLevelNotFound')) {
  142.         # Log the error the same way that Apache does (taken from default_handler in http_core.c)
  143.         $r->log_error("[Mason] File does not exist: ", $r->filename . ($r->path_info ? $r->path_info : ""));
  144.         return $self->ah->return_not_found($r);
  145.     } else {
  146.         rethrow_exception $@;
  147.     }
  148.     }
  149.  
  150.     # On a success code, send headers if they have not been sent and
  151.     # if we are the top-level request. Since the out_method sends
  152.     # headers, this will typically only apply after $m->abort.
  153.     # On an error code, leave it to Apache to send the headers.
  154.     # not needed in mod_per2 (??)
  155.     if (!$self->is_subrequest
  156.     and $self->auto_send_headers
  157.     and !HTML::Mason::ApacheHandler2::http_header_sent($r)
  158.     and (!$retval or $retval==200)) {
  159.     #$r->send_http_header();
  160.     }
  161.  
  162.     return defined($retval) ? $retval : OK;
  163. }
  164.  
  165. #
  166. # Override this method to always die when top level component is not found,
  167. # so we can return NOT_FOUND.
  168. #
  169. sub _handle_error
  170. {
  171.     my ($self, $err) = @_;
  172.  
  173.     if (isa_mason_exception($err, 'TopLevelNotFound')) {
  174.     rethrow_exception $err;
  175.     } else {
  176.         if ( $self->error_format eq 'html' ) {
  177.             $self->apache_req->content_type('text/html');
  178.         }
  179.     $self->SUPER::_handle_error($err);
  180.     }
  181. }
  182.  
  183. sub redirect
  184. {
  185.     my ($self, $url, $status) = @_;
  186.     my $r = $self->apache_req;
  187.  
  188.     $self->clear_buffer;
  189.     $r->method('GET');
  190.     $r->headers_in->unset('Content-length');
  191.     $r->err_headers_out->{ Location } = $url;
  192.     $self->abort($status || Apache::REDIRECT);
  193. }
  194.  
  195. #----------------------------------------------------------------------
  196. #
  197. # APACHE-SPECIFIC FILE RESOLVER OBJECT
  198. #
  199. package HTML::Mason::Resolver::File::ApacheHandler2;
  200.  
  201. use strict;
  202.  
  203. use HTML::Mason::Tools qw(paths_eq);
  204.  
  205. use HTML::Mason::Resolver::File;
  206. use base qw(HTML::Mason::Resolver::File);
  207. use Params::Validate qw(SCALAR ARRAYREF);
  208.  
  209. BEGIN
  210. {
  211.     __PACKAGE__->valid_params
  212.     (
  213.      comp_root =>   # This is optional in superclass, but required for us.
  214.      { parse => 'list',
  215.        type => SCALAR|ARRAYREF,
  216.        descr => "A string or array of arrays indicating the search path for component calls" },
  217.     );
  218. }
  219.  
  220. #
  221. # Given an apache request object, return the associated component
  222. # path or undef if none exists. This is called for top-level web
  223. # requests that resolve to a particular file.
  224. #
  225. sub apache_request_to_comp_path {
  226.     my ($self, $r) = @_;
  227.  
  228.     my $file = $r->filename;
  229.     $file .= $r->path_info unless -f $file;
  230.  
  231.     # Clear up any weirdness here so that paths_eq compares two
  232.     # 'canonical' paths (canonpath is called on comp roots when
  233.     # resolver object is created.  Seems to be needed on Win32 (see
  234.     # bug #356).
  235.     $file = File::Spec->canonpath($file);
  236.  
  237.     foreach my $root (map $_->[1], $self->comp_root_array) {
  238.     if (paths_eq($root, substr($file, 0, length($root)))) {
  239.         my $path = substr($file, length $root);
  240.             $path = length $path ? join '/', File::Spec->splitdir($path) : '/';
  241.             chop $path if $path ne '/' && substr($path, -1) eq '/';
  242.  
  243.             return $path;
  244.     }
  245.     }
  246.     return undef;
  247. }
  248.  
  249.  
  250. #----------------------------------------------------------------------
  251. #
  252. # APACHEHANDLER OBJECT
  253. #
  254. package HTML::Mason::ApacheHandler2;
  255.  
  256. use File::Path;
  257. use File::Spec;
  258. use HTML::Mason::Exceptions( abbr => [qw(param_error system_error error)] );
  259. use HTML::Mason::Interp;
  260. use HTML::Mason::Tools qw( load_pkg );
  261. use HTML::Mason::Utils;
  262. use Params::Validate qw(:all);
  263. Params::Validate::validation_options( on_fail => sub { param_error( join '', @_ ) } );
  264.  
  265. use Apache::Const -compile => qw( OK DECLINED NOT_FOUND );
  266. use APR;
  267. use Apache::ServerUtil;
  268.  
  269. # Require a mod_perl 2
  270. use mod_perl 1.99;
  271.  
  272. if ( $mod_perl::VERSION < 1.99 )
  273. {
  274.     error "you must use mod_perl 2 (version >= 1.99)", __PACKAGE__, "\n";
  275. }
  276.  
  277. use vars qw($VERSION);
  278.  
  279. $VERSION = 0.01;
  280.  
  281. use Class::Container;
  282. use base qw(Class::Container);
  283.  
  284. BEGIN
  285. {
  286.     __PACKAGE__->valid_params
  287.     (
  288.      apache_status_title =>
  289.          { parse => 'string', type => SCALAR, default => 'HTML::Mason status',
  290.            descr => "The title of the Apache::Status page" },
  291.  
  292.      args_method =>
  293.          { parse => 'string',  type => SCALAR,       default => 'mod_perl',
  294.            regex => qr/^(?:CGI|mod_perl)$/,
  295.            descr => "Whether to use CGI.pm or Apache::Request for parsing the incoming HTTP request",
  296.          },
  297.  
  298.      decline_dirs =>
  299.          { parse => 'boolean', type => BOOLEAN, default => 1,
  300.            descr => "Whether Mason should decline to handle requests for directories" },
  301.  
  302.      # the only required param
  303.      interp =>
  304.          { isa => 'HTML::Mason::Interp',
  305.            descr => "A Mason interpreter for processing components" },
  306.     );
  307.  
  308.     __PACKAGE__->contained_objects
  309.     (
  310.      interp =>
  311.          { class => 'HTML::Mason::Interp',
  312.            descr => 'The interp class coordinates multiple objects to handle request execution'
  313.          },
  314.     );
  315. }
  316.  
  317. use HTML::Mason::MethodMaker
  318.     ( read_only  => [ 'args_method' ],
  319.       read_write => [ map { [ $_ => __PACKAGE__->validation_spec->{$_} ] }
  320.               qw( apache_status_title
  321.               decline_dirs
  322.               interp ) ]
  323.     );
  324.  
  325. my ($STARTED);
  326.  
  327. # hack to let the make_params_pod.pl script work
  328. __PACKAGE__->_startup() if Apache->server;
  329. sub _startup
  330. {
  331.     my $pack = shift;
  332.     return if $STARTED++; # Allows a subclass to call us, without running twice
  333.  
  334.     if ( my $args_method = $pack->_get_string_param('MasonArgsMethod') )
  335.     {
  336.     if ($args_method eq 'CGI')
  337.     {
  338.         require CGI unless defined $CGI::VERSION;
  339.     }
  340.     elsif ($args_method eq 'mod_perl')
  341.     {
  342.         require Apache::Request unless defined $Apache::Request::VERSION;
  343.     }
  344.     }
  345. }
  346.  
  347. use constant
  348.     HAS_TABLE_API => $mod_perl::VERSION >= 1.99;
  349.  
  350. my %AH_BY_CONFIG;
  351. sub make_ah
  352. {
  353.     my ($package, $r) = @_;
  354.  
  355.     my $config = $r->dir_config;
  356.  
  357.     #
  358.     # If the user has virtual hosts, each with a different document
  359.     # root, then we will have to be called from the handler method.
  360.     # This means we have an active request.  In order to distinguish
  361.     # between virtual hosts with identical config directives that have
  362.     # no comp root defined (meaning they expect to use the default
  363.     # comp root), we append the document root for the current request
  364.     # to the key.
  365.     #
  366.     my $key =
  367.         ( join $;,
  368.           $r->document_root,
  369.           map { $_, HAS_TABLE_API ? sort $config->get($_) : $config->{$_} }
  370.           grep { /^Mason/ }
  371.           keys %$config
  372.         );
  373.  
  374.     return $AH_BY_CONFIG{$key} if exists $AH_BY_CONFIG{$key};
  375.  
  376.     my %p = $package->_get_mason_params($r);
  377.  
  378.     # can't use hash_list for this one because it's _either_ a string
  379.     # or a hash_list
  380.     if (exists $p{comp_root}) {
  381.     if (@{$p{comp_root}} == 1 && $p{comp_root}->[0] !~ /=>/) {
  382.         $p{comp_root} = $p{comp_root}[0];  # Convert to a simple string
  383.     } else {
  384.             my @roots;
  385.         foreach my $root (@{$p{comp_root}}) {
  386.         $root = [ split /\s*=>\s*/, $root, 2 ];
  387.         param_error "Configuration parameter MasonCompRoot must be either ".
  388.                             "a single string value or multiple key/value pairs ".
  389.                             "like 'foo => /home/mason/foo'.  Invalid parameter:\n$root"
  390.             unless defined $root->[1];
  391.  
  392.                 push @roots, $root;
  393.         }
  394.  
  395.             $p{comp_root} = \@roots;
  396.     }
  397.     }
  398.  
  399.     my $ah = $package->new(%p, $r);
  400.     $AH_BY_CONFIG{$key} = $ah if $key;
  401.  
  402.     return $ah;
  403. }
  404.  
  405. # The following routines handle getting information from $r->dir_config
  406.  
  407. sub calm_form {
  408.     # Transform from StudlyCaps to name_like_this
  409.     my ($self, $string) = @_;
  410.     $string =~ s/^Mason//;
  411.     $string =~ s/(^|.)([A-Z])/$1 ? "$1\L_$2" : "\L$2"/ge;
  412.     return $string;
  413. }
  414.  
  415. sub studly_form {
  416.     # Transform from name_like_this to StudlyCaps
  417.     my ($self, $string) = @_;
  418.     $string =~ s/(?:^|_)(\w)/\U$1/g;
  419.     return $string;
  420. }
  421.  
  422. sub _get_mason_params
  423. {
  424.     my $self = shift;
  425.     my $r = shift;
  426.  
  427.     my $config = $r ? $r->dir_config : Apache->server->dir_config;
  428.  
  429.     # Get all params starting with 'Mason'
  430.     my %candidates;
  431.  
  432.     foreach my $studly ( keys %$config )
  433.     {
  434.     (my $calm = $studly) =~ s/^Mason// or next;
  435.     $calm = $self->calm_form($calm);
  436.  
  437.     $candidates{$calm} = $config->{$studly};
  438.     }
  439.  
  440.     return unless %candidates;
  441.  
  442.     #
  443.     # We will accumulate all the string versions of the keys and
  444.     # values here for later use.
  445.     #
  446.     return ( map { $_ =>
  447.                    scalar $self->get_param( $_, \%candidates, $config, $r )
  448.                  }
  449.              keys %candidates );
  450. }
  451.  
  452. sub get_param {
  453.     # Gets a single config item from dir_config.
  454.  
  455.     my ($self, $key, $candidates, $config, $r) = @_;
  456.  
  457.     $key = $self->calm_form($key);
  458.  
  459.     my $spec = $self->allowed_params( $candidates || {} )->{$key}
  460.         or error "Unknown config item '$key'";
  461.  
  462.     # Guess the default parse type from the Params::Validate validation spec
  463.     my $type = ($spec->{parse} or
  464.         $spec->{type} & ARRAYREF ? 'list' :
  465.         $spec->{type} & SCALAR   ? 'string' :
  466.         $spec->{type} & CODEREF  ? 'code' :
  467.         undef)
  468.         or error "Unknown parse type for config item '$key'";
  469.  
  470.     my $method = "_get_${type}_param";
  471.     return $self->$method('Mason'.$self->studly_form($key), $config, $r);
  472. }
  473.  
  474. sub _get_string_param
  475. {
  476.     my $self = shift;
  477.     return scalar $self->_get_val(@_);
  478. }
  479.  
  480. sub _get_boolean_param
  481. {
  482.     my $self = shift;
  483.     return scalar $self->_get_val(@_);
  484. }
  485.  
  486. sub _get_code_param
  487. {
  488.     my $self = shift;
  489.     my $p = $_[0];
  490.     my $val = $self->_get_val(@_);
  491.  
  492.     return unless $val;
  493.  
  494.     my $sub_ref = eval $val;
  495.  
  496.     param_error "Configuration parameter '$p' is not valid perl:\n$@\n"
  497.     if $@;
  498.  
  499.     return $sub_ref;
  500. }
  501.  
  502. sub _get_list_param
  503. {
  504.     my $self = shift;
  505.     my @val = $self->_get_val(@_);
  506.     if (@val == 1 && ! defined $val[0])
  507.     {
  508.     @val = ();
  509.     }
  510.  
  511.     return \@val;
  512. }
  513.  
  514. sub _get_hash_list_param
  515. {
  516.     my $self = shift;
  517.     my @val = $self->_get_val(@_);
  518.     if (@val == 1 && ! defined $val[0])
  519.     {
  520.         return {};
  521.     }
  522.  
  523.     my %hash;
  524.     foreach my $pair (@val)
  525.     {
  526.         my ($key, $val) = split /\s*=>\s*/, $pair, 2;
  527.         param_error "Configuration parameter $_[0] must be a key/value pair ".
  528.                     qq|like "foo => 'bar'".  Invalid parameter:\n$pair|
  529.                 unless defined $key && defined $val;
  530.  
  531.         $hash{$key} = $val;
  532.     }
  533.  
  534.     return \%hash;
  535. }
  536.  
  537. sub _get_val
  538. {
  539.     my ($self, $p, $config, $r) = @_;
  540.  
  541.     my @val;
  542.     if (wantarray || !$config)
  543.     {
  544.         if ($config)
  545.         {
  546.             my $c = $r ? $r : Apache->server;
  547.             @val = HAS_TABLE_API ? $config->get($p) : $config->{$p};
  548.         }
  549.         else
  550.         {
  551.             my $c = $r ? $r : Apache->server;
  552.             @val = HAS_TABLE_API ? $c->dir_config->get($p) : $c->dir_config($p);
  553.         }
  554.     }
  555.     else
  556.     {
  557.         @val = exists $config->{$p} ? $config->{$p} : ();
  558.     }
  559.  
  560.     param_error "Only a single value is allowed for configuration parameter '$p'\n"
  561.     if @val > 1 && ! wantarray;
  562.  
  563.     return wantarray ? @val : $val[0];
  564. }
  565.  
  566. sub new
  567. {
  568.     my $class = shift;
  569.  
  570.     # Get $r off end of params if its there
  571.     my $r;
  572.     $r = pop() if @_ % 2;
  573.     my %params = @_;
  574.  
  575.     my %defaults;
  576.     $defaults{request_class}  = 'HTML::Mason::Request::ApacheHandler2'
  577.         unless exists $params{request};
  578.     $defaults{resolver_class} = 'HTML::Mason::Resolver::File::ApacheHandler2'
  579.         unless exists $params{resolver};
  580.  
  581.     my $allowed_params = $class->allowed_params(%defaults, %params);
  582.  
  583.      if ( exists $allowed_params->{comp_root} and
  584.      my $req = $r )  # DocumentRoot is only available inside requests
  585.     {
  586.     $defaults{comp_root} = $req->document_root;
  587.     }
  588. =comment
  589.    if ( exists $allowed_params->{comp_root} ) {
  590.     if ( my $req = $r ) {
  591.     # DocumentRoot is only available inside requests
  592.         $defaults{comp_root} = $req->document_root;
  593.     } else {
  594.         $defaults{comp_root} =
  595.         Apache->server->dir_config( '_MasonDefaultDocumentRoot' );
  596.     }
  597.     }
  598. =cut
  599.  
  600.     if (exists $allowed_params->{data_dir} and not exists $params{data_dir})
  601.     {
  602.     # constructs path to <server root>/mason
  603.     my $def = $defaults{data_dir} = Apache->server->server_root_relative('mason');
  604.     param_error "Default data_dir (MasonDataDir) '$def' must be an absolute path"
  605.         unless File::Spec->file_name_is_absolute($def);
  606.       
  607.     my @levels = File::Spec->splitdir($def);
  608.     param_error "Default data_dir (MasonDataDir) '$def' must be more than two levels deep (or must be set explicitly)"
  609.         if @levels <= 3;
  610.     }
  611.  
  612.     # Set default error_format based on error_mode
  613.     if (exists($params{error_mode}) and $params{error_mode} eq 'fatal') {
  614.     $defaults{error_format} = 'line';
  615.     } else {
  616.     $defaults{error_mode} = 'output';
  617.     $defaults{error_format} = 'html';
  618.     }
  619.  
  620.     # Push $r onto default allow_globals
  621.     if (exists $allowed_params->{allow_globals}) {
  622.     if ( $params{allow_globals} ) {
  623.         push @{ $params{allow_globals} }, '$r';
  624.     } else {
  625.         $defaults{allow_globals} = ['$r'];
  626.     }
  627.     }
  628.  
  629.     my $self = eval { $class->SUPER::new(%defaults, %params) };
  630.  
  631.     # We catch & throw this exception just to provide a better error message
  632.     if ( $@ && isa_mason_exception( $@, 'Params' ) && $@->message =~ /comp_root/ )
  633.     {
  634.     param_error "No comp_root specified and cannot determine DocumentRoot." .
  635.                     " Please provide comp_root explicitly.";
  636.     }
  637.     rethrow_exception $@;
  638.  
  639.     unless ( $self->interp->resolver->can('apache_request_to_comp_path') )
  640.     {
  641.     error "The resolver class your Interp object uses does not implement " .
  642.               "the 'apache_request_to_comp_path' method.  This means that ApacheHandler2 " .
  643.               "cannot resolve requests.  Are you using a handler.pl file created ".
  644.           "before version 1.10?  Please see the handler.pl sample " .
  645.               "that comes with the latest version of Mason.";
  646.     }
  647.  
  648.     # If we're running as superuser, change file ownership to http user & group
  649.     if (!($> || $<) && $self->interp->files_written)
  650.     {
  651.     chown getpwnam( Apache->server->dir_config( '_MasonUser' ) ),
  652.     getgrnam( Apache->server->dir_config( '_MasonGroup' ) ),
  653.     $self->interp->files_written
  654.         or system_error( "Can't change ownership of files written by interp object: $!\n" );
  655.     }
  656.  
  657.     $self->_initialize;
  658.     return $self;
  659. }
  660.  
  661. # Register with Apache::Status at module startup.  Will get replaced
  662. # with a more informative status once an interpreter has been created.
  663. my $status_name = 'mason0001';
  664. if ( load_pkg('Apache::Status') )
  665. {
  666.     Apache::Status->menu_item
  667.     ($status_name => __PACKAGE__->allowed_params->{apache_status_title}{default},
  668.          sub { ["<b>(no interpreters created in this child yet)</b>"] });
  669. }
  670.  
  671.  
  672. sub _initialize {
  673.     my ($self) = @_;
  674.  
  675.     if ($self->args_method eq 'mod_perl') {
  676.     unless (defined $Apache::Request::VERSION) {
  677.         warn "Loading Apache::Request at runtime.  You could " .
  678.                  "increase shared memory between Apache processes by ".
  679.                  "preloading it in your httpd.conf or handler.pl file\n";
  680.         require Apache::Request;
  681.     }
  682.     } else {
  683.     unless (defined $CGI::VERSION) {
  684.         warn "Loading CGI at runtime.  You could increase shared ".
  685.                  "memory between Apache processes by preloading it in ".
  686.                  "your httpd.conf or handler.pl file\n";
  687.  
  688.         require CGI;
  689.     }
  690.     }
  691.  
  692.     # Add an HTML::Mason menu item to the /perl-status page.
  693.     if (defined $Apache::Status::VERSION) {
  694.     # A closure, carries a reference to $self
  695.     my $statsub = sub {
  696.         my ($r,$q) = @_; # request and CGI objects
  697.         return [] if !defined($r);
  698.  
  699.         if ($r->path_info and $r->path_info =~ /expire_code_cache=(.*)/) {
  700.         $self->interp->delete_from_code_cache($1);
  701.         }
  702.  
  703.         return ["<center><h2>" . $self->apache_status_title . "</h2></center>" ,
  704.             $self->status_as_html
  705.             (apache_req => $r),
  706.             $self->interp->status_as_html
  707.             (ah => $self, $r) ];
  708.     };
  709.     local $^W = 0; # to avoid subroutine redefined warnings
  710.     Apache::Status->menu_item($status_name, $self->apache_status_title, $statsub);
  711.     }
  712.  
  713.     my $interp = $self->interp;
  714.  
  715.     #
  716.     # Allow global $r in components
  717.     #
  718.     $interp->compiler->add_allowed_globals('$r')
  719.     if $interp->compiler->can('add_allowed_globals');
  720. }
  721.  
  722. # Generate HTML that describes ApacheHandler2's current status.
  723. # This is used in things like Apache::Status reports.
  724.  
  725. sub status_as_html {
  726.     my ($self, %p) = @_;
  727.  
  728.     # Should I be scared about this?  =)
  729.  
  730.     my $comp_source = <<'EOF';
  731. <h3>ApacheHandler2 properties:</h3>
  732. <blockquote>
  733.  <tt>
  734. <table width="75%">
  735. <%perl>
  736. foreach my $property (sort keys %$ah) {
  737.     my $val = $ah->{$property};
  738.     my $default = ( defined $val && defined $valid{$property}{default} && $val eq $valid{$property}{default} ) || ( ! defined $val && exists $valid{$property}{default} && ! defined $valid{$property}{default} );
  739.  
  740.     my $display = $val;
  741.     if (ref $val) {
  742.         $display = '<font color="darkred">';
  743.         # only object can ->can, others die
  744.         my $is_object = eval { $val->can('anything'); 1 };
  745.         if ($is_object) {
  746.             $display .= ref $val . ' object';
  747.         } else {
  748.             if (UNIVERSAL::isa($val, 'ARRAY')) {
  749.                 $display .= 'ARRAY reference - [ ';
  750.                 $display .= join ', ', @$val;
  751.                 $display .= '] ';
  752.             } elsif (UNIVERSAL::isa($val, 'HASH')) {
  753.                 $display .= 'HASH reference - { ';
  754.                 my @pairs;
  755.                 while (my ($k, $v) = each %$val) {
  756.                    push @pairs, "$k => $v";
  757.                 }
  758.                 $display .= join ', ', @pairs;
  759.                 $display .= ' }';
  760.             } else {
  761.                 $display = ref $val . ' reference';
  762.             }
  763.         }
  764.         $display .= '</font>';
  765.     }
  766.  
  767.     defined $display && $display =~ s,([\x00-\x1F]),'<font color="purple">control-' . chr( ord('A') + ord($1) - 1 ) . '</font>',eg; # does this work for non-ASCII?
  768. </%perl>
  769.  <tr valign="top" cellspacing="10">
  770.   <td>
  771.     <% $property | h %>
  772.   </td>
  773.   <td>
  774.    <% defined $display ? $display : '<i>undef</i>' %>
  775.    <% $default ? '<font color=green>(default)</font>' : '' %>
  776.   </td>
  777.  </tr>
  778. % }
  779. </table>
  780.   </tt>
  781. </blockquote>
  782.  
  783. <%args>
  784.  $ah       # The ApacheHandler2 we'll elucidate
  785.  %valid    # Contains default values for member data
  786. </%args>
  787. EOF
  788.  
  789.     my $interp = $self->interp;
  790.     my $comp = $interp->make_component(comp_source => $comp_source);
  791.     my $out;
  792.  
  793.     $self->interp->make_request
  794.     ( comp => $comp,
  795.       args => [ah => $self, valid => $interp->allowed_params],
  796.       ah => $self,
  797.       apache_req => $p{apache_req},
  798.       out_method => \$out,
  799.     )->exec;
  800.  
  801.     return $out;
  802. }
  803.  
  804. sub handle_request
  805. {
  806.     my ($self, $r) = @_;
  807.  
  808.     my $req = $self->prepare_request($r);
  809.     return $req unless ref($req);
  810.  
  811.     return $req->exec;
  812. }
  813.  
  814. my $do_filter = sub { $_[0]->filter_register };
  815. my $no_filter = sub { $_[0] };
  816. sub prepare_request
  817. {
  818.     my $self = shift;
  819.  
  820.     my $r_sub = lc $_[0]->dir_config('Filter') eq 'on' ? $do_filter : $no_filter;
  821.  
  822.     # This gets the proper request object all in one fell swoop.  We
  823.     # don't want to copy it because if we do something like assign an
  824.     # Apache::Request object to a variable currently containing a
  825.     # plain Apache object, we leak memory.  This means we'd have to
  826.     # use multiple variables to avoid this, which is annoying.
  827.     my $r =
  828.         $r_sub->( $self->args_method eq 'mod_perl' ?
  829.                   Apache::Request->new( $_[0] ) :
  830.                   $_[0]
  831.                 );
  832.  
  833.     my $interp = $self->interp;
  834.  
  835.     #
  836.     # If filename is a directory, then either decline or simply reset
  837.     # the content type, depending on the value of decline_dirs.
  838.     #
  839.     # ** We should be able to use $r->finfo here, but finfo is broken
  840.     # in some versions of mod_perl (e.g. see Shane Adams message on
  841.     # mod_perl list on 9/10/00)
  842.     #
  843.     my $is_dir = -d $r->filename;
  844.     my $is_file = -f _;
  845.  
  846.     if ($is_dir) {
  847.     if ($self->decline_dirs) {
  848.         return Apache::DECLINED;
  849.     } else {
  850.         $r->content_type(undef);
  851.     }
  852.     }
  853.  
  854.     #
  855.     # Compute the component path via the resolver. Return NOT_FOUND on failure.
  856.     #
  857.     my $comp_path = $interp->resolver->apache_request_to_comp_path($r);
  858.     unless ($comp_path) {
  859.     #
  860.     # Append path_info if filename does not represent an existing file
  861.     # (mainly for dhandlers).
  862.     #
  863.     my $pathname = $r->filename;
  864.     $pathname .= $r->path_info unless $is_file;
  865.  
  866.     warn "[Mason] Cannot resolve file to component: " .
  867.              "$pathname (is file outside component root?)";
  868.     return $self->return_not_found($r);
  869.     }
  870.  
  871.     my ($args, undef, $cgi_object) = $self->request_args($r);
  872.  
  873.     #
  874.     # Set up interpreter global variables.
  875.     #
  876.     $interp->set_global( r => $r );
  877.  
  878.     # If someone is using a custom request class that doesn't accept
  879.     # 'ah' and 'apache_req' that's their problem.
  880.     #
  881.     my $request = eval {
  882.         $interp->make_request( comp => $comp_path,
  883.                                args => [%$args],
  884.                                ah => $self,
  885.                                apache_req => $r,
  886.                              );
  887.     };
  888.     if (my $err = $@) {
  889.         # Mason doesn't currently throw any exceptions in the above, but some
  890.         # subclasses might. So be sure to handle them appropriately. We
  891.         # rethrow everything but TopLevelNotFound, Abort, and Decline errors.
  892.     if ( isa_mason_exception($err, 'TopLevelNotFound') ) {
  893.             # Return a 404.
  894.         $r->log_error("[Mason] File does not exist: ", $r->filename .
  895.                           ($r->path_info || ""));
  896.         return $self->return_not_found($r);
  897.     }
  898.         # Abort or decline.
  899.     my $retval = isa_mason_exception($err, 'Abort')   ? $err->aborted_value  :
  900.              isa_mason_exception($err, 'Decline') ? $err->declined_value :
  901.              rethrow_exception $err;
  902.     # not needed in mod_perl2 (??)
  903.         #$r->send_http_header unless $retval and $retval != 200;
  904.     return $retval;
  905.     }
  906.  
  907.     my $final_output_method = ($r->method eq 'HEAD' ?
  908.                    sub {} :
  909.                    $r->can('print'));
  910.  
  911.     # Craft the request's out method to handle http headers, content
  912.     # length, and HEAD requests.
  913.     my $sent_headers = 0;
  914.     my $out_method = sub {
  915.  
  916.     # Send headers if they have not been sent by us or by user.
  917.         # We use instance here because if we store $request we get a
  918.         # circular reference and a big memory leak.
  919.     if (!$sent_headers and HTML::Mason::Request->instance->auto_send_headers) {
  920.         # not needed in mod_perl2 (??) - just set content type
  921.         #unless (http_header_sent($r)) {
  922.         #   $r->send_http_header();
  923.         #}
  924.         $sent_headers = 1;
  925.     }
  926.  
  927.     # We could perhaps install a new, faster out_method here that
  928.     # wouldn't have to keep checking whether headers have been
  929.     # sent and what the $r->method is.  That would require
  930.     # additions to the Request interface, though.
  931.  
  932.     
  933.     # Call $r->print (using the real Apache method, not our
  934.     # overriden method).
  935.     $r->$final_output_method(grep {defined} @_);
  936.     };
  937.  
  938.     $request->out_method($out_method);
  939.  
  940.     $request->cgi_object($cgi_object) if $cgi_object;
  941.  
  942.     return $request;
  943. }
  944.  
  945. sub request_args
  946. {
  947.     my ($self, $r) = @_;
  948.  
  949.     #
  950.     # Get arguments from Apache::Request or CGI.
  951.     #
  952.     my ($args, $cgi_object);
  953.     if ($self->args_method eq 'mod_perl') {
  954.     $args = $self->_mod_perl_args($r);
  955.     } else {
  956.     $cgi_object = CGI->new;
  957.     $args = $self->_cgi_args($r, $cgi_object);
  958.     }
  959.  
  960.     # we return $r solely for backwards compatibility
  961.     return ($args, $r, $cgi_object);
  962. }
  963.  
  964. #
  965. # Get $args hashref via CGI package
  966. #
  967. sub _cgi_args
  968. {
  969.     my ($self, $r, $q) = @_;
  970.  
  971.     # For optimization, don't bother creating a CGI object if request
  972.     # is a GET with no query string
  973.     return {} if $r->method eq 'GET' && !scalar($r->args);
  974.  
  975.     return HTML::Mason::Utils::cgi_request_args($q, $r->method);
  976. }
  977.  
  978. #
  979. # Get $args hashref via Apache::Request package.
  980. #
  981. sub _mod_perl_args
  982. {
  983.     my ($self, $apr, $request) = @_;
  984.  
  985.     my %args;
  986.     foreach my $key ( $apr->param ) {
  987.     my @values = $apr->param($key);
  988.     $args{$key} = @values == 1 ? $values[0] : \@values;
  989.     }
  990.  
  991.     return \%args;
  992. }
  993.  
  994. #
  995. # Determines whether the http header has been sent.
  996. #
  997. sub http_header_sent { shift->headers_out->{"Content-type"} }
  998.  
  999. # Utility function to prepare $r before returning NOT_FOUND.
  1000. sub return_not_found
  1001. {
  1002.     my ($self, $r) = @_;
  1003.  
  1004.     if ($r->method eq 'POST') {
  1005.     $r->method('GET');
  1006.     $r->headers_in->unset('Content-length');
  1007.     }
  1008.     return Apache::NOT_FOUND;
  1009. }
  1010.  
  1011. #
  1012. # PerlHandler HTML::Mason::ApacheHandler2
  1013. #
  1014. BEGIN
  1015. {
  1016.     # A mod_perl2 method handler
  1017.     my $handler_code = <<'EOF';
  1018. sub handler : method
  1019. {
  1020.     my ($package, $r) = @_;
  1021.  
  1022.     my $ah;
  1023.     $ah ||= $package->make_ah($r);
  1024.  
  1025.     return $ah->handle_request($r);
  1026. }
  1027. EOF
  1028.     eval $handler_code;
  1029.     rethrow_exception $@;
  1030. }
  1031.  
  1032. 1;
  1033.  
  1034. __END__
  1035.  
  1036. =head1 NAME
  1037.  
  1038. HTML::Mason::ApacheHandler2 - experimental (alpha) Mason/mod_perl2 interface
  1039.  
  1040. =head1 SYNOPSIS
  1041.  
  1042.     use HTML::Mason::ApacheHandler2;
  1043.  
  1044.     my $ah = HTML::Mason::ApacheHandler2->new (..name/value params..);
  1045.     ...
  1046.     sub handler {
  1047.      my $r = shift;
  1048.      $ah->handle_request($r);
  1049.     }
  1050.  
  1051. =head1 DESCRIPTION
  1052.  
  1053. B<HTML::Mason::ApacheHandler2 is highly experimental ( alpha ) and
  1054. should only be used in a test environment.>
  1055.  
  1056. HTML::Mason::ApacheHandler2 is a clone of HTML::Mason::ApacheHandler
  1057. changed to work under a pure mod_perl2 environment. The external
  1058. interface is unchanged, see
  1059. L<HTML::Mason::ApacheHandler|ApacheHandler>.
  1060.  
  1061. The actual changes I made can be found in the distribution in
  1062. B<diff/ApacheHandler.diff> ( made with 'diff -Naru' ... ).
  1063.  
  1064. A HOTWO for HTML::Mason::ApacheHandler2 may be found at
  1065. L<HOWTO Run Mason with mod_perl2|Mason-with-mod_perl2>.
  1066.  
  1067. =head1 PREREQUISITES
  1068.  
  1069. You must have the following packages installed:
  1070.  
  1071.     mod_perl        => 1.9910
  1072.     HTML::Mason'    => 1.25
  1073.     libapreq2       => 2.02-dev
  1074.  
  1075. Please refer to the original packages' documentation
  1076. for instructions.
  1077.  
  1078. =head1 SEE ALSO
  1079.  
  1080. My documents, including:
  1081. L<HOWTO Run Mason with mod_perl2|Mason-with-mod_perl2>,
  1082. L<MasonX::Request::WithApacheSession2|WithApacheSession2>,
  1083. L<MasonX::Request::WithMultiSession2|WithMultiSession2>,
  1084.  
  1085. Original Mason documents, including:
  1086. L<HTML::Mason::ApacheHandler|ApacheHandler>,
  1087. L<MasonX::Request::WithApacheSession|WithApacheSession>,
  1088. L<MasonX::Request::WithMultiSession|WithMultiSession>.
  1089.  
  1090. Also see the Mason documentation at L<http://masonhq.com/docs/manual/>.
  1091.  
  1092. =head1 AUTHOR
  1093.  
  1094. Beau E. Cox <beau@beaucox.com> L<http://beaucox.com>.
  1095.  
  1096. The real authors (I just made mod_perl2 changes) are the Mason crew, including:
  1097. Jonathan Swartz <swartz@pobox.com>,
  1098. Dave Rolsky <autarch@urth.org>,
  1099. Ken Williams <ken@mathforum.org>.
  1100.  
  1101. Version 0.01 as of January, 2004.
  1102.  
  1103. =cut
  1104.