Safe::World - Create multiple virtual instances of a Perl interpreter that can be assembled together.
See USE section for complexer example and the test.pl script.
use Safe::World ; my $world = Safe::World->new( stdout => \$stdout , ## - redirect STDOUT to this scalar. stderr => \$stderr , ## - redirect STDERR to this scalar. flush => 1 , ## - output is flushed, soo don't need to wait exit to ## have all the data inside $stdout. ) ; ## Evaluate some code: $world->eval(q` use Data::Dumper ; print Dumper( {a => 1 , b => 2} ) ; `); $world->close ; ## ensure that everything is finished and flushed. die($stderr) if $stderr ; print $stdout ; $world = undef ; ## Destroy the world. Here the compartment is cleanned.
Note that in this example, inside the World is loaded Data::Dumper, but Data::Dumper was loaded only inside of it, keeping the outside normal.
With Safe::World you can create multiple virtual instances/compartments of a Perl interpreter, that will work/run without touch the other instances/compartments and mantaining the main interpreter normal.
Actually each Each instance (WORLD object), is a Safe compartment (Safe::World::Compartment) with all the resources of a normal Perl interpreter implemented (IO, @INC, %INC, Dynaloader::, etc...). But what happens inside each World doesn't change the enverioment outside of it or other Worlds.
Each instance (WORLD object) has their own STDOUT, STDERR and STDIN handlers, also has a fake HEADOUT output (when the argument headout is past) for the headers implemented inside the STDOUT. Soo, you can use this to redirect the outputs of the WORLD object to a FILEHANDLER, SCALAR or a SUB.
The module Safe::World was created for 3 purposes:
This enable a way to run multiple scripts in one Perl interpreter process, saving memory and time. After each execution the Safe compartment is "fully" cleanned, saving memory for the next compartment.
A normal Safe objects doesn't have the output handlers, actually is just a compartment to run codes that can't go outsied of it. Having a full WORLD implemented, with the STDOUT, STDERR, STDIN and HEADERS handlers, the output can be redirected to any kind of listener. Also the error outputs (STDERR) can be catched via sub (CODE), that can be displayed in the STDOUT in a nice way, or in the case of HTML output, be displayed inside comment tags, instead to go to an error log.
But to implement a full WORLD warn(), die() and exit() need to be overwrited too. Soo you can control if exit() will really exit from the virtual interpreter, and redirect the warn messages.
This is the advanced purpose, that need all the previous resources, and most important thing of Safe::World. Actually this was projected to work with mod_perl, soo the Perl codes can be runned in different compartments, but can have some part of the code cached in memory, specially the Perl Modules (Classes) that need to be loaded all the time.
Soo, you can load your classes in one World, and your script/page in other World, then link them and run your code normally. Then after run it you unlink the 2 Worlds, and only CLEAN the World with your script/page, and now you can keep the 1st World with your Classes cached, to link it again with the next script/page to run.
Here's how to implement that:
A cache world is created, where all the classes common to the all the different scripts/pages are loaded.
For each script/page is created a world, each time that is executed (unless a script need to be persistent). Inside this worlds only the main code of the scripts/pages are loaded.
Using the method link_world(), two worlds can be assembled. Actually one world is imported inside another. In this case the Cache World is linked to the Execution World. Now you can't evaluate codes in the Cache World, since it's shared, and evaluation is only accepted in the Execution World.
my $world_cache = Safe::World->new(sharepack => ['DBI','DBD::mysql']) ; $world_cache->eval(" use DBI ;") ; $world_cache->eval(" use DBD::mysql ;") ; my ( $stdout , $stderr ) ; my $world_exec = Safe::World->new( stdout => \$stdout , stderr => \$stderr , ) ; $world_exec->link_world($world_cache) ; $world_exec->eval(q` $dbh = DBI->connect("DBI:mysql:database=$db;host=$host", 'user' , 'pass') ; `);
See the test.pl script for more examples.
use Safe::World ; my $world = Safe::World->new( stdout => \$stdout , ## - redirect STDOUT to this scalar. stderr => \$stderr , ## - redirect STDERR to this scalar. headout => \$headout , ## - SCALAR to hold the headers. autohead => 1 , ## - tell to handle headers automatically. headsplitter => 'HTML' , ## - will split the headers from the content handling ## the output as HTML. flush => 1 , ## - output is flushed, soo don't need to wait exit to ## have all the data inside $stdout. on_closeheaders => sub { ## sub to call when headers are closed (when content start). my ( $world ) = @_ ; my $headers = $world->headers ; $headers =~ s/\r\n?/\n/gs ; $headers =~ s/\n+/\n/gs ; $headers .= "\015\012\015\012" ; ## add the headers end. $world->print($headers) ; ## print the headers to STDOUT $world->headers('') ; ## clean the headers scalar. } , on_exit => sub { ## sub to call when exit() happens. my ( $world ) = @_ ; $world->print("<!-- ON_EXIT_IN -->\n"); return 0 ; ## 0 make exit() to be skiped. 1 make exit() work normal. } , ) ; ## Evaluate some code: $world->eval(q` print "Content-type: text/html\n\n" ; print "<html>\n" ; print "content1\n" ; ## print some header after print the content, ## but need to be before flush the output! $SAFEWORLD->print_header("Set-Cookie: FOO=BAR; domain=foo.com; path=/;\n") ; print "content2\n" ; print "</html>\n" ; warn("some alert to STDERR!") ; exit; `); $world->close ; ## ensure that everything is finished and flushed. print $socket $stdout ; ## print the output to some client socket. print $log $stderr ; ## print errors to a log. $world = undef ; ## Destroy the world. Here the compartment is cleanned.
Create the World object.
Arguments:
The name of the package where the compartment will be created.
By default is used SAFEWORLDx, where x will increse: SAFEWORLD1, SAFEWORLD2, SAFEWORLD3...
The STDOUT target. Can be another GLOB/FILEHANDLER, a SCALAR reference, or a sub reference.
DEFAULT: \*main::STDOUT
The STDERR target. Can be another GLOB/FILEHANDLER, a SCALAR reference, or a sub reference.
DEFAULT: \*main::STDERR
The STDIN handler. Need to be a IO handler.
DEFAULT: \*main::STDIN
The HEADOUT target. Can be another GLOB/FILEHANDLER, a SCALAR reference, or a sub reference.
The HASH reference for the internal %ENV of the World.
If TRUE tell that STDOUT will be always flushed ( $| = 1 ).
** This is good to use if you are using a SCALAR as STDOUT. Also will run faster, since uses a simple STDOUT handler.
If TRUE tell that the compartment wont be cleaned when destroyed.
If TRUE tell to not set the internal object $SAFEWORLD, that gives access to it self inside the compartment.
If TRUE tell that the STDOUT will handler automatically the handlers in the output, using headsplitter.
A REGEXP or CODE reference to split the header from the content.
Example of REGEXP:
my $splitter = qr/(?:\r\n\r\n|\012\015\012\015|\n\n|\015\015|\r\r|\012\012)/s ; ## This is the DEFAULT
Example of SUB:
sub splitter { my ( $world , $data ) = @_ ; my ($headers , $rest) = split(/\r\n?\r\n?/s , $data) ; return ($headers , $rest) ; }
When a World is linked to another you need to tell what packages inside it can be shared:
my $world_cache = Safe::World->new(sharepack => ['DBI','DBD::mysql']) ;
Sub to be called when the headers are closed.
Sub to be called when exit() is called.
** If the sub returns '0', exit will be skiped.
Sub to be called when the WORLD is selected to evaluate codes inside it.
Sub to be called when the WORLD is unselected, just after evaluate the codes.
Block the output to STDOUT/STDERR of the WORLD.
UNblock the output to STDOUT/STDERR of the WORLD.
Call DESTROY() and clean the compartment.
** Do not use the World object after this!
Call a sub inside the World and returning their values.
my @ret0 = $world->call('foo::methodx', $var1 , time()); ## foo::methodx($var1 , time()) my @ret1 = $world->call('methodz', 123); ## main::methodz(123)
Ensure that everything is finished and flushed.
You can't evaluate codes after this!
Close the tied STDOUT.
Close the tied STDERR.
Evaluate a code inside the World and return their values.
Evaluate a code inside the World without error alerts, warn(), die(), exit() or any output to STDERR.
Evaluate inside some package.
Same as:
my $code = "print time ;" ; $world->eval("package foo ; $code") ;
Evaluate code sending args (defining internal @_):
$world->eval_args(' print "$_[0]\n" ' , qw(a b c) ); ## Should print 'a'.
Same as eval_args(), but setting the package name to run the code.
Set $| to 1 or 0 if bool is defined.
Also flush STDOUT. Soo, if some data exists in the buffer it will be flushed to the output.
Return some variable value from the World:
my $document_root = $world->get('$ENV{DOCUMENT_ROOT}') ;
Return some variable value inside some package in the World:
my $document_root = $world->get('Foo' , '$VERSION') ;
Return a reference to a variable:
my $env = $world->get_ref('%ENV') ; $$env{ENV}{DOCUMENT_ROOT} = '/home/httpd/www' ; ## Set the value inside the World.
Return reference copy of a variable:
my $env = $world->get_ref_copy('%ENV') ;
** Note that the reference inside $env is not pointing to a variable inside the World.
Return the headers data.
** Note that this will only return data if HEADOUT is defined as SCALAR.
Load a module inside a World created for cache (an World to be linked to the others).
This will require inside the World the MODULE and handle automatically the sharedpack list of all the modules loaded and sub-loaded by the MODULE.
Link some package to the world.
$world->link_pack("Win32") ;
Unlink a package.
Unlink all the packages linked to this World.
** You shouldn't call this by your self. This is only used by DESTROY().
Link the compartment of a world to another.
$world->link_world( $world_shared ) ;
Unlink/disassemble a World from another.
Unlink all the worlds linked to this.
Deny the listed operators from being used when compiling code in the compartment (other operators may still be permitted).
Example of use:
$world->deny_only( qw(chroot syscall exit dump fork lock threadsv) ) ;
** See Opcode for the OP list.
Deny only the listed operators from being used when compiling code in the compartment (all other operators will be permitted).
Permit the listed operators to be used when compiling code in the compartment (in addition to any operators already permitted).
Permit only the listed operators to be used when compiling code in the compartment (no other operators are permitted).
Print some data to the STDOUT of the world.
Print some data to the HEADOUT of the world.
Print some data to the STDERR of the world.
Same as print.
Redirect the STDOUT to a scalar. Soo, you can internally redirect a peace of the output to a scalar.
In this example I want to catch what the sub test() prints:
sub test { print "sub_test[@_]" ; } print "A\n" ; my $out ; $SAFEWORLD->redirect_stdout(\$out) ; test(123); $SAFEWORLD->restore_stdout ; print "B\n" ; print "OUT: <$out>" ;
** See restore_stdout().
Restore the STDOUT output if a redirect_stdout() was made before.
** See redirect_stdout().
Reset the object flags. Soo, if it was closed (exited) can be reused.
You also can redefine this attributes sending this arguments:
Reset the internal flags. Soo, if the World was exited, can be reused.
Redefine the outputs (STDOUT, STDERR, STDIN, HEADOUT) targets.
Arguments: stdout, stderr, stdin, headout.
** Note that only pasted arguments will be used to redefine. Soo, wont be used default values like reset().
** See reset().
Return the root name of the compartment of the World.
Return the Safe object of the World.
Scan the elements of a symbol table of a package.
Return the package list of a World.
Select static a World to make multiple evaluations faster:
$world->select_static ; $world->eval("... 1 ...") ; $world->eval("... 2 ...") ; $world->eval("... 3 ...") ; $world->unselect_static ;
Unselect the world. Should be called after select_static().
Set the value of a varaible inside the World:
my @inc = qw('.','./lib') ; $world->set('@INC' , \@inc) ; ## To set a value that is a reference, like an object: $world->set('$objectx' , $objecty , 1) ;
Set a package inside a world SHARED, soo, when this World is linked to another this package is imported.
** See argument sharepack at new().
Unset a SHARED package.
$world->set_vars( '%SIG' => \%SIG , '$/' => $/ , '$"' => $" , '$;' => $; , '$$' => $$ , '$^W' => 0 , ) ;
Set a list of variables to be shared:
$world->share_vars( 'main' , [ '@INC' , '%INC' , '$@','$|','$_', '$!', ]) ;
Unshare the shared variables. Note that this is called only to clean the package.
Return the stdout data.
** Note that this will only return data if STDOUT is defined as SCALAR.
(NEW_DATA) can be used to set the new value of stdout data or to undef it.
Return the buffered stdout data.
** Note that this will only return data if STDOUT is not FLUSHED.
(NEW_DATA) can be used to set the new value of the buffer or to undef it.
The tiehandler of STDOUT.
The tiehandler of STDERR.
Send some warn message to the world, that will be redirected to the STDERR of the World.
To make a cache system for the Perl Modules you should use the method use_shared() and 2 Worlds, one as cache and other for execution:
use Safe::World ; ## Cache world shouldn't have output, soo using $tmp: my $tmp ; my $world_cache = Safe::World->new( stdout => \$tmp , stderr => \$tmp , flush => 1 , ) ; ## Cache this perl module: $world_cache->use_shared('Data::Dumper') ; ## Run 3 Worlds using Data::Dumper cached: for(1..3) { my ( $stdout , $stderr ) ; my $world = Safe::World->new( stdout => \$stdout , stderr => \$stderr , flush => 1 , ) ; $world->link_world($world_cache) ; $world->eval(q` print Data::Dumper::Dumper( \%INC ) ; `); $world->close ; ## ensure that everything is finished and flushed. ## close() also make an unlink_all_worlds() to ## free $world_cache for the next World. print "$stdout\n" ; print "=======================\n" ; }
And here's the output:
$VAR1 = { 'Carp.pm' => '#shared#', 'Exporter.pm' => '#shared#', 'XSLoader.pm' => '#shared#', 'strict.pm' => 'C:/Perl/lib/strict.pm', 'warnings/register.pm' => '#shared#', 'warnings.pm' => '#shared#', 'overload.pm' => '#shared#', 'Data/Dumper.pm' => '#shared#' }; ======================= $VAR1 = { 'Carp.pm' => '#shared#', 'Exporter.pm' => '#shared#', 'XSLoader.pm' => '#shared#', 'strict.pm' => 'C:/Perl/lib/strict.pm', 'warnings/register.pm' => '#shared#', 'warnings.pm' => '#shared#', 'overload.pm' => '#shared#', 'Data/Dumper.pm' => '#shared#' }; ======================= $VAR1 = { 'Carp.pm' => '#shared#', 'Exporter.pm' => '#shared#', 'XSLoader.pm' => '#shared#', 'strict.pm' => 'C:/Perl/lib/strict.pm', 'warnings/register.pm' => '#shared#', 'warnings.pm' => '#shared#', 'overload.pm' => '#shared#', 'Data/Dumper.pm' => '#shared#' }; =======================
Safe::World::Scope, HPL, Safe, Opcode.
This module was made to work with HPL and mod_perl, enabling multiple executions of scripts in one Perl interpreter, and also brings a way to cache loaded modules, making the execution of multiple scripts and mod_perl pages faster and with less memory.
Actually this was first writed as HPL::PACK module, then I haved moved it to Safe::World to be shared with other projects. ;-P
** Note that was hard to implement all the enverioment inside Safe::World, soo if you have ideas or suggestions to make this work better, please send them. ;-P
Graciliano M. P. <gm@virtuasites.com.br>
I will appreciate any type of feedback (include your opinions and/or suggestions). ;-P
Enjoy!
Thanks to:
Elizabeth Mattijsen <liz@dijkmat.nl>, to test it in different Perl versions and report bugs.
This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
To install Safe::World, copy and paste the appropriate command in to your terminal.
cpanm
cpanm Safe::World
CPAN shell
perl -MCPAN -e shell install Safe::World
For more information on module installation, please visit the detailed CPAN module installation guide.