Apache-ASP
view release on metacpan or search on metacpan
# For documentation for this module, please see the end of this file
# or try `perldoc Apache::ASP`
package Apache::ASP;
$VERSION = 2.63;
#require DynaLoader;
#@ISA = qw(DynaLoader);
#bootstrap Apache::ASP $VERSION;
use Digest::MD5 qw(md5_hex);
use Cwd qw(cwd);
# create multiple entries for this symbols for StatINC
use Fcntl qw(:flock O_RDWR O_CREAT);
# load these always, but only load ::State, ::Session, ::Application
# at runtime in non mod_perl environments since they may not be needed
use Apache::ASP::GlobalASA;
use Apache::ASP::Response;
use Apache::ASP::Request;
use Apache::ASP::Server;
use Apache::ASP::Date;
use Apache::ASP::Lang::PerlScript;
use Carp qw(confess cluck);
use strict;
no strict qw(refs);
use vars qw($VERSION
%NetConfig %LoadedModules %LoadModuleErrors
%Codes %includes %Includes %CompiledIncludes
@Objects %Register %XSLT
$ServerID $ServerPID $SrandPid
$CompileErrorSize $CacheSize @CompileChecksumKeys
%ScriptLanguages $ShareDir $INCDir $AbsoluteFileMatch
$QuickStartTime
$SessionCookieName
$LoadModPerl
$ModPerl2
);
# other common modules load now, these are optional though, so we do not error upon failure
# just do this once perl mod_perl parent startup
unless($LoadModPerl++) {
my @load_modules = qw( Config lib Time::HiRes );
if($ENV{MOD_PERL}) {
# Only pre-load these if in a mod_perl environment for sharing memory post fork.
# These will not be loaded then for CGI until absolutely necessary at runtime
push(@load_modules, qw(
mod_perl
MLDBM::Serializer::Data::Dumper Devel::Symdump CGI
Apache::ASP::StateManager Apache::ASP::Session Apache::ASP::Application
Apache::ASP::StatINC Apache::ASP::Error
)
);
}
for my $module ( @load_modules ) {
eval "use $module ();";
}
if(exists $ENV{MOD_PERL_API_VERSION}) {
if($ModPerl2 = ($ENV{MOD_PERL_API_VERSION} >= 2)) {
if($ModPerl2) {
eval "use Apache::ASP::ApacheCommon ();";
die($@) if $@;
}
}
}
}
## HEADER TOKEN TWEAK
# This must be called outside the above load module block, so that
# its gets run whenever this module is loaded
# This didn't work in 1.27 mod_perl, with DSO enabled, would
# put the Apache::ASP token in front.
# eval { &Apache::add_version_component("Apache::ASP/$VERSION"); };
# $Apache::Server::AddPerlVersion = 1;
#use integer; # don't use screws up important numeric logic
@Objects = ('Application', 'Session', 'Response', 'Server', 'Request');
map { eval "sub $_ { shift->{$_} }" } @Objects;
# use regexp directly, not sub for speed
$AbsoluteFileMatch = '^(/|[a-zA-Z]:)';
$CacheSize = 1024*1024*10;
$SessionCookieName = 'session-id';
# ServerID creates a unique identifier for the server
srand();
$ServerID = substr(md5_hex($$.rand().time().(-M('..')||'').(-M('/')||'')), 0, 16);
$ServerPID = $$;
# DEFAULT VALUES
$Apache::ASP::CompileErrorSize = 500;
@CompileChecksumKeys = qw ( Global DynamicIncludes UseStrict XMLSubsMatch XMLSubsPerlArgs XMLSubsStrict GlobalPackage UniquePackages IncludesDir InodeNames PodComments );
%ScriptLanguages = (
'PerlScript' => 1,
);
&InitPaths();
%Apache::ASP::LoadModuleErrors =
(
'Filter' =>
"Apache::Filter was not loaded correctly for using SSI filtering. ".
"If you don't want to use filtering, make sure you turn the Filter ".
"config option off whereever it's being used",
Clean => undef,
CreateObject =>
'OLE-active objects not supported for this platform, '.
'try installing Win32::OLE',
Gzip =>
'Compress::Zlib is needed to make gzip content-encoding work, '.
'If you want to use this feature, get yourself the latest '.
'Compress::Zlib from CPAN. ',
HiRes => undef,
FormFill =>
'HTML::FillInForm is needed to use the FormFill feature '.
'for auto filling forms with $Response->Form() data',
MailAlert => undef,
SendMail => "No mailing support",
StateDB =>
'cannot load StateDB '.
'must be a valid perl module with a db tied hash interface '.
'such as: SDBM_File (default), or DB_File',
StateSerializer =>
'cannot load StateSerializer '.
'must be a valid serializing perl module for use with MLDBM '.
'such as Data::Dumper (default), or Storable',
StatINC => "You need this module for StatINC, please download it from CPAN",
'Cache' => "You need this module for xml output caching",
XSLT => 'Cannot load XML::XSLT. Try installing the module.',
);
sub handler {
my($package, $r) = @_;
my $status = 200;
# allows it to be called as an object method
ref $package and $r = $package;
# default to Apache request object if not passed in, for possible DSO fix
# rarely happens, but just in case
my $filename;
unless($filename = eval { $r->filename }) {
my $rtest = $ModPerl2 ? Apache2::RequestUtil->request() : Apache->request();
if($filename = eval { $rtest->filename }) {
$r = $rtest;
} else {
return &DSOError($rtest);
}
}
# better error checking ?
$filename ||= $r->filename();
# using _ is optimized to use last stat() record
return(404) if (! -e $filename or -d _);
# alias $0 to filename, bind to glob for bug workaround
local *0 = \$filename;
# ASP object creation, a lot goes on in there!
# method call used for speed optimization, as OO calls are slow
my $self = &Apache::ASP::new('Apache::ASP', $r, $filename);
# for runtime use/require library loads from global/INCDir
# do this in the handler section to cover all the execution stages
# following object set up as possible.
local @INC = ($self->{global}, $INCDir, @INC);
# Execute if no errors
$self->{errs} || &Run($self);
# moved print of object to the end, so we'll pick up all the
# runtime config directives set while the code is running
$self->{dbg} && $self->Debug("ASP Done Processing $self", $self );
# error processing
if($self->{errs}) {
require Apache::ASP::Error;
$status = $self->ProcessErrors;
}
die("no include $include") unless defined $file;
$include = $file;
}
# treat as anonymous subroutine compilation like data passed in
# as a scalar ref as above if we have NoCache set
if($self->{no_cache}) {
$include = $self->ReadFile($include);
$include_ref = $include;
goto COMPILE_INCLUDE_PARSE;
}
my $id = &FileId($self, $include);
$subid = ($package || $self->{GlobalASA}{'package'})."::$id".'xINC';
my $compiled = $Apache::ASP::CompiledIncludes{$subid};
if($compiled && ! $self->{stat_scripts}) {
$self->{dbg} && $self->Debug("no stat: found cached code for include $id");
return $compiled;
}
# return cached code if include hasn't been modified recently
$mtime = (stat($include))[9];
if($compiled && ($compiled->{mtime} > $mtime)) {
# $self->Debug("found cached code for include $id");
# now check for changed includes, return if not changed
my $includes_changed = 0;
if(my $includes = $Apache::ASP::Includes{$include}) {
for my $k (keys %$includes) {
my $v = $includes->{$k} || 0;
my @stat = stat($k);
if(@stat) {
if($stat[9] >= $v) {
$self->{dbg} && $self->Debug("file $k mtime changed from $v to $stat[9]");
$includes_changed = 1;
last;
}
} else {
$self->{dbg} && $self->Debug("can't get mtime for file $k: $!");
$includes_changed = 1;
last;
}
}
}
if(! $includes_changed) {
return $compiled;
} else {
$self->{dbg} && $self->Debug("includes changed for $include, recompiling");
}
}
}
COMPILE_INCLUDE_PARSE:
my $parse_data = $self->Parse($include);
my $no_cache = $self->{no_cache};
my $data;
# use Data::Dumper qw(Dumper);
# print STDERR Dumper($include, $parse_data);
# $self->Debug($self);
if ($parse_data->{is_perl}) {
my $sub = $self->CompilePerl($parse_data->{data}, $subid, $package);
# for perl with subs in it, do not cache the code compilation
# to help prevent my closure problems for newbies, --jc 2/11/2003
unless($no_cache) {
$no_cache = $self->TestForSubs($parse_data->{data});
if($no_cache) {
$self->Debug("test for subs returned $no_cache, no_cache = $no_cache");
}
}
if ($sub) {
$data = {
mtime => time(),
code => $sub,
perl => $parse_data->{data},
file => $include_ref || $include,
};
}
} elsif($parse_data->{is_raw}) {
$data = {
mtime => time(),
code => $parse_data->{data},
perl => $parse_data->{data},
file => $include_ref || $include,
};
} else {
$data = undef;
}
if ($data && $subid && ! $no_cache) { # for a returned code ref, don't cache
$Apache::ASP::CompiledIncludes{$subid} = $data;
}
$data;
}
sub UndefRoutine {
my($self, $subid) = @_;
my $code = \&{$subid};
if($code) {
$self->{dbg} && $self->Debug("undefing sub $subid code $code");
undef(&$code); # method for perl 5.6.1
undef($code); # older perls ??
}
}
sub ReadFile {
my($self, $file) = @_;
local *READFILE;
open(READFILE, $file) || $self->Error("can't open file $file for reading");
local $/ = undef;
my $data = <READFILE>;
close READFILE;
The default has not changed since implementing this config directive.
The reason for this config option is to allow operating systems with caching
file systems like Solaris to specify a state directory separately
from the Global directory, which contains more permanent files.
This way one may point StateDir to /tmp/myaspapp, and make one's ASP
application scream with speed.
PerlSetVar StateDir ./.state
=item StateManager
default 10, this number specifies the numbers of times per SessionTimeout
that timed out sessions are garbage collected. The bigger the number,
the slower your system, but the more precise Session_OnEnd's will be
run from global.asa, which occur when a timed out session is cleaned up,
and the better able to withstand Session guessing hacking attempts.
The lower the number, the faster a normal system will run.
The defaults of 20 minutes for SessionTimeout and 10 times for
StateManager, has dead Sessions being cleaned up every 2 minutes.
PerlSetVar StateManager 10
=item StateDB
default SDBM_File, this is the internal database used for state
objects like $Application and $Session. Because an SDBM_File %hash
has a limit on the size of a record key+value pair, usually 1024 bytes,
you may want to use another tied database like DB_File or
MLDBM::Sync::SDBM_File.
With lightweight $Session and $Application use, you can get
away with SDBM_File, but if you load it up with complex data like
$Session{key} = { # very large complex object }
you might max out the 1024 limit.
Currently StateDB can be: SDBM_File, MLDBM::Sync::SDBM_File,
DB_File, and GDBM_File. Please let me know if you would like to
add any more to this list.
As of version .18, you may change this setting in a live production
environment, and new state databases created will be of this format.
With a prior version if you switch to a new StateDB, you would want to
delete the old StateDir, as there will likely be incompatibilities between
the different database formats, including the way garbage collection
is handled.
PerlSetVar StateDB SDBM_File
=item StateCache
Deprecated as of 2.23. There is no equivalent config for
the functionality this represented from that version on.
The 2.23 release represented a significant rewrite
of the state management, moving to MLDBM::Sync for its
subsystem.
=item StateSerializer
default Data::Dumper, you may set this to Storable for
faster serialization and storage of data into state objects.
This is particularly useful when storing large objects in
$Session and $Application, as the Storable.pm module has a faster
implementation of freezing and thawing data from and to
perl structures. Note that if you are storing this much
data in your state databases, you may want to use
DB_File since it does not have the default 1024 byte limit
that SDBM_File has on key/value lengths.
This configuration setting may be changed in production
as the state database's serializer type is stored
in the internal state manager which will always use
Data::Dumper & SDBM_File to store data.
PerlSetVar StateSerializer Data::Dumper
=head2 Sessions
=item CookiePath
URL root that client responds to by sending the session cookie.
If your asp application falls under the server url "/asp",
then you would set this variable to /asp. This then allows
you to run different applications on the same server, with
different user sessions for each application.
PerlSetVar CookiePath /
=item CookieDomain
Default 0, this NON-PORTABLE configuration will allow sessions to span
multiple web sites that match the same domain root. This is useful if
your web sites are hosted on the same machine and can share the same
StateDir configuration, and you want to shared the $Session data
across web sites. Whatever this is set to, that will add a
; domain=$CookieDomain
part to the Set-Cookie: header set for the session-id cookie.
PerlSetVar CookieDomain .your.global.domain
=item SessionTimeout
Default 20 minutes, when a user's session has been inactive for this
period of time, the Session_OnEnd event is run, if defined, for
that session, and the contents of that session are destroyed.
PerlSetVar SessionTimeout 20
=item SecureSession
default 0. Sets the secure tag for the session cookie, so that the cookie
will only be transmitted by the browser under https transmissions.
PerlSetVar SecureSession 1
=item HTTPOnlySession
default 0. Sets HttpOnly flag to session cookie to mitigate XSS attacks.
Supported by most modern browsers, it only allows access to the
session cookie by the server (ie NOT Javascript)
PerlSetVar HTTPOnlySession 1
=item ParanoidSession
default 0. When true, stores the user-agent header of the browser
that creates the session and validates this against the session cookie presented.
If this check fails, the session is killed, with the rationale that
there is a hacking attempt underway.
This config option was implemented to be a smooth upgrade, as
you can turn it off and on, without disrupting current sessions.
Sessions must be created with this turned on for the security to take effect.
<HTML><BODY>
$counter++;
$Response->Write("<BR>Counter: $counter");
</BODY></HTML>
The value for $counter++ will remain between requests.
Generally use of globals in this way is a BAD IDEA,
and you can spare yourself many headaches if do
"use strict" perl programming which forces you to
explicity declare globals like:
use vars qw($counter);
You can make all your Apache::ASP scripts strict by
default by setting:
PerlSetVar UseStrict 1
=item Apache errors on the PerlHandler or PerlModule directives ?
You get an error message like this:
Invalid command 'PerlModule', perhaps mis-spelled or defined by a
module not included in the server configuration.
You do not have mod_perl correctly installed for Apache. The PerlHandler
and PerlModule directives in Apache *.conf files are extensions enabled by mod_perl
and will not work if mod_perl is not correctly installed.
Common user errors are not doing a 'make install' for mod_perl, which
installs the perl side of mod_perl, and not starting the right httpd
after building it. The latter often occurs when you have an old apache
server without mod_perl, and you have built a new one without copying
over to its proper location.
To get mod_perl, go to http://perl.apache.org
=item Error: no request object (Apache=SCALAR(0x???????):)
Your Apache + mod_perl build is not working properly,
and is likely a RedHat Linux RPM DSO build. Make sure
you statically build your Apache + mod_perl httpd,
recompiled fresh from the sources.
=item I am getting a tie or MLDBM / state error message, what do I do?
Make sure the web server or you have write access to the eg directory,
or to the directory specified as Global in the config you are using.
Default for Global is the directory the script is in (e.g. '.'), but should
be set to some directory not under the www server document root,
for security reasons, on a production site.
Usually a
chmod -R -0777 eg
will take care of the write access issue for initial testing purposes.
Failing write access being the problem, try upgrading your version
of Data::Dumper and MLDBM, which are the modules used to write the
state files.
=head2 Sessions
=item How can I use $Session to store complex data structures.
Very carefully. Please read the $Session documentation in
the OBJECTS section. You can store very complex objects
in $Session, but you have to understand the limits, and
the syntax that must be used to make this happen.
In particular, stay away from statements that that have
more than one level of indirection on the left side of
an assignment like:
BAD: $Session->{complex}{object} = $data;
=item How can I keep search engine spiders from killing the session manager?
If you want to disallow session creation for certain non web
browser user agents, like search engine spiders, you can use a mod_perl
PerlInitHandler like this to set configuration variables at runtime:
# put the following code into httpd.conf and stop/start apache server
PerlInitHandler My::InitHandler
<Perl>
package My::InitHandler;
use Apache;
sub handler {
my $r = shift; # get the Apache request object
# if not a Mozilla User Agent, then disable sessions explicitly
unless($r->headers_in('User-Agent') =~ /^Mozilla/) {
$r->dir_config('AllowSessionState', 'Off');
}
return 200; # return OK mod_perl status code
}
1;
</Perl>
This will configure your environment before Apache::ASP executes
and sees the configuration settings. You can use the mod_perl
API in this way to configure Apache::ASP at runtime.
Note that the Session Manager is very robust on its own, and denial
of service attacks of the types that spiders and other web bots
normally execute are not likely to affect the Session Manager significantly.
=item How can I use $Session to store a $dbh database handle ?
You cannot use $Session to store a $dbh handle. This can
be awkward for those coming from the IIS/NT world, where
you could store just about anything in $Session, but this
boils down to a difference between threads vs. processes.
+ Wrapped call to $r->connection->fileno in eval {} so to
preserve backwards compatibility with older mod_perl versions
that do not have this method defined. Thanks to Helmut Zeilinger
for catching this.
+ removed ./dev directory from distribution, useless clutter
+ Removed dependency on HTTP::Date by taking code into
Apache::ASP as Apache::ASP::Date. This relieves
the dependency of Apache::ASP on libwww LWP libraries.
If you were using HTTP::Date functions before without loading
"use HTTP::Date;" on your own, you will have to do this now.
+ Streamlined code execution. Especially worked on
$Response->IsClientConnected which gets called during
a normal request execution, and got rid of IO::Select
dependency. Some function style calls instead of OO style
calls where private functions were being invokes that one
would not need to override.
- Fixed possible bug when flushing a data buffer where there
is just a '0' in it.
+ Updated docs to note that StateCache config was deprecated
as of 2.23. Removed remaining code that referenced the config.
+ Removed references to unused OrderCollections code.
- Better Cache meta key, lower chance of collision with
unrelated data since its using the full MD5 keyspace now
+ Optimized some debugging statements that resulted
from recent development.
+ Tie::TextDir .04 and above is supported for StateDB
and CacheDB settings with MLDBM::Sync .21. This is good for
CacheDB where output is larger and there are not many
versions to cache, like for XSLTCache, where the site is
mostly static.
+ Better RESOURCES section to web site, especially with adding
some links to past Apache::ASP articles & presentations.
=item $VERSION = 2.25; $DATE="10/11/2001";
+ Improved ./site/apps/search application, for better
search results at Apache::ASP site. Also, reengineered
application better, with more perl code moved to global.asa.
Make use of MLDBM::Sync::SDBM_File, where search database
before was engineering around SDBM_File's shortcomings.
- Fix for SessionSerialize config, which broke in 2.23
Also, added t/session_serialize.t to test suite to catch
this problem in the future.
=item $VERSION = 2.23; $DATE="10/11/2001";
+Make sure a couple other small standard modules get loaded
upon "PerlModule Apache::ASP", like Time::HiRes, Class::Struct,
and MLDBM::Serializer::Data::Dumper. If not available
these modules won't cause errors, but will promote child httpd
RAM sharing if they are.
-XMLSubs args parsing fix so an arg like z-index
does not error under UseStrict. This is OK now:
<my:layer z-index=3 top=0 left=0> HTML </my:layer>
-Only remove outermost <SCRIPT> tags from global.asa
for IIS/PerlScript compatibility. Used to remove
all <SCRIPT> tags, which hurt when some subs in globa.asa
would be printing some JavaScript.
+$Response->{IsClientConnected} now updated correctly
before global.asa Script_OnStart. $Response->IsClientConnect()
can be used for accurate accounting, while
$Response->{IsClientConnected} only gets updated
after $Response->Flush(). Added test cases to response.t
+$Server->HTMLEncode(\$data) API extension, now can take
scalar ref, which can give a 5% improvement in benchmarks
for data 100K in size.
-Access to $Application is locked when Application_OnEnd &
Application_OnStart is called, creating a critical section
for use of $Application
++MLDBM::Sync used now for core DBM support in Apache::ASP::State.
This drastically simplifies/stabilizes the code in there
and will make it easier for future SQL database plugins.
+New API for accessing ASP object information in non content
handler phases:
use Apache::ASP;
sub My::Auth::handler {
my $r = shift;
my $ASP = Apache::ASP->new($r)
my $Session = $ASP->Session;
}
In the above example, $Session would be the same $Session
object created later while running the ASP script for this
same request.
Added t/asp_object.t test for this. Fixed global.asa to only
init StateDir when application.asp starts which is the first
test script to run.
-Fixed on Win32 to make Apache::ASP->new($r) able to create
multiple master ASP objects per request. Was not reentrant
safe before, particularly with state locking for dbms like
$Application & $Session.
++Output caching for includes, built on same layer ( extended )
as XSLTCache, test suite at t/cache.t. Enabled with special
arguments to
$Response->Include(\%args, @include_args)
$Response->TrapInclude(\%args, @include_args)
<a href>, <area href>, <form action>, <frame src>,
<iframe src>, <img src>, <input src>, <link href>
$Response->Redirect($URL) and the first URL in
script tags like <script>*.location.href=$URL</script>
These settings require that buffering be enabled, as
Apache::ASP will parse through the buffer to parse the URLs.
With SessionQueryParse on, it will just parse non-absolute
URLs, but with SessionQueryParseMatch set to some server
url regexp, like ^http://localhost , will also parse
in the session id for URLs that match that.
When testing, the performance hit from this parsing
a script dropped from 12.5 hits/sec on my WinNT box
to 11.7 hits per second for 1K of buffered output.
The difference is .007 of my PII300's processing power
per second.
For 10K of output then, my guess is that this speed
of script, would be slowed to 6.8 hits per second.
This kind of performance hit would also slow a
script running at 40 hits per second on a UNIX box
to 31 hits/sec for 1K, and to 11 hits/sec for 10K parsed.
Your mileage may vary and you will have to test the difference
yourself. Get yourself a valid URL with a session-id in
it, and run it through ab, or Socrates, with SessionQuery
turned on, and then with SessionQueryParse set to see
the difference. SessionQuery just enables of session id
setting from the query string but will not auto parse urls.
-If buffering, Content-Length will again be set.
It broke, probably while I was tuning in the past
couple versions.
+UseStrict setting compiles all scripts including
global.asa with "use strict" turned on for catching
more coding errors. With this setting enabled,
use strict errors die during compilation forcing
Apache::ASP to try to recompile the script until
successful.
-Object use in includes like $Response->Write()
no longer error with "use strict" programming.
+SessionQuery config setting with $Server->URL($url, { %params } )
alpha API extensions to enable cookieless sessions.
+Debugging not longer produces internal debugging
by default. Set to -1,-2 for internal debugging
for Debug settings 1 & 2.
+Both StateSerializer & StateDB can be changed
without affecting a live web site, by storing
the configurations for $Application & $Session
in an internal database, so that if $Session was
created with SDBM_File for the StateDB (default),
it will keep this StateDB setting until it ends.
+StateSerializer config setting. Default Data::Dumper,
can also be set to Storable. Controls how data is
serialized before writing to $Application & $Session.
+Beefed up the make test suite.
+Improved the locking, streamlining a bit of the
$Application / $Session setup process. Bench is up to
22 from 21 hits / sec on dev NT box.
+Cut more fat for faster startup, now on my dev box
I get 44 hits per sec Apache::ASP vs. 48 Embperl
vs. 52 CGI via Apache::Registry for the HelloWorld Scripts.
-Improved linking for the online site documentation,
where a few links before were bad.
=item $VERSION = 0.17; $DATE="11/15/99";
++20%+ faster startup script execution, as measured by the
HelloWorld bench. I cut a lot of the fat out of
the code, and is now at least 20% faster on startup
both with and without state.
On my dev (NT, apache 1.3.6+mod_perl) machine, I now get:
42 hits per sec on Apache::ASP HelloWorld bench
46 hits per sec on Embperl (1.2b10) and
51 hits per sec for CGI Apache::Registry scripts
Before Apache::ASP was clocking some 31 hits per sec.
Apache::ASP also went from 75 to 102 hits per second
on Solaris.
+PerlTaintCheck On friendly. This is mod_perl's way
of providing -T taint checking. When Apache::ASP
is used with state objects like $Session or $Application,
MLDBM must also be made taint friendly with:
$MLDBM::RemoveTaint = 1;
which could be put in the global.asa. Documented.
+Added $Response->ErrorDocument($error_code, $uri_or_string)
API extension which allows for setting of Apache's error
document at runtime. This is really just a wrapper
for Apache->custom_response() renamed so it syncs with
the Apache ErrorDocument config setting. Updated
documentation, and added error_document.htm example.
=OrderCollections setting was added, but then REMOVED
because it was not going to be used. It bound
$Request->* collections/hashes to Tie::IxHash, so that data
in those collections would be read in the order the
browser sent it, when eaching through or with keys.
-global.asa will be reloaded when changed. This broke
when I optimized the modification times with (stat($file))[9]
rather than "use File::stat; stat($file)->mtime"
-Make Apache::ASP->Loader() PerlRestartHandler safe,
( run in 2.091 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )