CGIWrapper version 1.0.2
The CGI Wrapper is a CGI script used to execute other Python CGI scripts. The wrapper provides convenient access to form fields and headers, exception catching, and usage and performance logging. Hooks are provided for cookies and class-based CGI scripts.
The CGI Wrapper is a single CGI script used to execute other Python CGI scripts. A typical URL for a site that uses the wrapper looks like this:
http://www.somesite.com/server.cgi/SignUp
The server.cgi part is the CGI Wrapper and the SignUp part is the target Python script, whose real filename is SignUp.py. Also, that file is located in a directory named Scripts/, but there's no need for the user of the site to know about Scripts/ or .py; those are implementation details.
The wrapper provides the following benefits:
You don't have to immediately write code to play with CGI Wrapper. There are several samples included. See Running and Testing below.
The globals set up by the wrapper for the target CGI script are:
Global | Type/Class | Description |
---|---|---|
headers |
dictionary | Contains all the HTTP headers that will be sent back to the client. The default contents are 'Content-type': 'text/html'. Often, the headers don't need to be modified at all. One popular use of the headers is 'Redirect': 'someURL' to point the client to a different place. |
fields |
cgi.FieldStorage | This instance of FieldStorage comes from the standard Python cgi module. Typical uses include fields.has_key('someField') and fields['someField'].value . See the Python standard module documentation for cgi for more information. |
environ |
dictionary | This dictionary represents the environment variables passed to the CGI scripts. Scripts should use this rather than os.environ since future versions of CGI Wrapper could be tightly integrated into web servers, thereby changing the nature of how environment variables get passed around (e.g., no longer through the OS). Also, note that the environment may seem a little non-standard to the target CGI script since the web server is setting it up to run the CGI Wrapper instead. In most CGI scripts (that execute under the wrapper), the environment is not even needed. |
wrapper |
CGIWrapper | This is a pointer back to the CGI Wrapper instance. This allows CGI scripts to communicate with the wrapper if they want. However, this is hardly ever needed. |
cookies |
Cookie | This global is not set up by the wrapper, but is looked for upon exit of the CGI script. See the Cookies section below for more information. |
One of the main benefits of the wrapper is the handling of uncaught exceptions raised by target CGI scripts. The typical behavior of the wrapper upon detecting an uncaught exception is:
stderr
. This information will typically appear in the web server's error log.SaveErrorMessages
setting is true (the default). They are stored in the directory named by the ErrorMessagesDir
(defaults to 'ErrorMsgs').EmailErrors
setting is true, using the settings ErrorEmailServer
and ErrorEmailHeaders
.Here is a sample error page.
Archived error messages can be browsed through the administration page.
Error handling behavior can be configured as described in Configuration.
There are several configuration parameters through which you can alter how CGI Wrapper behaves. They are described below, including their default values:
= 'Examples'
Scripts
directory instead of putting your scripts in the Examples
directory.
= 1
= []
sys.path
. This setting is useful if you have one or more modules that are shared by your CGI scripts that expect to be able to import them.
= 1
sys.path
where the ExtraPath
value is inserted. Often the first path in sys.path is '.'
which is why the default value of ExtraPathsIndex
is 1
.
= 1
= 'Scripts.csv'
LogScripts
is true. If the filename is not an absolute path, then it is relative to the directory of the CGI Wrapper.
= ['environ.REMOTE_ADDR',
'environ.REQUEST_METHOD', 'environ.REQUEST_URI',
'responseSize', 'scriptName', 'serverStartTimeStamp',
'serverDuration', 'scriptDuration', 'errorOccurred']
obj1.obj2.attr
).
= ['', 'Page']
_admin
in admin.py
). See Class-based CGIs below.
= 1
= 'The site is having technical difficulties with this page. An error has been logged, and the problem will be fixed as soon as possible. Sorry!'
= 1
= 'Errors.csv'
LogErrors
is true.
= 1
= 'ErrorMsgs'
SaveErrorMessages
is true.
= 0
= 'localhost'
=
{ 'From': 'webware@mydomain', 'To': [webware@mydomain'], 'Reply-to': 'webware@mydomain', 'Content-type': 'text/html', 'Subject': 'Error' }The e-mail MIME headers used for e-mailing error messages. Be sure to configure 'From', 'To' and 'Reply-to' before using this feature.
= ['127.0.0.1']
You can override any of these values by creating a CGIWrapper.config
file in the same directory as the wrapper and selectively specifying values in a dictionary like so:
{ 'ExtraPaths': ['Backend', 'ThirdParty'], 'ScriptLog': 'Logs/Scripts.csv' }
Let's assume you have a web server running on a Unix box and a public HTML directory in your home directory. First, make a link from your public HTML directory to the source directory of the CGI Wrapper:
cd ~/public_html
ln -s ~/Projects/Webware/CGIWrapper pycgi
Note that in the Source directory there is an Examples directory and that the CGI Wrapper will automatically look there (you can configure this; see Configuration). Therefore you can type a URL like:
http://localhost/~echuck/pycgi/server.cgi/Hello
Note that you didn't need to include Examples in the page or a .py at the end.
There is a special CGI example called Directory.py that lists the other examples as links you can click on to run or view source. In this way, you can see the full set of scripts.
http://localhost/~echuck/pycgi/server.cgi/Directory
The resulting page will look something like the following. (Note: Those links aren't real!)
Size | Script | View | ||
---|---|---|---|---|
96 | Hello | view | ||
167 | Time | view | ||
210 | Error | view | ||
565 | View | view | ||
802 | Introspect | view | ||
925 | Colors | view | ||
1251 | Directory | view |
The above instructions rely on your web server executing any files that end in .cgi
. However, some servers require that executable scripts are also located in a special directory (such as cgi-bin
) so you may need to take that into consideration when getting CGI Wrapper to work. Please consult your web server admin or your web server docs. You may also have to specify the exact location of the Python interpreter in the first line of the server.cgi
script, particularly under Windows.
CGI Wrapper comes with a script for administration purposes. You can access it by specifying the _admin
script in the URL. You typically only have to remember _admin
because it contains links to the other scripts.
Note that access to the admin scripts is restricted to the local host by default, but you can add more hosts or networks for administrators in the configuration.
From the administration page, you can view the script log, the error log and the configuration of the server. The error log display also contains links to the archived error messages so that you can browse through them.
The administration scripts are good examples of class-based CGIs so you may wish to read through their code.
Here's an example of the admin page.
The script log uses the comma-separated-value (CSV) format, which can be easily read by scripts, databases and spreadsheets. The file is located in the same directory as the CGI Wrapper. The columns are fairly self-explanatory especially once you look at actual file. The Configuration section has more details under the ScriptLogColumns setting.
As you write CGI scripts, and especially if they are for the same site, you may find that they have several things in common. For example, the generated pages may all have a common toolbar, heading and/or footing. You might also find that you display programmatically collected data in a similar fashion throughout your pages.
When you see these kinds of similarities, it's time to start designing a class hierarchy that takes advantage of inheritance, encapsulation and polymorphism in order to save you from duplicative work.
For example your base class could have methods header()
, body()
and footer()
with the header and footer being fully implemented. Subclasses would then only need to override body()
and would therefore inherit their look and feel from one source. You could take this much farther by providing several utility methods in the base class that are available to subclasses for use or customization.
CGI Wrapper provides a hook to support such class-based CGI scripts by checking for certain classes in the target script. The ClassNames
setting, whose default value is ['', 'Page']
, controls this behavior. After a script executes, CGI Wrapper checks these classes. The empty string is a special case which specifies a class whose name is the same name as its containing script (e.g., the class _admin
in the script _admin.py
).
If a matching class is found, it is automatically instantiated so that you don't have to do so in every script. The instantiation is basically:
print TheClass(info).html()
Where info
has keys ['wrapper', 'fields', 'environ', 'headers'].
A good example of class-based CGIs are the admin pages for CGI Wrapper. Start by reading AdminPage.py and then continuing with the various admin scripts such as _admin.py and _showConfig.py. All of these are located in the same directory as CGI Wrapper.
On a final note, if you find that you're developing a sophisticated web-based application with accounts, sessions, persistence, etc. then you should consider using the WebKit, which is analogous to Apple's WebObjects and Sun's Java Servlets.
CGI Wrapper assumes that a URL with no extension (such .html) is a Python script. However, if the URL does contain an extension, the wrapper simply passes it through via HTTP redirection (e.g., the Location:
header).
This becomes important when one of your CGI scripts writes a relative URL to a non-CGI resource. Such a relative URL ends up forcing server.cgi
to come into play.
Cookies are often an important part of web programming. CGI Wrapper does not provide explicit support for cookies, however, it provides an easily utilized hook for them.
If upon completion, the target script has a cookies
global variable, the CGI Wrapper will print it to stdout. This fits in nicely with the Cookie module written by Timothy O'Malley that is part of the standard library since Python 2.0. There is also a copy of this module in the WebUtils package of Webware for Python.
This is just a note that CGI Wrapper is a class with well defined, broken-out methods. If it doesn't behave precisely as you need, you may very well be able to subclass it and override the appropriate methods. See the source which contains numerous doc strings and comments.
Any script starting with an underscore ('_') is considered private to CGI Wrapper and is expected to be found in the CGI Wrapper's directory (as opposed to the directory named by the ScriptsHomeDir
setting).
The most famous private script is the admin script which then contains links to others:
http://localhost/~echuck/pycgi/server.cgi/_admin
A second script is _dumpCSV
which dumps the contents of a CSV file (such as the script log or the error log).
server.cgi - Just a Python script with a generic name (that appears in URLs) that imports CGIWrapper.py. By keeping the CGIWrapper.py file separate we get byte code caching (CGIWrapper.pyc) and syntax highlighting when viewing or editing the script.
CGIWrapper.py - The main script that does the work.
CGIWrapper.config - An optional file containing a dictionary that overrides default configuration settings.
Scripts.csv - The log of script executions as described above.
Errors.csv - The log of uncaught exceptions including date & time, script filename and archived error message filename.
ErrorMsgs/Error-scriptname-YYYY-MM-DD-*.py - Archived error messages.
_*.py - Administration scripts for CGI Wrapper.
Note: CGI scripts are fine for small features, but if you're developing a full blown web-based application then you typically want more support, persistence and classes. That's where other Webware components like WebKit and MiddleKit come into play.
Here are some future ideas, with no commitments or timelines as to when/if they'll be realized. This is open source, so feel free to jump in!
The following are in approximate order of the author's perceived priority, but the numbering is mostly for reference.
Author: Chuck Esterbrook
Some improvements were made by Christoph Zwerschke.
The idea of a CGI wrapper is based on a WebTechniques article by Andrew Kuchling.