Table of Contents
This document is also available in the following languages: Italian, Russian
Document revision: $Revision: 532699 $, last modified $Date: 2007-04-26 12:50:39 +0200 (Thu, 26 Apr 2007) $ by $Author: davidw $.
Apache Rivet is a system for creating dynamic web content via a programming language integrated with Apache Web Server. It is designed to be fast, powerful and extensible, consume few system resources, be easy to learn, and to provide the user with a platform that can also be used for other programming tasks outside the web (GUI's, system administration tasks, text processing, database manipulation, XML, and so on). In order to meet these goals, we have chosen the Tcl programming language to combine with the Apache Web Server.
In this manual, we aim to help get you started, and then writing productive code as quickly as possible, as well as giving you ideas on how to best take advantage of Rivet's architecture to create different styles of web site.
This documentation is a work in progress, and, like everything else about Apache Rivet, it is Free Software. If you see something that needs improving, and have ideas or suggestions, don't hesitate to let us know. If you want to contribute directly, better yet!
Check Dependencies
To install Rivet, you will need Tcl 8.4 or greater and Apache 1.3.xx. It is known to run on Linux, FreeBSD, OpenBSD, and Solaris and HPUX. Windows NT is also possible - please see the directions in the distribution. Note that Rivet does not currently work with Apache 2.
Get Rivet
Download the sources at http://tcl.apache.org/rivet/download. Currently the only way to obtain Rivet. In the future, we hope to have a FreeBSD port, Debian package, RPM's, and windows binaries.
Install Tcl
If you don't have Tcl already, you need it! If you already have it, you should just be able to use your system Tcl as long as it is recent. You can tell Rivet where Tcl is via the -with-tclconfig option to configure.tcl (see below).
Get and Install Apache Sources
Rivet needs some Apache include (.h) files in order to build. The easiest way to get them is to download the source code of the Apache web server, although some systems (Debian GNU/Linux for example) make it possible to install only the headers and other development files. If you intend to build Rivet statically (compiled into the Apache web server instead of loaded dynamically), you definitely need the sources. We recommend that you build Rivet as a loadable shared library, for maximum flexibility, meaning that you also build Apache to be able to load modules. Other than that, the default Apache install is fine. We will tell Rivet where it is located via the -with-apxs option to configure.tcl (see below).
The source code for the Apache web server may be found by following the links here: http://httpd.apache.org/.
Uncompress Sources
We will assume that you have Apache installed at this point. You must uncompress the Rivet sources in the directory where you wish to compile them.
gunzip tcl-rivet-X.X.X.tar.gz tar -xvf tcl-rivet-X.X.X.tar.gz
Building Rivet
On Linux or Unix systems, Rivet uses the standard ./configure ; make ; make install technique.
There are several options to configure that might be useful or necessary:
tclConfig.sh
file is located.
tclsh
executable.apxs
program that provides information about the
configuration and compilation of Apache modules.
cd src/ ./configure --with-tcl=/usr/lib/tcl8.4/ --with-tclsh=/usr/bin/tclsh8.4 \ --with-apxs=/usr/bin/apxs
Run make
At this point, you are ready to run make, which should run to completion without any errors (a warning or two is ok, generally).
Install
Now, you are ready to run the make
install to install the resulting files.
This should copy the shared object (like
mod_rivet.so
, if one was
successfully created, into Apache's
libexec
directory, as well as
install some support scripts and various code.
Apache Configuration Files
Rivet is relatively easy to configure - we start off by adding the module itself:
LoadModule rivet_module /usr/lib/apache/1.3/mod_rivet.so
This tells Apache to load the Rivet shared object, wherever it happens to reside on your file system. Now we have to tell Apache what kind of files are "Rivet" files and how to process them:
AddType application/x-httpd-rivet .rvt AddType application/x-rivet-tcl .tcl
These tell Apache to process files with the
.rvt
and .tcl
extensions as Rivet files.
You may also wish to use Rivet files as index files for directories. In that case, you would do the following:
DirectoryIndex index.html index.htm index.shtml index.cgi index.tcl index.rvt
For other directives that Rivet provides for Apache configuration, please see the section called “Rivet Apache Directives”.
These directives are used within the Apache httpd server configuration files to modify Apache Rivet's behavior. Their precedence is as follows: RivetDirConf, RivetUserConf, RivetServerConf, meaning that DirConf will override UserConf, which will in turn override ServerConf.
size
?size
is
the number of byte-compiled pages to be cached for
future use. Default is
MaxRequestsPerChild / 5, or 50,
if MaxRequestsPerChild is 0.
script
?script
is an actual Tcl script, so to run a file, you would
do:
RivetServerConf GlobalInitScript "source /var/www/foobar.tcl"
script
?script
?script
?![]() | Note |
---|---|
This code is evaluated at the global level, not inside the request namespace where pages are evaluated. |
script
?script
?directory
?size
?The var command retrieves information about GET or POST variables sent to the script via client request. It treats both GET and POST variables the same, regardless of their origin. Note that there are two additional forms of var: var_qs and var_post. These two restrict the retrieval of information to parameters arriving via the querystring (?foo=bar&bee=bop) or POSTing, respectively.
varname
? ??default?
?varname
as a string (even if there are multiple values). If
the variable doesn't exist as a GET or POST
variable, the
?default?
value is returned, otherwise "" - an empty string -
is returned.
varname
?varname
as a
list, if there are multiple values.
varname
?varname
exists, 0 if it doesn't.
The upload command is for file upload manipulation. See the relevant Apache Directives to further configure the behavior of this Rivet feature.
uploadname
?uploadname
,
returns a Tcl channel that can be used to access the
uploaded file.
uploadname
? ?filename
?uploadname
in
the file
filename
.
uploadname
?uploadname
?uploadname
?Content-type
is set, it is
returned, otherwise, an empty string.
uploadname
?arrayName
?array_name
?Load the headers that come from a client request into the provided array name, or use headers if no name is provided.
array_name
?Load the array of cookie variables into the specified array name. Uses array cookies by default.
array_name
?Load the array of environment variables into the specified array name. Uses array ::request::env by default.
As Rivet pages are run in the ::request namespace, it isn't necessary to qualify the array name for most uses - it's ok to access it as env.
varName
?If it is only necessary to load one environmental variable, this command may be used to avoid the overhead of loading and storing the entire array.
filename_name
?Include a file without parsing it for processing tags <? and ?>. This is the best way to include an HTML file or any other static content.
filename
?Like the Tcl source command, but also parses for Rivet <? and ?> processing tags. Using this command, you can use one .rvt file from another.
The headers command is for setting and parsing HTTP headers.
headername
? ?value
?uri
?headername
? ?value
?headername
.content-type
?Content-type
header returned by the script, which is useful if you wish
to send content other than HTML with Rivet - PNG or jpeg
images, for example.
response code
?filename
?Create a self referencing URL from a filename. For example:
makeurl /tclp.gif
returns
http://[hostname]:[port]/tclp.gif
.
where hostname and port are the hostname and port of the
server in question.
cookieName
? ??cookiValue?
? ?-days expireInDays
? ?-hours expireInHours
? ?-minutes expireInMinutes
? ?-expires Wdy, DD-Mon-YYYY HH:MM:SS GMT
? ?-path uriPathCookieAppliesTo
? ?-secure 1/0
?cookieName
?cookie gets or sets a cookie. When you get a cookie, the command returns the value of the cookie, or an empty string if no cookie exists.
seconds
?Convert an integer-seconds-since-1970 click value to RFC850 format, with the additional requirement that it be GMT only.
string
? ?arg
...?Print text with the added ability to pass HTML tags following the string. Example:
html "Test" b i
produces: <b><i>Test</i></b>
varname
? ?num
?
Increment a variable
varname
by
num
. If the
variable doesn't exist, create it instead of returning an
error.
arrayName
? ??pattern?
?An html version of the standard Tcl parray command. Displays the entire contents of an array in a sorted, nicely-formatted way. Mostly used for debugging purposes.
This command flushes the output buffer and stops the Tcl script from sending any more data to the client. A normal Tcl script might use the exit command, but that cannot be used in Rivet without actually exiting the apache child process!
This command is useful for situations where it is necessary to only return HTTP headers and no actual content. For instance, when returning a 304 redirect.
Scans through each character in the specified string looking for special characters, escaping them as needed, mapping special characters to a quoted hexadecimal equivalent, returning the result.
This is useful for quoting strings that are going to be part of a URL.
Scans through each character in the specified string looking for any special (with respect to SGML, and hence HTML) characters from the specified string, and returns the result. For example, the right angle bracket is escaped to the corrected ampersand gt symbol.
Scans through each character in the specified string looking for any shell metacharacters, such as asterisk, less than and greater than, parens, square brackets, curly brackets, angle brackets, dollar signs, backslashes, semicolons, ampersands, vertical bars, etc.
For each metacharacter found, it is quoted in the result by prepending it with a backslash, returning the result.
Scans through each character in the specified string looking for escaped character sequences (characters containing a percent sign and two hexadecimal characters, unescaping them back to their original character values, as needed, also mapping plus signs to spaces, and returning the result.
This is useful for unquoting strings that have been quoted to be part of a URL.
Some examples of Rivet usage follow. Some prior Tcl knowledge is assumed. If you don't know much Tcl, don't worry, it's easy, and there are some good resources available on the web that will get you up to speed quickly. Go to the web sites section and have a look.
Example 1. Hello World
As with any tool, it's always nice to see something work, so let's create a small "Hello World" page.
Assuming you have Apache configured correctly, create a file
called hello.rvt
where Apache can find
it, with the following content:
<? puts "Hello World" ?>
If you then access it with your browser, you should see a blank page with the text "Hello World" (without the quotes) on it.
Example 2. Generate a Table
In another simple example, we dynamically generate a table:
<? puts "<table>\n" for {set i 1} { $i <= 8 } {incr i} { puts "<tr>\n" for {set j 1} {$j <= 8} {incr j} { set num [ expr $i * $j * 4 - 1] puts [ format "<td bgcolor=\"%02x%02x%02x\" > $num $num $num </td>\n" \ $num $num $num ] } puts "</tr>\n" } puts "</table>\n" ?>
If you read the code, you can see that this is pure Tcl. We could take the same code, run it outside of Rivet, and it would generate the same HTML!
The result should look something like this:
Example 3. Variable Access
Here, we demonstrate how to access variables set by GET or POST operations.
Given an HTML form like the following:
<form action="vars.rvt"> <table> <tbody> <tr> <td><b>Title:</b></td> <td><input name="title"></td> </tr> <tr> <td><b>Salary:</b></td> <td><input name="salary"></td> </tr> <tr> <td><b>Boss:</b></td> <td><input name="boss"></td></tr> <tr> <td><b>Skills:</b></td> <td> <select name="skills" multiple="multiple"> <option>c</option> <option>java</option> <option>Tcl</option> <option>Perl</option> </select> </td> </tr> <tr> <td><input type="submit"></td> </tr> </tbody> </table> </form>
We can use this Rivet script to get the variable values:
<? set errlist {} if { [var exists title] } { set title [var get title] } else { set errlist "You need to enter a title" } if { [var exists salary] } { set salary [var get salary] if { ! [string is digit $salary] } { lappend errlist "Salary must be a number" } } else { lappend errlist "You need to enter a salary" } if { [var exists boss] } { set boss [var get boss] } else { set boss "Mr. Burns" } if { [var exists skills] } { set skills [var list skills] } else { lappend errlist "You need to enter some skills" } if { [llength $errlist] != 0 } { foreach err $errlist { puts "<b> $err </b>" } } else { puts "Thanks for the information!" ?> <table> <tbody> <tr> <td><b>Title:</b></td> <td><? puts $title ?></td> </tr> <tr> <td><b>Boss:</b></td> <td><? puts $boss ?></td> </tr> <tr> <td><b>Salary:</b></td> <td><? puts $salary ?></td> </tr> <tr> <td><b>Skills:</b></td> <td><? puts $skills ?></td> </tr> </tbody> </table> <? } ?>
The first statement checks to make sure that the
boss
variable has been passed to the
script, and then does something with that information. If
it's not present, an error is added to the list of errors.
In the second block of code, the variable
salary
is fetched, with one more error
check - because it's a number, it needs to be composed of
digits.
The boss
variable isn't required to have
been sent - we set it to "Mr. Burns" if it isn't among the
information we received.
The last bit of variable handing code is a bit trickier.
Because skills
is a listbox, and can
potentially have multiple values, we opt to receive them as a
list, so that at some point, we could iterate over them.
The script then checks to make sure that
errlist
is empty and outputting a thankyou
message. If errlist
is not empty, the list
of errors it contains is printed.
Example 4. File Upload
The upload command endows Rivet with an
interface to access files transferred over http as parts of a
multipart form. The following HTML in one file, say,
upload.html
creates a form with a text
input entry. By clicking the file chooser button the file
browser shows up and the user selects the file to be uploaded
(the file path will appear in the text input). In order to make
sure you're uploading the whole file you must combine the
action of the enctype and method attributes of the
<form...> tag in the way shown in the example. Failure
to do so would result in the client sending only the file's
path, rather than the actual contents.
<form action="foo.rvt" enctype="multipart/form-data" method="post"> <input type="file" name="MyUpload"></input> <input type="submit" value="Send File"></input> </form>
In the script invoked by the form
(upload.rvt
) upload
?argument ...? commands can be used to manipulate the
various files uploaded.
<? upload save MyUpload /tmp/uploadfiles/file1 puts "Saved file [upload filename MyUpload] \ ([upload size MyUpload] bytes) to server" ?>
Don't forget that the apache server must have write access to the directory where files are being created. The Rivet Apache directives have a substantial impact on the upload process, you have to carefully read the docs in order to set the appropriate directives values that would match your requirements.
It is also important to understand that some upload commands are effective only when used in a mutually exclusive way. Apache stores the data in temporary files which are read by the upload save ?upload name? ?filename? or by the upload data ?upload name? command. Subsequent calls to these 2 commands using the same ?upload name? argument will return no data on the second call. Likewise upload channel ?upload name? will return a Tcl file channel that you can use in regular Tcl scripts only if you haven't already read the data, for example with a call to the upload data ?upload name? command.
Example 5. File Download
In general setting up a data file to be sent over http is as easy as determining the file's URI and letting Apache's standard download mechanism do all that is needed. If this approach fits your design all you have to do is to keep the downloadable files somewhere within Apache's DocumentRoot (or in any of the directories that you can configure and register using the Alias definitions or the Virtual Hosts mechanism).
When a client sends a request for a file Apache takes care of determining the filetype, sends appropriate headers to the client and then the file content. If the client is a browser capable of displaying the content of the file a representation is shown in the browser's window. When the browser hasn't a valid builtin method or plugin registered for the file's content the typical download dialog pops up asking for directions from the user.
Rivet can help if you have more sofisticated needs. For instance you may be developing an application that uses webpages to collect input data that have to be passed on to scripts or programs. In this case the content is generated on demand and a real file representing the data doesn't exist on the server. In other circumstances you may need to dynamically inhibit the download of a specific file and hide it away, even to those clients that might have already saved the URI to the file in their bookmarks. Your scripts may expunge from the pages every link to the file (your pages are dynamic, aren't they?) and move the file out of way, but it looks like a cumbersome solution.
Putting Tcl and Rivet in charge of the whole download mechanism helps in building cleaner and safer approaches to the download problem.
In this example a procedure checks for the existence of a parameter passed in by the browser. The parameter is the name (without extension) of a pdf file. Pdf files are stored in a directory whose path is in the pdf_repository variable.
# Code example for the transmission of a pdf file. if {[var exists pdfname]} { set pdfname [var get pdfname] # let's build the full path to the pdf file. The 'pdf_repository' # directory must be readable by the apache children set pdf_full_path [file join $pdf_repository ${pdfname}.pdf] if {[file exists $pdf_full_path]} { # Before the file is sent we inform the client about the file type and # file name. The client can be proposed a filename different from the # original one. In this case, this is the point where a new file name # must be generated. headers type "application/pdf" headers add Content-Disposition "attachment; filename=${pdfname}.pdf" headers add Content-Description "PDF Document" # The pdf is read and stored in a Tcl variable. The file handle is # configured for a binary read: we are just shipping raw data to a # client. The following 4 lines of code can be replaced by any code # that is able to retrieve the data to be sent from any data source # (e.g. database, external program, other Tcl code) set paper [open $pdf_full_path r] fconfigure $paper -translation binary set pdf [read $paper] close $paper # Now we got the data: let's tell the client how many bytes we are # about to send (useful for the download progress bar of a dialog box) headers add Content-Length [string length $pdf] # Let's send the actual file content puts $pdf } else { source pdf_not_found_error.rvt } } else { source parameter_not_defined_error.rvt }
Before the pdf is sent the procedure sets the
Content-Type
,
Content-Disposition
,
Content-Description
and
Content-Length
headers to inform
the client about the file type, name and size. Notice that in
order to set the Content-Type
header Rivet
uses a specialiezed form of the headers
command. Headers must be sent before data gets sent down the
output channel. Messing with this prescription causes an error
to be raised (in fact the protocol itself is been violated)
More information about the meaning of the mime headers in the http context can be found at http://www.w3.org/Protocols/rfc2616/rfc2616.html
Example 6. XML Messages and Ajax
The headers command is crucial for generating XML messages that have to be understood by JavaScript code used in Ajax applications.
Ajax is a web programming technique that heavily relies on the abilty of JavaScript (and other scriping languages like VBScript) to manipulate dynamically the HTML structure of a page. In modern browsers JavaScript code is enabled to send GET or POST requests to the webserver. These requests can ask the server to run scripts (for example Rivet Tcl scripts) that build responses and send them back to the browser. JavaScript code reads asynchronously these responses, elaborates their content and accordingly modifies the page on display. Ajax helps to build web applications that are more responsive and flexible. Instead of going through the cycle of request-generation-transfer of a page, Ajax allows the programmer to request and transmit only the essential data thus matching the general requirement of separation between data and user interface (and saving the server from sending over the same html code and graphics every time a page is refreshed)
In Ajax applications the communication between client and server is controlled by an instance of a specialized object: the non-IE world uses the XMLHttpRequest class to create it, whereas IE uses the ActiveXObject class. Through an instance of that class a POST or GET request is sent to the server, which in turn responds with a message that is stored as a string in a property of the communication object called 'returnedText'. Although this sort of communication channel doesn't imply that the messages being transmitted through it have a specific protocol, it's become widely customary to use XML as the format for such messages. A number of XML specification are being used for this, among which XML-RPC and SOAP are worth to be quoted. Anyway, you can invent your own protocol (either based on XML or anything else), but one has to be aware of the fact that if the http headers are properly set and the message returned to the client is a well formed XML fragment, also the property XMLResponse is set with a reference to an object that stores the response as an XML document. This object is an instance of a class that implements the XML W3C DOM tree methods and properties, thus enabling the scripts to read and manipulate the data embedded in the XML message with a structured and standard interface.
In this example a Rivet script initializes an array with the essential data regarding a few of the major composers of the european music. This array plays the role of a database which, in a real case, might store large tables with thousands records and more complete and extended information. The script is designed to send back to the client two types of responses: a catalog of the composers or a single record of a composer.
# The database array contains xml fragments representing the # results of queries to a database. Many databases are now able # to produce the results of a query in XML. # #array unset composer # set composer(1) "<composer>\n" append composer(1) " <first_name>Claudio</first_name>\n" append composer(1) " <last_name>Monteverdi</last_name>\n" append composer(1) " <lifespan>1567-1643</lifespan>\n" append composer(1) " <era>Renaissance/Baroque</era>\n" append composer(1) " <key>1</key>\n" append composer(1) "</composer>\n" set composer(2) "<composer>\n" append composer(2) " <first_name>Johann Sebastian</first_name>\n" append composer(2) " <last_name>Bach</last_name>\n" append composer(2) " <lifespan>1685-1750</lifespan>\n" append composer(2) " <era>Baroque</era>\n" append composer(2) " <key>2</key>\n" append composer(2) "</composer>\n" set composer(3) "<composer>\n" append composer(3) " <first_name>Ludwig</first_name>\n" append composer(3) " <last_name>van Beethoven</last_name>\n" append composer(3) " <lifespan>1770-1827</lifespan>\n" append composer(3) " <era>Romantic</era>\n" append composer(3) " <key>3</key>\n" append composer(3) "</composer>\n" set composer(4) "<composer>\n" append composer(4) " <first_name>Wolfgang Amadaeus</first_name>\n" append composer(4) " <last_name>Mozart</last_name>\n" append composer(4) " <lifespan>1756-1791</lifespan>\n" append composer(4) " <era>Classical</era>\n" append composer(4) " <key>4</key>\n" append composer(4) "</composer>\n" set composer(5) "<composer>\n" append composer(5) " <first_name>Robert</first_name>\n" append composer(5) " <last_name>Schumann</last_name>\n" append composer(5) " <lifespan>1810-1856</lifespan>\n" append composer(5) " <era>Romantic</era>\n" append composer(5) " <key>5</key>\n" append composer(5) "</composer>\n" # we use the 'load' argument in order to determine the type of query # # load=catalog: we have to return a list of the names in the database # load=composer&res_id=<id>: the script is supposed to return the record # having <id> as record id if {[var exists load]} { # the xml declaration is common to every message (error messages included) set xml "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n" switch [var get load] { catalog { append xml "<catalog>\n" foreach nm [array names composer] { if {[regexp {<last_name>(.+)</last_name>} $composer($nm) m last_name] && \ [regexp {<first_name>(.+)</first_name>} $composer($nm) m first_name]} { append xml " <composer key='$nm'>$first_name $last_name</composer>\n" } } append xml "</catalog>" } composer { if {[var exists rec_id]} { set rec_id [var get rec_id] if {[info exists composer($rec_id)]} { append xml $composer($rec_id) } } } } # we have to tell the client this is an XML message. Failing to do so # would result in an XMLResponse property set to null headers type "text/xml" headers add Content-Length [string length $xml] puts $xml }
For sake of brevity the JavaScript and HTML will not listed here. They can be downloaded (along with the Tcl script) stored in the rivet-ajax.tar.gz archive. By simply opening this tar archive in a directory accessible by your apache server and pointing your browser to the rivetService.html page you should see a page with a drop-down list. Every time a different name is picked from the list a new query is sent and logged in the apache access.log file, even though the html is never reloaded.
In addition to the core Apache module, Rivet provides a number of Tcl packages that include potentially useful code.
interface
?objectName
? (-option | option
| -option | option
| ...)DIO is designed to be a generic, object-oriented interface to SQL databases. Its main goal is to be as generic as possible, but since not all SQL databases support the exact same syntaxes, keeping code generic between databases is left to the abilities of the programmer. DIO simply provides a way to keep the Tcl interface generic.
interface - The name of the database interface. Currently supported interfaces are Postgresql and Mysql.
If objectName
is
specified, DIO creates an object of that name. If there is
no objectName
given, DIO will automatically generate a unique object ID
hostname
?portNumber
?username
?password
?database
?tableName
?keyFieldname
?sequenceName
?objectName
?array? ?request
?objectName
?autokey? (value | boolean)objectName
?close?objectName
?count?objectName
?db? ?value
?objectName
?delete? ?key
? (-option | option
| ...)objectName
?destroy?objectName
?errorinfo? ?value?objectName
?exec? ?request
?objectName
?fetch? ?key
? ?arrayName
? (-option | option
| ...)objectName
?forall? ?request
? ?arrayName
? ?body
?objectName
?host? ?value
?objectName
?insert? ?arrayName
? (-option | option
| ...)objectName
?interface?Postgresql
or Mysql
.
objectName
?keyfield? ?value
?objectName
?keys? ?pattern
? (-option | option
| ...)objectName
?lastkey?objectName
?list? ?request?objectName
?makekey? ?arrayName
? ?keyfield
?objectName
?nextkey?objectName
?open?objectName
?pass? ?value
?objectName
?port? ?value
?objectName
?quote? ?string
?objectName
?search? (-option | option
| ...)set res [DIO search -table people -firstname Bob]
objectName
?sequence? ?value
?objectName
?store? ?arrayName
? (-option | option
| ...)objectName
?string? ?request
?objectName
?table? ?value
?objectName
?update? ?arrayName
? (-option | option
| ...)objectName
?user? ?value
?resultObj
?autocache? ?value
?resultObj
?cache?resultObj
?errorcode? ?value
?resultObj
?errorinfo? ?value
?resultObj
?fields? ?value
?resultObj
?forall? ?-type
? ?varName
? ?body
?varName
as an array where the indexes are the names of
the fields in the table and the values are the
values of the current row.
varName
to a list containing key-value pairs of fields
and values from the current row. (-field value
-field value)
varName
to a list that contains the values of the
current row.
resultObj
?next? ?-type
? ?varName
?varName
as an array where the indexes are the names of
the fields in the table and the values are the
values of the current row.
varName
to a list containing key-value pairs of fields
and values from the current row. (-field value
-field value)
varName
to a list that contains the values of the
current row.
resultObj
?numrows? ?value
?resultObj
?resultid? ?value
?resultObj
?rowid? ?value
?objectName
| #auto) (-option | option
| -option | option
| ...)DIODisplay is an HTML display class that uses a DIO object to do the database work and a form object to do the displaying.
dioObject
procName
fieldList
formObject
functionList
pageSize
fieldList
functionList
fieldList
title
objectName
cleanup ?value
?value
is
specified, it sets a new value for the cleanup
option.
objectName
delete key
objectName
destroy objectName
DIO ?value
?value
is
specified, it sets a new value for DIO.
objectName
errorhandler ?value
?value
is specified, it
sets a new value for errorhandler.
objectName
fetch key
arrayName
key
from the database and store it as an array in
arrayName
.
objectName
field fieldName
(-arg | arg
...)objectName
fields ?value
?value
is
specified, it sets a new value for fields.
objectName
form ?value
?value
is
specified, it sets a new value for form.
objectName
function functionName
objectName
functions ?value
?value
is
specified, it sets a new value for functions. See
[FIXME - LINK DIO Display Functions] for a list of
default functions.
objectName
pagesize ?value
?value
is
specified, it sets a new value for pagesize.
objectName
rowfields ?value
?value
is
specified, it sets a new value for rowfields.
objectName
rowfooter objectName
rowfunctions ?value
?value
is
specified, it sets a new value for rowfunctions.
objectName
rowheader objectName
searchfields ?value
?value
is
specified, it sets a new value for searchfields.
objectName
show mode
to be passed in through a form response and uses
that mode to execute the appropriate function. If
mode is not given, the Main
function is called.
objectName
showform objectName
showrow arrayName
arrayName
is a fetched array of the record.
objectName
showview objectName
store arrayName
arrayName
in the database.
objectName
text ?value
?value
is
specified, it sets a new value for text.
objectName
title ?value
?value
is
specified, it sets a new value for title.
objectName
type ?value
?value
is
specified, it sets a new value for type.
objectName
value ?value
?value
is
specified, it sets a new value for value.
These functions are called from the
show method when a form response
variable called mode
is set. If no
mode has been set, the default mode is
Main. The show method will handle
the necessary switching of functions. The show method
also handles checking to make sure the function given is a
true function. If not, an error message is displayed.
New functions can be added as methods or by use of the
function command, and any of the
default functions can be overridden with new methods to
create an entirely new class. These are the default
functions provided.
confirmdelete
is true (the
default), the Delete function
will ask the user if they're sure they want to
delete the record from the database. If
confirmdelete
is false, or if the
user confirms they wish to delete, this function
calls the DoDelete function to do
the actual deletion of a record.
searchBy
field matches
query
. Once any number of records
are found, Search displays the
results in rows.
Display fields are created with the field command of the DIODisplay object. Each field is created as a new DIODisplayField object or as a subclass of DIODisplayField. The standard form fields use the standard field class, while specialized field types use a class with different options but still supports all of the same commands and values a generic field does.
displayObject
field fieldname
(-option | option
...)These are the standard options supported by field types:
arguments
formargs
variable. You can use
this option to override or add options after the
initial creation of an object
readonly
is set to true, the
field will not display a form entry when displaying
in a form.
text
fieldType
fieldType
can be any of the accepted form field types. See
[FIXME - LINK DIO Field Types] for a list of types
available.
All other arguments, unless specified in an individual field type, are passed directly to the form object when the field is created. So, you can pass -size or -maxsize to specify the length and maximum length of the field entry. Or, if type were textarea, you could define -rows and -cols to specify its row and column count.
The following is a list of recognized field types by DIODisplay. Some are standard HTML form fields, and others are DIODisplay fields which execute special actions and functions.
This is session management code. It provides an interface to allow you to generate and track a browser's visit as a "session", giving you a unique session ID and an interface for storing and retrieving data for that session on the server.
This is an alpha/beta release -- documentation is not in final form, but everything you need should be in this file.
Using sessions and their included ability to store and retrieve session-related data on the server, programmers can generate more secure and higher-performance websites. For example, hidden fields do not have to be included in forms (and the risk of them being manipulated by the user mitigated) since data that would be stored in hidden fields can now be stored in the session cache on the server. Forms are then faster since no hidden data is transmitted -- hidden fields must be sent twice, once in the form to the broswer and once in the response from it.
Robust login systems, etc, can be built on top of this code.
Rivet. Currently has only been tested with Postgresql. All DB interfacing is done through DIO, though, so it should be relatively easy to add support for other databases.
Create the tables in your SQL server. With Postgres,
do a psql www or whatever DB you
connect as, then a backslash-i on
session-create.sql
(If you need to delete the tables, use session-drop.sql
)
The session code by default requires a DIO handle
called DIO
(the name of which can be
overridden). We get it by doing a
RivetServerConf ChildInitScript "package require DIO" RivetServerConf ChildInitScript "::DIO::handle Postgresql DIO -user www"
In your httpd.conf, add:
RivetServerConf ChildInitScript "package require Session; Session SESSION"
This tells Rivet you want to create a session object named SESSION in every child process Apache creates.
You can configure the session at this point using numerous key-value pairs (which are defined later in this doc). Here's a quick example:
RivetServerConf ChildInitScript "package require Session; Session SESSION \ -cookieLifetime 120 -debugMode 1"
Turn debugging on -debugMode 1 to figure out what's going on -- it's really useful, if verbose.
In your .rvt file, when you're generating the <HEAD> section:
SESSION activate
Activate handles everything for you with respect to creating new sessions, and for locating, validating, and updating existing sessions. Activate will either locate an existing session, or create a new one. Sessions will automatically be refreshed (their lifetimes extended) as additional requests are received during the session, all under the control of the key-value pairs controlling the session object.
The main methods your code will use are:
packageName
? ?key
? ?data
?packageName
? ?key
?The following key-value pairs can be specified when a session object (like SESSION above) is created:
/dev/urandom
) Data will
be used from this file to help generate a
super-hard-to-guess session ID./
)rivet_session
)rivet_session_cache
)stdout
)
The following methods can be invoked to find out information about the current session, store and fetch server data identified with this session, etc:
packageName
? ?key
? ?data
?packageName
? ?key
?RivetServerConf ChildInitScript "Session SESSION -entropyFile /dev/urandom \ -entropyLength 10 -debugMode 1"
This options say we want to get randomness from an entropy file (random data pseudo-device) of /dev/urandom, to get ten bytes of random data from that entropy device, and to turn on debug mode, which will cause the SESSION object to output all manner of debugging information as it does stuff. This has been tested on FreeBSD and appears to work.
The Rivet mailing list is the first place you should turn for
help, if you haven't found the solution to your problem in the
documentation. Send email to
<rivet-dev@tcl.apache.org>
. If you have a
question, idea, or comment about the Rivet code itself, please
send us email at <rivet-dev@tcl.apache.org>
. To
subscribe to either list, post email to
<rivet-
,
where list
-subscribe@tcl.apache.org>list
is either dev or user.
Currently, dev is the preferred list to use.
The mailing list archives are available at http://mail-archives.apache.org/eyebrowse/SummarizeList?listId=118
The news:comp.lang.tcl newsgroup is a good place to ask about Tcl questions in general. Rivet developers also follow the newsgroup, but it's best to ask Rivet-specific questions on the Rivet list.
There are several web sites that cover Apache and Tcl extensively.
Apache Rivet uses the Apache Bug Tracking system at http://issues.apache.org/bugzilla/. Here, you can report problems, or check and see if existing issues are already known and being dealt with.
Rivet makes available code for two popular editors,
emacs and
vim to facilitate the editing of
Rivet template files. The key concept is that the editor is
aware of the <? and ?> tags and switches back and forth
between Tcl and HTML modes as the cursor moves. These files,
two-mode-mode.el
and
rvt.vim
are available in the
contrib/
directory.
This section easily falls out of date, as new code is added, old code is removed, and changes are made. The best place to look is the source code itself. If you are interested in the changes themselves, the Subversion revision control system (svn) can provide you with information about what has been happening with the code.
When Apache is started, (or when child Apache processes are
started if a threaded Tcl is used),
Rivet_InitTclStuff
is called, which
creates a new interpreter, or one interpreter per virtual
host, depending on the configuration. It also initializes
various things, like the RivetChan
channel system, creates the Rivet-specific Tcl commands, and
executes Rivet's init.tcl
. The caching
system is also set up, and if there is a
GlobalInitScript, it is run.
The RivetChan system was created in order to have an actual Tcl channel that we could redirect standard output to. This lets us use, for instance, the regular puts command in .rvt pages. It works by creating a channel that buffers output, and, at predetermined times, passes it on to Apache's IO system. Tcl's regular standard output is replaced with an instance of this channel type, so that, by default, output will go to the web page.
Rivet aims to run standard Tcl code with as few surprises as possible. At times this involves some compromises - in this case regarding the global command. The problem is that the command will create truly global variables. If the user is just cut'n'pasting some Tcl code into Rivet, they most likely just want to be able to share the variable in question with other procs, and don't really care if the variable is actually persistant between pages. The solution we have created is to create a proc ::request::global that takes the place of the global command in Rivet templates. If you really need a true global variable, use either ::global or add the :: namespace qualifier to variables you wish to make global.
When a Rivet page is requested, it is transformed into an ordinary Tcl script by parsing the file for the <? ?> processing instruction tags. Everything outside these tags becomes a large puts statement, and everything inside them remains Tcl code.
Each .rvt file is evaluated in its own
::request
namespace, so that it is not
necessary to create and tear down interpreters after each
page. By running in its own namespace, though, each page will
not run afoul of local variables created by other scripts,
because they will be deleted automatically when the namespace
goes away after Apache finishes handling the request.
![]() | Note |
---|---|
One current problem with this system is that while variables are garbage collected, file handles are not, so that it is very important that Rivet script authors make sure to close all the files they open. |
After a script has been loaded and parsed into it's "pure Tcl" form, it is also cached, so that it may be used in the future without having to reload it (and re-parse it) from the disk. The number of scripts stored in memory is configurable. This feature can significantly improve performance.
If you are interested in hacking on Rivet, you're welcome to contribute! Invariably, when working with code, things go wrong, and it's necessary to do some debugging. In a server environment like Apache, it can be a bit more difficult to find the right way to do this. Here are some techniques to try.
The first thing you should know is that Apache can be launched as a single process with the -X argument:
httpd -X.
On Linux, one of the first things to try is the system call tracer, strace. You don't even have to recompile Rivet or Apache for this to work.
strace -o /tmp/outputfile -S 1000 httpd -X
This command will run httpd in the system call tracer,
which leaves its output (there is potentially a lot of it) in
/tmp/outputfile
. The -S
option tells strace to only record the
first 1000 bytes of a syscall. Some calls such as
write
can potentially be much longer than
this, so you may want to increase this number. The results
are a list of all the system calls made by the program. You
want to look at the end, where the failure presumably occured,
to see if you can find anything that looks like an error. If
you're not sure what to make of the results, you can always
ask on the Rivet development mailing list.
If strace (or its equivalent on your operating system) doesn't answer your question, it may be time to debug Apache and Rivet. To do this, you will need to run the ./configure.tcl script with the -enable-symbols option, and recompile.
Since it's easier to debug a single process, we'll still run Apache in single process mode with -X:
@ashland [~] $ gdb /usr/sbin/apache.dbg GNU gdb 5.3-debian Copyright 2002 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "powerpc-linux"... (gdb) run -X Starting program: /usr/sbin/apache.dbg -X [New Thread 16384 (LWP 13598)] . . .
When your apache session is up and running, you can request a web page with the browser, and see where things go wrong (if you are dealing with a crash, for instance). A helpful gdb tutorial is available here: http://www.delorie.com/gnu/docs/gdb/gdb_toc.html
Rivet is a break from the past, in that we, the authors, have attempted to take what we like best about our past efforts, and leave out or change things we no longer care for. Backwards compatibility was not a primary goal when creating Rivet, but we do provide this information which may be of use to those wishing to upgrade from mod_dtcl or NWS installations.
Rivet was originally based on the dtcl code, but it has changed (improved!) quite a bit. The concepts remain the same, but many of the commands have changed.