Recipe 2.3. Writing Library Files


Problem

You notice that you're writing the same code to perform common operations in multiple programs.

Solution

Put routines to perform those operations in a library file, and have your programs access the library. Then write the code only once. You might need to set an environment variable so that your scripts can find the library.

Discussion

This section describes how to put code for common operations in library files. Encapsulation (or modularization) isn't really a "recipe" so much as a programming technique. Its principal benefit is that you don't have to repeat code in each program you write. Instead, you just call a routine that's in the library. For example, by putting the code for connecting to the cookbook database into a library routine, you need not write out all the parameters associated with making that connection. Simply invoke the routine from your program, and you're connected.

Connection establishment isn't the only operation you can encapsulate, of course. Later sections in this book develop other utility functions to be placed in library files. All such files, including those shown in this section, can be found under the lib directory of the recipes distribution. As you write your own programs, you'll probably identify several operations that you perform often and that are good candidates for inclusion in a library. The techniques demonstrated in this section will help you write your own library files.

Library files have other benefits besides making it easier to write programs. They can help portability. For example, if you write connection parameters directly into each program that connects to the MySQL server, you have to change all those programs if you move them to another machine that uses different parameters. If instead you write your programs to connect to the database by calling a library routine, you localize the changes that need to be made: it's necessary to modify only the affected library routine, not all the programs that use it.

Code encapsulation also can improve security in some ways. If you make a private library file readable only to yourself, only scripts run by you can execute routines in the file. Or suppose that you have some scripts located in your web server's document tree. A properly configured server will execute the scripts and send their output to remote clients. But if the server becomes misconfigured somehow, the result can be that your scripts are sent to clients as plain text, thus displaying your MySQL username and password. (And you'll probably realize it too late. Oops.) If the code for establishing a connection to the MySQL server is placed in a library file that's located outside the document tree, those parameters won't be exposed to clients.

Be aware that if you install a library file to be readable by your web server, you don't have much security should you share the web server with other developers. Any of those developers can write a web script to read and display your library file because, by default, the script runs with the permissions of the web server and thus will have access to the library.


The examples of programs that follow demonstrate how to write, for each API, a library file that contains a routine for connecting to the cookbook database on the MySQL server. The calling program can use the error-checking techniques discussed in Section 2.2 to determine whether a connection attempt fails. The connection routine for each language except PHP returns a database handle or connection object when it succeeds or raises an exception if the connection cannot be established. The PHP routine returns an object that represents a connection or an error, because that is what the PEAR DB connection method does (it does not raise an exception).

Libraries are of no utility in themselves, so each one's use is illustrated by a short "test harness" program. You can use any of these harness programs as the basis for creating new programs of your own: make a copy of the file and add your own code between the connect and disconnect calls.

Library file writing involves not only the question of what to put in the file but also subsidiary issues such as where to install the file so it can be accessed by your programs, and (on multiuser systems such as Unix) how to set its access privileges so its contents aren't exposed to people who shouldn't see it.

Choosing a library file installation location

If you install a library file in a directory that a language processor searches by default, programs written in that language need do nothing special to access the library. However, if you install a library file in a directory that the language processor does not search by default, you'll have to tell your scripts how to find the library. There are two common ways to do this:

  • Most languages provide a statement that can be used within a script to add directories to the language processor search path. This requires that you modify each script that needs the library.

  • You can set an environment or configuration variable that changes the language processor search path. This approach requires that each user who uses scripts that require the library to set the appropriate variable. Alternatively, if the language processor has a configuration file, you might be able to set a parameter in the file that affects scripts globally for all users.

We'll use the second approach. For our API languages, the following table shows the relevant variables. In each case, the variable value is a directory or list of directories.

LanguageVariable nameVariable type
Perl PERL5LIB Environment variable
Ruby RUBYLIB Environment variable
PHP include_path Configuration variable
Python PYTHONPATH Environment variable
Java CLASSPATH Environment variable


For general information on setting environment variables, see Appendix B. You can use those instructions to set environment variables to the values in the following discussion.

Suppose that you want to install library files in a directory that language processors do not search by default. For purposes of illustration, let's use /usr/local/lib/mcb on Unix or C:\lib\mcb on Windows. (To put the files somewhere else, adjust the pathnames in the variable settings accordingly. For example, you might want to use a different directory, or you might want to put libraries for each language in separate directories.)

Under Unix, if you put Perl library files in the /usr/local/lib/mcb directory, you can set the PERL5LIB environment variable. For a shell in the Bourne shell family (sh, bash, ksh), set the variable like this in the appropriate startup file:

export PERL5LIB=/usr/local/lib/mcb 

NOTE

If you are using the original Bourne shell, sh, you may need to split this into two commands:

PERL5LIB=/usr/local/lib/mcb export PERL5LIB

For a shell in the C shell family (csh, tcsh), set PERL5LIB like this in your .login file:

setenv PERL5LIB /usr/local/lib/mcb 

Under Windows, if you put Perl library files in C:\lib\mcb, you can set PERL5LIB as follows:

PERL5LIB=C:\lib\mcb 

In each case, the variable setting tells Perl to look in the specified directory for library files, in addition to whatever other directories it would search by default. If you set PERL5LIB to name multiple directories, the separator character between directory pathnames is colon (:) on Unix or semicolon (;) on Windows.

The other environment variables (RUBYLIB, PYTHONPATH, and CLASSPATH) are specified using the same syntax.

NOTE

Setting these environment variables as just discussed should suffice for scripts that you run from the command line. But for scripts that are intended to be executed by a web server, you'll likely have to configure the server as well so that it can find the library files. See Section 17.2 for details on how to do this.

For PHP, the search path is defined by the value of the include_path variable in the php.ini PHP initialization file. On Unix, the file's pathname is likely to be /usr/lib/php.ini or /usr/local/lib/php.ini. Under Windows, the file is likely to be found in the Windows directory or under the main PHP installation directory. The value of include_path is defined with a line like this:

include_path = "value" 

value is specified using the same syntax as for environment variables that name directories. That is, it's a list of directory names, with the names separated by colons on Unix or semicolons on Windows. For example, on Unix, if you want PHP to look for include files in the current directory and in /usr/local/lib/mcb, set include_path like this:

include_path = ".:/usr/local/lib/mcb" 

On Windows, to search the current directory and C:\lib\mcb, set include_path like this:

include_path = ".;C:\lib\mcb" 

If you modify the php.ini file, and PHP is running as an Apache module, you'll need to restart Apache to make your changes take effect.

Setting library file access privileges

Questions about file ownership and access mode are issues about which you'll need to make decisions if you're using a multiple-user system such as Unix:

  • If a library file is private and contains code to be used only by you, the file can be placed under your own account and made accessible only to you. Assuming that a library file mylib is already owned by you, you can make it private like this:

    % chmod 600 mylib                      

  • If the library file is to be used only by your web server, you can install it in a server library directory and make the file owned by and accessible only to the server user ID. You may need to be root to do this. For example, if the web server runs as wwwusr, the following commands make the file private to that user:

    # chown wwwusr mylib # chmod 600 mylib                      

  • If the library file is public, you can place it in a location that your programming language searches automatically when it looks for libraries. (Most language processors search for libraries in some default set of directories.) You may need to be root to install files in one of these directories. Then you can make the file world-readable:

    # chmod 444 mylib                      

Now let's construct a library for each API. Each section here demonstrates how to write the library file itself and discusses how to use the library from within programs.

Perl

In Perl, library files are called modules, and typically have an extension of .pm ("Perl module"). Here's a sample module file, Cookbook.pm, that implements a module named Cookbook. (It's conventional for the basename of a Perl module file to be the same as the identifier on the package line in the file.)

package Cookbook; # Cookbook.pm - library file with utility method for connecting to MySQL # via Perl DBI module use strict; use warnings; use DBI; my $db_name = "cookbook"; my $host_name = "localhost"; my $user_name = "cbuser"; my $password = "cbpass"; my $port_num = undef; my $socket_file = undef; # Establish a connection to the cookbook database, returning a database # handle.  Raise an exception if the connection cannot be established. sub connect { my $dsn = "DBI:mysql:host=$host_name"; my %conn_attrs = (PrintError => 0, RaiseError => 1, AutoCommit => 1);   $dsn .= ";database=$db_name" if defined $db_name;   $dsn .= ";mysql_socket=$socket_file" if defined $socket_file;   $dsn .= ";port=$port_num" if defined $port_num;   return (DBI->connect ($dsn, $user_name, $password, \%conn_attrs)); } 1;  # return true 

The module encapsulates the code for establishing a connection to the MySQL server into a connect⁠(⁠ ⁠ ⁠) method, and the package identifier establishes a Cookbook namespace for the module, so you invoke the connect⁠(⁠ ⁠ ⁠) method using the module name:

$dbh = Cookbook::connect (); 

The final line of the module file is a statement that trivially evaluates to true. This is needed because Perl assumes that something is wrong with a module and exits after reading it if the module doesn't return a true value.

Perl locates library files by searching through the directories named in its @INC array. This array contains a default list of directories. To check the value of this variable on your system, invoke Perl as follows at the command line:

% perl -V                

The last part of the output from the command shows the directories listed in the @INC array. If you install a library file in one of those directories, your scripts will find it automatically. If you install the module somewhere else, you need to tell your scripts where to find it by setting the PERL5LIB environment variable, as discussed earlier in the introduction to this recipe.

After installing the Cookbook.pm module, try it from a test harness script harness.pl written as follows:

#!/usr/bin/perl # harness.pl - test harness for Cookbook.pm library use strict; use warnings; use Cookbook; my $dbh; eval {   $dbh = Cookbook::connect ();   print "Connected\n"; }; die "$@" if $@; $dbh->disconnect (); print "Disconnected\n"; 

harness.pl has no use DBI statement. It's not necessary because the Cookbook.php library file itself imports the DBI module, so any script that uses Cookbook also gains access to DBI.

If you don't want to bother catching connection errors explicitly, you can write the body of the script more simply. In this case, Perl will catch any connection exception and terminate the script after printing the error message generated by the connect⁠(⁠ ⁠ ⁠) method:

my $dbh = Cookbook::connect (); print "Connected\n"; $dbh->disconnect (); print "Disconnected\n"; 

Ruby

The following Ruby library file, Cookbook.rb, defines a Cookbook class that implements a connect method:

# Cookbook.rb - library file with utility method for connecting to MySQL # via Ruby DBI module require "dbi" # Establish a connection to the cookbook database, returning a database # handle.  Raise an exception if the connection cannot be established. class Cookbook   @@host = "localhost"   @@db_name = "cookbook"   @@user_name = "cbuser"   @@password = "cbpass"   # class method for connecting to server to access   # cookbook database; returns database handle object.   def Cookbook.connect       return DBI.connect("DBI:Mysql:host=#{@@host};database=#{@@db_name}",                          @@user_name, @@password)   end end 

The connect method is defined in the library as Cookbook.connect because Ruby class methods are defined as class_name.method_name.

Ruby locates library files by searching through the directories named in its $LOAD_PATH variable (also known as $:), which is an array that contains a default list of directories. To check the value of this variable on your system, use Ruby to execute this statement:

puts $LOAD_PATH 

If you install a library file in one of those directories, your scripts will find it automatically. If you install the file somewhere else, you need to tell your scripts where to find it by setting the RUBYLIB environment variable, as discussed earlier in the introduction to this recipe.

After installing the Cookbook.rb library file, try it from a test harness script harness.rb written as follows:

#!/usr/bin/ruby -w # harness.rb - test harness for Cookbook.rb library require "Cookbook" begin   dbh = Cookbook.connect   print "Connected\n" rescue DBI::DatabaseError => e   puts "Cannot connect to server"   puts "Error code: #{e.err}"   puts "Error message: #{e.errstr}"   exit(1) end dbh.disconnect print "Disconnected\n" 

harness.rb has no require statement for the DBI module. It's not necessary, because the Cookbook module itself imports DBI, so any script that imports Cookbook also gains access to DBI.

If you just want a script to die if an error occurs without checking for an exception yourself, write the body of the script like this:

dbh = Cookbook.connect print "Connected\n" dbh.disconnect print "Disconnected\n" 

PHP

The contents of PHP library files are written like regular PHP scripts. You can write such a file, Cookbook.php, that implements a Cookbook class with a connect⁠(⁠ ⁠ ⁠) method as follows:

<?php # Cookbook.php - library file with utility method for connecting to MySQL # via PEAR DB module require_once "DB.php"; class Cookbook {   # Establish a connection to the cookbook database, returning a   # connection object, or an error object if an error occurs.   function connect ()   {     $dsn = array (              "phptype"  => "mysqli",              "username" => "cbuser",              "password" => "cbpass",              "hostspec" => "localhost",              "database" => "cookbook"            );     return (DB::connect ($dsn));   } } # end Cookbook ?> 

Although most PHP examples throughout this book don't show the <?php and ?> tags, I've shown them as part of Cookbook.php here to emphasize that library files must enclose all PHP code within those tags. The PHP interpreter doesn't make any assumptions about the contents of a library file when it begins parsing it because you might include a file that contains nothing but HTML. Therefore, you must use <?php and ?> to specify explicitly which parts of the library file should be considered as PHP code rather than as HTML, just as you do in the main script.

PHP looks for libraries by searching the directories named in the value of the include_path variable in the PHP initialization file, as described earlier in the introduction to this recipe. Assuming that Cookbook.php is installed in one of those directories, you can access it from a test harness script harness.php written as follows:

<?php # harness.php - test harness for Cookbook.php library require_once "Cookbook.php"; $conn =& Cookbook::connect (); if (PEAR::isError ($conn))   die ("Cannot connect to server: " . $conn->getMessage () . "\n"); print ("Connected\n"); $conn->disconnect (); print ("Disconnected\n"); ?> 

harness.php has no statement to include DB.php. It's not necessary because the Cookbook module itself includes DB.php, which gives any script that includes Cookbook.php access to DB.php.

Where Should PHP Library Files Be Installed?

PHP scripts often are placed in the document tree of your web server, and clients can request them directly. For PHP library files, I recommend that you place them somewhere outside the document tree, especially if (like Cookbook.php) they contain usernames and passwords. This is particularly true if you use a different extension such as .inc for the names of include files. If you do that and install include files in the document tree, they might be requested directly by clients and be displayed as plain text, exposing their contents. To prevent that from happening, reconfigure Apache so that it treats files with the .inc extension as PHP code to be processed by the PHP interpreter rather than being displayed literally.


Python

Python libraries are written as modules and referenced from scripts using import or from statements. To create a method for connecting to MySQL, we can write a module file Cookbook.py:

# Cookbook.py - library file with utility method for connecting to MySQL # via MySQLdb module import MySQLdb host_name = "localhost" db_name = "cookbook" user_name = "cbuser" password = "cbpass" # Establish a connection to the cookbook database, returning a connection # object.  Raise an exception if the connection cannot be established. def connect ():   return MySQLdb.connect (db = db_name,                           host = host_name,                           user = user_name,                           passwd = password) 

The filename basename determines the module name, so the module is called Cookbook. Module methods are accessed through the module name; thus you would invoke the connect⁠(⁠ ⁠ ⁠) method of the Cookbook module like this:

conn = Cookbook.connect (); 

The Python interpreter searches for modules in directories named in the sys.path variable. You can find out what the default value of sys.path is on your system by running Python interactively and entering a couple of commands:

% python >>> import sys >>> sys.path                

If you install Cookbook.py in one of the directories named by sys.path, your scripts will find it with no special handling. If you install Cookbook.py somewhere else, you'll need to set the PYTHONPATH environment variable, as discussed earlier in the introduction to this recipe.

After installing the Cookbook.py library file, try it from a test harness script harness.py written as follows:

#!/usr/bin/python # harness.py - test harness for Cookbook.py library import sys import MySQLdb import Cookbook try:   conn = Cookbook.connect ()   print "Connected" except MySQLdb.Error, e:   print "Cannot connect to server"   print "Error code:", e.args[0]   print "Error message:", e.args[1]   sys.exit (1) conn.close () print "Disconnected" 

NOTE

The Cookbook.py file imports the MySQLdb module, but a script that imports Cookbook does not thereby gain access to MySQLdb. If the script needs MySQLdb-specific information (such as MySQLdb.Error), the script must also import MySQLdb.

If you just want a script to die if an error occurs without checking for an exception yourself, write the body of the script like this:

conn = Cookbook.connect () print "Connected" conn.close () print "Disconnected" 

Java

Java library files are similar to Java programs in most ways:

  • The class line in the source file indicates a class name.

  • The file should have the same name as the class (with a .java extension).

  • You compile the .java file to produce a .class file.

Java library files also differ from Java programs in some ways:

  • Unlike regular program files, Java library files have no main⁠(⁠ ⁠ ⁠) function.

  • A library file should begin with a package identifier that specifies the location of the class within the Java namespace.

A common convention for Java package identifiers is to begin them with the reverse domain of the code author; this helps make identifiers unique and avoids conflict with classes written by other authors. Domain names proceed right to left from more general to more specific within the domain namespace, whereas the Java class namespace proceeds left to right from general to specific. Thus, to use a domain as the prefix for a package name within the Java class namespace, it's necessary to reverse it. In my case, the domain is kitebird.com, so if I write a library file and place it under mcb within my domain's namespace, the library begins with a package statement like this:

package com.kitebird.mcb; 

Java packages developed for this book are placed within the com.kitebird.mcb namespace to ensure their uniqueness in the package namespace.

The following library file, Cookbook.java, defines a Cookbook class that implements a connect⁠(⁠ ⁠ ⁠) method for connecting to the cookbook database. connect⁠(⁠ ⁠ ⁠) returns a Connection object if it succeeds, and throws an exception otherwise. To help the caller deal with failures, the Cookbook class also defines getErrorMessage⁠(⁠ ⁠ ⁠) and printErrorMessage⁠(⁠ ⁠ ⁠) utility methods that return the error message as a string or print it to System.err.

// Cookbook.java - library file with utility method for connecting to MySQL // via MySQL Connector/J package com.kitebird.mcb; import java.sql.*; public class Cookbook {   // Establish a connection to the cookbook database, returning   // a connection object.  Throw an exception if the connection   // cannot be established.   public static Connection connect () throws Exception   {     String url = "jdbc:mysql://localhost/cookbook";     String user = "cbuser";     String password = "cbpass";     Class.forName ("com.mysql.jdbc.Driver").newInstance ();     return (DriverManager.getConnection (url, user, password));   }   // Return an error message as a string   public static String getErrorMessage (Exception e)   {     StringBuffer s = new StringBuffer ();     if (e instanceof SQLException)  // JDBC-specific exception?     {       // print general message, plus any database-specific message       s.append ("Error message: " + e.getMessage () + "\n");       s.append ("Error code: " + ((SQLException) e).getErrorCode () + "\n");     }     else     {       s.append (e + "\n");     }     return (s.toString ());   }   // Get the error message and print it to System.err   public static void printErrorMessage (Exception e)   {     System.err.println (Cookbook.getErrorMessage (e));   } } 

The routines within the class are declared using the static keyword, which makes them class methods rather than instance methods. That is done here because the class is used directly rather than creating an object from it and invoking the methods through the object.

To use the Cookbook.java file, compile it to produce Cookbook.class, and then install the class file in a directory that corresponds to the package identifier. This means that Cookbook.class should be installed in a directory named com/k⁠i⁠t⁠ebird/mcb (or com\kitebird\mcb under Windows) that is located under some directory named in your CLASSPATH setting. For example, if CLASSPATH includes /usr/local/lib/mcb under Unix, you can install Cookbook.class in the /usr/local/lib/mcb/com/kitebird/mcb directory. (See the Java discussion in Section 2.1 for more information about the CLASSPATH variable.)

To use the Cookbook class from within a Java program, import it, and then invoke the Cookbook.connect⁠(⁠ ⁠ ⁠) method. The following test harness program, Harness.java, shows how to do this:

// Harness.java - test harness for Cookbook library class import java.sql.*; import com.kitebird.mcb.Cookbook; public class Harness {   public static void main (String[] args)   {     Connection conn = null;     try     {       conn = Cookbook.connect ();       System.out.println ("Connected");     }     catch (Exception e)     {       Cookbook.printErrorMessage (e);       System.exit (1);     }     finally     {       if (conn != null)       {         try         {           conn.close ();           System.out.println ("Disconnected");         }         catch (Exception e)         {           String err = Cookbook.getErrorMessage (e);           System.out.println (err);         }       }     }   } } 

Harness.java also shows how to use the error message utility methods from the Cookbook class when a MySQL-related exception occurs:

  • printErrorMessage⁠(⁠ ⁠ ⁠) takes the exception object and uses it to print an error message to System.err.

  • getErrorMessage⁠(⁠ ⁠ ⁠) returns the error message as a string. You can display the message yourself, write it to a logfile, or whatever.




MySQL Cookbook
MySQL Cookbook
ISBN: 059652708X
EAN: 2147483647
Year: 2004
Pages: 375
Authors: Paul DuBois

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net