Apache-ASP

 view release on metacpan or  search on metacpan

ASP.pm  view on Meta::CPAN


# 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;
    }

ASP.pm  view on Meta::CPAN

	    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;

ASP.pm  view on Meta::CPAN


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.

ASP.pm  view on Meta::CPAN


  <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.

ASP.pm  view on Meta::CPAN


 + 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)

ASP.pm  view on Meta::CPAN

  <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 )