NuSphere Corporation Tech Library

Using mod_perl on a NuSphere Installation


Table of Contents

Perl Tools Installed by NuSphere

A Look at DBI

Setting up DBI (Determining the Port)

Verifying Your Connection

Getting Ready to Write a Simple Application

Creating a Simple Application

Writing an Input/Error Form

Wrapping Up

About NuSphere

Perl Tools Installed by NuSphere

One of the great things about the NuSphere package is that not only does it come complete with MySQL, Apache, the mod_perl Apache module, and all the Perl libraries you need to get your database-driven website up and running - it comes with the DOCUMENTATION, too. What more can you ask for? OK, besides a pony.

In this article, I'll show you how to write Perl scripts that will run via the fast and efficient Apache mod_perl module. The scripts will use the Perl DBI interface to access a local MySQL database that stores your data. There are really only a few functions you need to learn to get your website up and running. The hard part, as always, will be figuring out what exactly you want to do.


A Look at DBI

DBI is a Perl library that lets you write your code without being too tied to a particular DBMS. It isolates you from the native libraries and function calls used to access a database. Instead, whether you're using MySQL, Postgres, or anything else, you use the same basic library functions to connect to the database and execute SQL statements.

I say "too tied", because ultimately, the portability of your code depends not so much on DBI as on the SQL you write - SQL being one of those standards where "standard" means "kinda mostly the same but always a bit different". For instance, later on, I'll be using MySQL's auto_increment feature, that lets you create a numeric primary key column for your table, and have MySQL take care of incrementing values for it for each new row. When you've added a row to a table with one of these auto_increment columns, you can get the value of the new row's key with MySQL's last_insert_id() function.

Now, all of this works just this way only with MySQL. If you ported your code to, say, Oracle, you'd have to figure out how to do the same thing in its world.

At least you know your DBI code probably won't change - much.


TOP

Setting up DBI (Determining the Port)

On a standard NuSphere installation, the documentation for the DBI library is at http://localhost:9000/perldoc/site/lib/DBI.html. Take a look at this file. (And hint: bookmark it. You'll be coming back here.) The SYNOPSIS link will take you to a list of all the main methods provided by DBI. It's handy when you forget the exact name of a method. For now, follow the Class Methods link.

The first method described is connect. This is the method you use to establish a session with the MySQL database server. The format is pretty simple:

      $dbh = DBI->connect($data_source, $username, $password) or die 
      $DBI::errstr;

The variables $username and $password are what they appear to be. $data_source is a bit trickier. DBI uses this string to find the right driver, database, etc. for your database server. Again, the documentation gives examples of typical $data_source values:

      dbi:DriverName:database_name
      dbi:DriverName:database_name@hostname:port
      dbi:DriverName:database=database_name;host=hostname;port=port

But note the emphasized text just after these examples: "There is no standard for the text following the driver name." This is one of the things you definitely will have to change if you switch from one database to another.

So, what do you use with MySQL? The way to find out is to look at the documentation for DBD::mysql. This is the Perl library that is the interface between DBI and MySQL. (It's also the library for the mSQL database, so be careful when you read it that you're looking at the appropriate parts.)

And this is what you're looking for:

     # Connect to the database.
     my $dbh = DBI->connect("DBI:mysql:database=test;host=localhost",
                            "joe", "joe's password",
                            {'RaiseError' => 1});

RaiseError is a flag to the DBI 'connect' method that will cause your script to die if there are any database errors. It defaults to a value of 0 (off). You can just omit this if you don't want to set it.


TOP

Verifying Your Connection

You can use the Sample1.pl script to test out your installation. You can run it from the nsperl-bin directory that's set up by default on your server. (I'll show you how to set up other directories to run mod_perl later.) You can run the script by entering this URL in your browser: http://localhost:9000/perl-bin/script1.pl

Walking through it a bit at a time, we begin:

     #!perl
     print "Content-type: text/plain\n\r\n\r";
     print "Hello. This is a test file to see if DBI is working.\n";
     use DBI;

     my $driver = "mysql";
     my $hostname = "localhost";
     my $database = "mysql";
     my $port = "3306";
     my $user = "root";
     my $password = "";
     my $dsn = "dbi:$driver:database=$database;host=$hostname;port=$port";

The database mysql is a good one for this test because it's available in every working MySQL installation. The 'root' user is defined by default. In my example, I haven't changed root's MySQL password from the default of an empty string. If you have, use the new password you set up.

By default, MySQL runs on port 3306. You don't even have to specify the port unless you're using a different value. An easy way to check on a NuSphere installation is to use the phpMyAdmin tool. From the Home page of phpMyAdmin, click on the Show MySQL System Variables link. In the list of system variables displayed, you'll find a listing with the port number.

Now that I've created a data source string, I can connect to MySQL:

     my $dbh = DBI->connect($dsn, $user, $password);
     if (!$dbh)

     {
          print "Error connecting: ".$DBI::errstr;
          exit;

     }

If you don't see the error message when you run this, you should be OK. But I've included a query in the script to prove everything's working. It's a simple 'show tables' query; it's a good test, because it doesn't depend on anything in the database.

     my $query = "show tables";
     print "query=$query\n";

     my $result = $dbh->selectall_hashref($query) or print $dbh->errstr;

The DBI method 'selectall_hashref' returns a reference to an array containing all of the rows returned by the query. Each element of the array is a hash, with the keys set to the column names and the values set to the column values for that row. The script walks through the array:

     print "results:\n";
     foreach my $row (@$result)
     {
          foreach my $f (keys %{$row})
          {
                print "$f=$$row{$f}\t";
          }
          print "\n";
     }

The following screenshot shows the result you should get from this script.

If you want to know what happens when a query goes wrong, try running a bad query. Replace the show tables query with something like:

   $query = "select * from ajdf";

Unless you happen to have a table named 'ajdf' in your database, this is what you should see:


TOP

Getting Ready to Write a Simple Application

Now that you've proved that everything is working OK, it's time to try something a little more complicated. I'll go through a simple website that will let you record information about your stack of CDs. It's not the most likely thing you'd want to do, I'll grant you, but hey - it's not recipes.

First, though, let's set up Apache to let us run our code in a little sandbox area of the NuSphere installation. By default, Apache is set up to run mod_perl scripts from the 'nsperl-bin' directory. I want to put my scripts in a separate directory, but I don't want to mess around with the main installation's setup. So instead, I added some lines to the 'httpd.conf' file (located in \nusphere\apache\conf) to set up a new VirtualHost on my machine, on port 8080:

   # VirtualHost for my site
   Listen 8080
   <VirtualHost _default_:8080>
   DocumentRoot "C:/Program Files/nusphere/apache/htdocs"
   DirectoryIndex index.html index.htm index.cgi index.pl
   AddHandler cgi-script .cgi
   <Location /myscripts>
   Options All MultiViews
    </Location>
    <FilesMatch "*.pl">
   SetHandler perl-script
   PerlHandler Apache::Registry
   PerlSendHeader On
    </FilesMatch>
   </VirtualHost>

The code will run in the myscripts directory which is in the normal Apache document root, the htdocs folder. Rather than make every file in a directory run as a Perl script, the FilesMatch config directive lets you tell Apache to run any file ending in .pl as Perl code.

Something else worth noting up there is the DirectoryIndex config directive. This tells Apache what file names to look for when the user asks for a URL ending in a directory name. By adding 'index.pl' to the more usual values of index.html and 'index.htm', we can have a Perl script that runs automatically when the user goes to a directory. This makes for cleaner-looking URLs.

So, if you were to take the test script seen above, name it index.pl, and put it in the 'myscripts' directory, it could be accessed from the URL:

   http://localhost:8080/myscripts/index.pl
   or, just
   http://localhost:8080/myscripts/

Next, you can create a database for this simple application. I called mine 'cds'. It has four tables, 'bands', 'cds', 'tracks', and 'genres'. It's about what you'd imagine: a band can have multiple CDs (if they're lucky), and a CD usually has more than one song on it. The genres table is a lookup table to store some descriptive words about the bands and/or the CDs.

Here's the SQL code to create the tables:

  CREATE TABLE bands (
       band_id int NOT NULL auto_increment,
       band_name varchar(255) default NULL,
       band_prefix varchar(50) default NULL,
       band_genre varchar(20) default NULL,
       PRIMARY KEY  (band_id),
       UNIQUE KEY band_name (band_name)
  );

  CREATE TABLE cds (
       cd_id int NOT NULL auto_increment,
       band_id int NOT NULL default '0',
       cd_title varchar(255) default NULL,
       cd_release_year smallint default NULL,
       cd_genre varchar(20) default NULL,
       PRIMARY KEY  (cd_id)
  );

  CREATE TABLE tracks (
       cd_id int NOT NULL default '0',
       track_no tinyint NOT NULL default '0',
       track_name varchar(255) default NULL,
       track_mins tinyint default NULL,
       track_secs tinyint default NULL,
       PRIMARY KEY  (cd_id,track_no)
  );

  CREATE TABLE genres (
       genre varchar(20) NOT NULL default '',
       PRIMARY KEY  (genre),
);

These are all pretty simple, though there is something to say about the 'bands' table. Instead of just one band_name field, it also has a field named band_prefix. This is so we can sort the names properly. You've probably seen this kind of list before:

   ABBA
   Guided By Voices
   Julieta Venegas
   Sugar
   The Archies

Every ex-record-store-clerk knows it ought to be:

   ABBA
   The Archies
   Guided By Voices
   Sugar
   Julieta Venegas

So you should store artist names slightly differently, depending on if it's a band name, a band name starting with "The", or the name of a person:

band_prefix

band_name

ABBA

The

Archies

Guided By Voices

Sugar

Julieta

Venegas

A query can retrieve those rows ordered by band_name, band_prefix and get them in the right order.


TOP

Creating a Simple Application

Now that I have a database, I need some code to put data into it and get it back out again.

I've created a directory named cds in the myscripts directory, and the code will run from a file there placed there. You can find the entire script here. Just copy the contents to the appropriate directory and name it as you wish. All the code for the application is in the one file. Partly, this is because it's not that complex and doesn't require multiple files. But partly it's because there are functions that are shared among the multiple screens that the code presents to the users, and using a library file to store those functions presents us with something of a problem - at least during development.

If my script calls a library file named 'myfuncs.pm', say, like so:

   require "$my_full_path/myfuncs.pm";

that file is loaded into Apache just once during the lifetime of the server process, and not over and over again every time a script that calls the library is run - as with a CGI script. So it runs much faster and this is great when your applications are live.

But, if you're fixing bugs and generally making it up as you go, the only way to make your changes take effect is to restart Apache. That's fine, for about the first 50 times. Then it gets tiresome.

If I was going to actually use this application, now that it's done, I would take the functions out of the index.pl file and put them into a library file in the directory, to take advantage of the way that mod_perl works.

The structure of the script is not complex:

  • connect to the database
  • get static lookup info (the list of genres) that gets used in a lot of different places
  • print out HTML header information and some navigation links
  • print out the content of the page, based (possibly) on fields passed in from a form, or in the URL
  • print out HTML footer information
  • disconnect from the database
  • exit

The fourth bullet point requires some more explanation. If you look at the script you'll see, starting on line 60, the do_something function. This function goes through a list of actions that the script might take. The function evaluates information that comes from either from data from a parameter in URL that I've called action.

For example, if the URL that accesses the script looks like this:

 http://localhost/myscripts/cds/index.pl?action=edit_cd&cd_id;=0
 

the following portion of the do_something() function (on line 112) will test true

 if ($action eq "edit_cd")
 {
      print edit_cd($cgi,$dbh,$genres);
 }

and the edit_cd() function will run, the results of which will be printed. I've also included the action parameter in the values of the submit buttons of the forms used to insert and edit CDs. If you look through the do_something() function, reading the comments as you go, you should see that the other functions in the script are called in a manner similar to the above example.


TOP

Writing an Input/Error Form

If you continue with the example given above, where the action is edit_cd and the cd_id is 0, you end up at the edit_cd() function, which starts on line 154. The first if branch looks for the existence of a cd_id:

        if ($cd_id)
        {

But since the cd_id is 0, this will test false. As you can see in the script, if it had tested true, information regarding the CD would have been retrieved from the database, and that data would have been added to the forms displayed in the HTML page.

Output from the form data starts on line 245. From that line to the end of the function, you can see that there is a lot of conditional information that can be included in the form, depending on what data are available. Finally, on line line 360, a couple of submit buttons are printed. These will carry the parameter action and will have a value of either "Delete CD" or "Save CD". Note that the Delete CD submit button will be available only if a cd_id is available. That is, if there is no cd_id, then this must be a new entry for the database and therefore there's nothing to delete.

Wrapping Up

The above section covered just one portion of the script. The best way to see the rest of what this small application can do -- and learn a few things about mod_perl -- is to use this application with your NuSphere install. Then look through the source code, including the comments, to see how the script is reacting to input from the user and responses from the database.

Though this is a small example, it's a pretty good one. Your own Perl scripts might be larger but for the most part, they'll be doing the same kinds of things.

Good luck and good coding.


TOP

About NuSphere Corporation

NuSphere delivers the first Internet Application Platform (IAP) based on open source compo-nents, providing an integrated foundation that allows companies to deploy reliable, cost-effective, enterprise-class applications across Windows, UNIX and Linux environments. NuSphereŽ Advantage is an integrated software suite that pairs the reliability and cost-effectiveness of PHP, Apache, Perl and open source databases with new technology for build-ing business-critical web applications and web services. The company's commercial software services include technical support, consulting and training. For more information, visit www.nusphere.com.


NuSphere is a registered trademark in Australia, Norway, Hong Kong, Switzerland, and the European Community; NuSphere and PhpED are trademarks of NuSphere Corporation in the U.S. and other countries. Any other trademarks or service marks contained herein are the property of their respective owners.

MySQL AB distributes the MySQL database pursuant to the applicable GNU General Public License that is available as of the date of this publication at http://www.fsf.org/licenses/gpl.txt and all of the terms and disclaimers contained therein. NuSphere Corporation is not affiliated with MySQL AB. The products and services of NuSphere Corporation are not sponsored or endorsed by MYSQL AB.