focal (3) Embperl::TipsAndTricks.3pm.gz

Provided by: libembperl-perl_2.5.0-13ubuntu1_amd64 bug

NAME

       Embperl::TipsAndTricks - Embperl Tips and Tricks

Contents

       Tips and Tricks
       Alternative Way To Do Global Variables, using __PACKAGE__
       Global Variables Via Namespaces
       Handling Queries in DBI
       Handling Exits
       Handling Errors
       Development and Production Websites

NAME

       Embperl::TipsAndTricks - Embperl Tips and Tricks

Tips and Tricks

       This document follows on from the Embperl/EmbperlObject introductory tutorial. As you can see from that,
       Embperl/EmbperlObject enables extremely powerful websites to be built using a very intuitive object-
       oriented structure. Now, we'll look at some additional, "unofficial" techniques which may also be useful
       in certain circumstances.

       This is a small collection of personal tricks which I have developed over the course of months using
       EmbperlObject in my own websites. I hope they are useful, or at least spur you on to develop your own
       frameworks and share these with others.

       If you have any Tips & Tricks you want to share with the public please send them to richter at embperl
       dot org .

Alternative Way To Do Global Variables, using __PACKAGE__

       In the process of developing a large website I have found it can be a little onerous at times to use the
       Request object to pass around global data. I would like to just create variables like $xxx rather than
       typing $req->{xxx} all the time. It may not seem like much, but after a while your code can start looking
       a lot more complex because of all the extra brackets and suchlike. As a typical lazy programmer, I looked
       for a way to simplify this.

       The method I am going to describe should be used with caution, because it can increase memory useage
       rather dramatically if you're not careful. The way I use it, no extra memory is used, but you do need to
       be aware of the issues.

       Basically, you change the way you include files from /base.html, so that they are included into the same
       package as /base.html:

               [- Execute ({inputfile => '*', package => __PACKAGE__}) -]

       You should only do this with HTML files which are included from /base.html, not with the files such as
       subs.html - those files have to be in their own packages in order for Perl inheritance to work. You can't
       use this technique with any files which are accessed via method calls.

       So how does this make things better? Well, since all these files now share the same package, any
       variables which are created in one of the files is accessible to any of the other files. This means that
       if you create $xxx in /init.html, then you can access $xxx in /head.html or any other file. This
       effectively gives you global variables across all the files which are included from /base.html into the
       same package as /base.html.

       The thing you need to be careful of here is that if one of these files is included more than once
       elsewhere on the website, then it will be separately compiled for that instance - thus taking up more
       memory. This is the big caveat. As a rule, if your files are all just included once by /base.html, then
       you should be fine. Note that you'll also need to change any calls to parent files, for example:

       /contact/init.html

               [- Execute ({inputfile => '../init.html', package => __PACKAGE__}) -]

               [-
                       # Do some setup specific to this subdirectory
               -]

       This is ok, since ../init.html will still be compiled into the same package as the rest of the files
       included from /base.html, and so only one version of it will exist in the Embperl cache. Thus memory
       usage is not increased.

       I like this technique because it simplifies the look of my code, which is important for projects
       containing complex algorithms. It is not the "official" way to implement globals though, and should be
       used with care.

Global Variables Via Namespaces

       The previous section described a way to share variables between different files which are included from
       /base.html, by using the same package across all the files. However this doesn't help us much when
       dealing with the method files such as subs.html, because these files have to have their own packages - so
       we are back to square one.

       There is another way to share variables across even different packages, and that is by using namespaces.
       For variables that need to be accessible even from subs.html, you could use a namespace which is specific
       to your website. For example, if your website domain is mydomain.com, then you could create variables
       using the form

               $mydomain::xxx = "hello";

       As long as you then make sure that you only use this namespace on this website (and other websites on the
       same Apache web server use their own namespaces), then you shouldn't get any conflicts. Once again, use
       this with caution, since you introduce the possibility of inadvertently sharing variables between
       completely different websites. For example, if you cut and paste some useful code from one website to
       another, you will need to make sure you change the namespace of any globals. Otherwise, you could get
       some very obscure bugs, since different requests to the various websites could conflict.

       You also need to be careful about variable initialization, since these globals will now exist between
       different requests. So, it's possible that if you don't re-initialize a global variable, then it may
       contain some random value from a previous request. This can result in obscure bugs. Just be careful to
       initialize all variables properly and you'll be fine.

       Finally, note that Embperl will only clean up variables which don't have an explicit package (i.e. are in
       one of the packages automatically set up by Embperl). Variables in other namespaces are not automatically
       cleaned up. As a result, you need to pay closer attention to cleaning up if you use your own namespaces.
       The safe way to clean up a variable is simply to 'undef' it.

Handling Queries in DBI

       If you are like me, you probably use DBI extensively to enable your dynamic websites. I have found the
       cleanup of queries to be onerous - e.g. calling finish() on queries. If you don't do that, then you tend
       to get warnings in your error log about unfinished queries.

       What I do these days is use a global hash, called e.g. %domain::query (see the previous section for using
       namespaces to safely implement global variables). Then, whenever I create a query, I use this variable.
       For example:

               $domain::query{first_page} = $domain::dbh->prepare (qq{
                       SELECT *
                       FROM pages
                       WHERE page = 1
                       });
               $domain::query{first_page}->execute();
               my $first_page = $domain::query{first_page}->fetchrow_hashref();

       This little pattern, I find, makes all my queries easier to read and keep track of. You give each one a
       name in the %domain::query hash that makes sense. Then, at the end of each request, in the /cleanup.html
       file, you can do something like this:

               while (($name, $query) = each (%domain::query))
               {
                       $query->finish();
               }
               $domain::dbh->disconnect();

       Once again, this method is not really the "official" way of doing things in Embperl. You should use the
       Request object to pass around global variables if you're not comfortable with the risks involved with
       namespaces (e.g. conflicting websites on the same web server).

Handling Exits

       You will often find that you want to terminate a page before the end. This doesn't necessarily indicate
       an error condition; it can be just that you've done all you want to do. When you do this, it is good to
       first clean up, otherwise you can get annoying warnings showing up in your error logs.

       I use the following framework. /cleanup.html is Executed from /base.html, and it is the last thing that
       is done. It calls the cleanup() function in the /subs.html file:

       /cleanup.html

               [-
                       $subs->cleanup ();
               -]

       /subs.html

               [!
                       sub cleanup
                       {
                               while (($name, $query) = each (%domain::query))
                               {
                                       $query->finish();
                               }
                               $domain::dbh->disconnect();
                       }

                       sub clean_exit
                       {
                               cleanup();
                               exit();
                       }
               !]

       Now, whenever I want to exit prematurely, I use a call to $subs->clean_exit() rather than just exit().
       This makes sure that the queries and database connections are shut down nicely.

Handling Errors

       The EMBPERL_OBJECT_FALLBACK directive in httpd.conf allows you to set a file which will be loaded in the
       event that the requested file is not found. This file should be relative to the same directory as
       base.html.

       I have found that making a special /errors/ directory is useful, because it enables that special
       subdirectory to define its own head.html file, init.html and so on.  So, I then just put this in
       /notfound.html:

               [-
                       $http_headers_out{'Location'} = "/errors/";
                       clean_exit();
               -]

       See the previous section, "Handling Exits" for more on clean_exit().

Development and Production Websites

       When I am developing a website, I usually use at least two machines. I have a workstation where I do
       developing and testing, and a separate production server, which is accessed by the public. When I am
       finished making changes to the development version of the website, I move it over to the production
       server for testing there. However when I do this, I usually don't copy it immediately over the existing
       production version, because there are sometimes issues with Perl modules which haven't been installed on
       the server, or other issues which break the code on a different machine. So I use a separate virtual
       server and subdomain (which is easy if you run your own DNS) to test the new version. For example if the
       production version of the server is at www.mydomain.com, then I might do testing on the production server
       under test.mydomain.com, or beta. or whatever subdomain you like. This means you have to create a new
       virtual server in the httpd.conf file. You also obviously create a new directory for the test server (see
       below for an example).

       When you do all this, you end up with a very nice, isolated testing environment on the same server as
       production. Obviously you hopefully did all your major testing on your workstation, where you can crash
       the machine and it doesn't matter too much. The production server testbed is a last staging area before
       production, to get rid of any lingering glitches or omissions. When you're sure it's all working
       correctly you just copy the files from one directory tree (test) to another (production) on the same
       machine. This test server can also be used as a beta of the new production version. Friendly users can be
       given access to the new version, while the old version is still running.

       One issue that comes up when you do this is that of databases. It is very likely that you will be using a
       special test database rather than the live one to test your new version. It would be very unwise to use a
       production database for testing. So your production database might be called "mydatabase", and the test
       one called "mydatabase_test". This is fine, but it means that you have to remember to change the database
       name in your code when you copy the files over to production. This is very error prone. The solution is
       to set variables like the database name in httpd.conf, by setting an environment variable. You just add
       it to the virtual server section.

       Here is a real example of two virtual servers on the same production machine, which use two different
       directories, separate log files and different databases. The website is crazyguyonabike.com, which is a
       journal of a bicycle ride I did across America in 1998. I decided to expand the site to allow other
       cyclists to upload their own journals, which resulted in substantial changes to the code. I wanted to
       keep the original site up while testing the new version, which I put under new.crazyguyonabike.com. Here
       are the relevant apache settings:

       /etc/apache/httpd.conf

               # The production server
               <VirtualHost 10.1.1.2:80>
                       ServerName www.crazyguyonabike.com
                       SSLDisable
                       ServerAdmin neil@nilspace.com
                       DocumentRoot /www/crazyguyonabike/com/htdocs
                       DirectoryIndex index.html
                       ErrorLog /www/crazyguyonabike/com/logs/error_log
                       TransferLog /www/crazyguyonabike/com/logs/access_log
                       ErrorDocument 403 /
                       ErrorDocument 404 /
                       PerlSetEnv WEBSITE_DATABASE crazyguyonabike
                       PerlSetEnv WEBSITE_ROOT /www/crazyguyonabike/com/htdocs
                       PerlSetEnv EMBPERL_DEBUG 0
                       PerlSetEnv EMBPERL_ESCMODE 0
                       PerlSetEnv EMBPERL_OPTIONS 16
                       PerlSetEnv EMBPERL_MAILHOST mail.nilspace.com
                       PerlSetEnv EMBPERL_OBJECT_BASE base.html
                       PerlSetEnv EMBPERL_OBJECT_FALLBACK notfound.html
               </VirtualHost>

               <VirtualHost 10.1.1.2:80>
                       ServerName crazyguyonabike.com
                       Redirect / http://www.crazyguyonabike.com
               </VirtualHost>

               # Set EmbPerl handler for main directory
               <Directory "/www/crazyguyonabike/com/htdocs/">
                       <FilesMatch ".*\.html$">
                               SetHandler  perl-script
                               PerlHandler HTML::EmbperlObject
                               Options     ExecCGI
                       </FilesMatch>
               </Directory>

               # The test server
               <VirtualHost 10.1.1.2:80>
                       ServerName new.crazyguyonabike.com
                       SSLDisable
                       ServerAdmin neil@nilspace.com
                       DocumentRoot /www/crazyguyonabike/com/new
                       Alias /pics /www/crazyguyonabike/com/pics
                       DirectoryIndex index.html
                       ErrorLog /www/crazyguyonabike/com/logs/new_error_log
                       TransferLog /www/crazyguyonabike/com/logs/new_access_log
                       ErrorDocument 401 /user/register/
                       ErrorDocument 403 /
                       ErrorDocument 404 /
                       PerlSetEnv WEBSITE_DATABASE crazyguyonabike_new
                       PerlSetEnv WEBSITE_ROOT /www/crazyguyonabike/com/new
                       PerlSetEnv EMBPERL_DEBUG 0
                       PerlSetEnv EMBPERL_ESCMODE 0
                       PerlSetEnv EMBPERL_OPTIONS 16
                       PerlSetEnv EMBPERL_MAILHOST mail.nilspace.com
                       PerlSetEnv EMBPERL_OBJECT_BASE base.html
                       PerlSetEnv EMBPERL_OBJECT_FALLBACK notfound.html
               </VirtualHost>

               # Set EmbPerl handler for new directory
               <Directory "/www/crazyguyonabike/com/new/">
                       <FilesMatch ".*\.html$">
                               SetHandler  perl-script
                               PerlHandler HTML::EmbperlObject
                               Options     ExecCGI
                       </FilesMatch>
               </Directory>

               # Restrict access to test server
               <Directory /www/crazyguyonabike/com/new>
                       AuthType Basic
                       AuthName CrazyTest
                       Auth_MySQL_DB http_auth
                       Auth_MySQL_Encryption_Types Plaintext
                       require valid-user
                       PerlSetEnv EMBPERL_OPTIONS 16
                       PerlSetEnv EMBPERL_MAILHOST mail.nilspace.com
               </Directory>

       Note that the test and production servers each get their own databases, directories and log files.

       You can also see that I restrict access to the test server (which is generally wise, unless you actually
       like hackers potentially screwing with your head while testing). For basic authentication I use
       mod_auth_mysql, which is available from the MySQL website. It is nice because it allows you to
       authenticate based on a MySQL database.

       When you use PerlSetEnv to pass in variables, you access these variables in your code as follows:

               $db_name = $ENV{WEBSITE_DATABASE};

       If you move those constants which differ between the test and production versions of the same code into
       the httpd.conf file, then you can just copy the files over from the test directories to the production
       directory without any alterations. This cuts down on editing errors and also documents specific constants
       in one place.

Author

       Neil Gunton neil@nilspace.com