Copyright © 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Apache Software Foundation
Table of Contents
List of Examples
Document revision: $Revision: 959821 $, last modified 2010-07-20 21:49:04+02:00$ by $Author: mxmanghi $.
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. Rivet can be compiled to work with either Apache 1.3.xx or Apache 2.2.xx server. It is known to run on Linux, FreeBSD, OpenBSD, Solaris and HPUX. Windows NT is also possible - please see the directions in the distribution.
Get Rivet
Download the sources at http://tcl.apache.org/rivet/download.html. 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 build scripts where Tcl is via the --with-tcl option to configure (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 (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 rivet-X.X.X.tar.gz tar -xvf 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 rivet specific options to configure that might be useful (or needed):
tclConfig.sh
file is located.
tclsh
executable.apxs
program that provides information about the
configuration and compilation options of Apache modules.
Example: configuring the build system to compile Rivet for an apache 2.x server, using tcl8.5 and specifying a custom name for the apxs program.
./configure --with-tcl=/usr/lib/tcl8.5/ --with-tclsh=/usr/bin/tclsh8.5 \ --with-apxs=/usr/bin/apxs2 --with-apache=/usr --with-apache-version=2
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/apache2/modules/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.
The characters encoding can be changed using the header type command, but you can also change the default charset for the whole site:
AddType 'application/x-httpd-rivet;charset=utf-8' rvt
All the pages generated by Rivet on this site will be sent with a Content-Type:'text/html;charset=utf-8' header.
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
?var, var_qs, var_post — get the value of a form variable.
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.
upload — handle a file uploaded by a client.
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
?uploadname
?See upload.
load_response — load form variables into an array.
arrayName
?Load any form variables passed to this page into an array. If load_response is called without arguments the array response is created in the scope of the caller. If the variables var1,var2,var3... having values val1,val2,val3... are passed to the page, the resulting array will be a collection mapping var1,var2,var3... to their corresponding values. load_response was inspired by the same NeoWebScript procedure in the way it deals with multiple assignments: if a variable is assigned more than once the corresponding array element will be a list of the values for the variable. This can be useful in the case of forms with checkbox options that are given the same name. Calling load_response several times for the same array results in adding more values to the array at every call. When needed it is left to the caller to empty the array between two subsequent calls.
load_cookies — get any cookie variables sent by the client.
array_name
?Load the array of cookie variables into the specified array name. Uses array cookies by default.
include — includes a file into the output stream without modification.
filename_name
?headers — set and parse HTTP headers.
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
?cookie — get and set cookies.
cookieName
? ??cookiValue?
? ?-days expireInDays
? ?-hours expireInHours
? ?-minutes expireInMinutes
? ?-expires Wdy, DD-Mon-YYYY HH:MM:SS GMT
? ?-path uriPathCookieAppliesTo
? ?-secure 1/0
?cookieName
?clock_to_rfc850_gmt — create a rfc850 time from [clock seconds].
seconds
?abort_page — Stops outputing data to web page, similar in purpose to PHP's die command.
escape_string — convert a string into escaped characters.
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.
![]() | Note |
---|---|
You must require the Rivet package in order to gain access to this command |
escape_sgml_chars — escape special SGML characters in a string.
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.
![]() | Note |
---|---|
You must require the Rivet package in order to gain access to this command |
escape_shell_command — escape shell metacharacters in a string.
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.
![]() | Note |
---|---|
You must require the Rivet package in order to gain access to this command |
unescape_string — unescape escaped characters in a string.
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.
![]() | Note |
---|---|
You must require the Rivet package in order to gain access to this command |
apache_log_error — log messages to the Apache error log
apache_table — access and manipulate Apache tables in the request structure.
The apache_table command is for accessing and manipulating Apache tables in the request structure.
The table name must be one of notes, headers_in, headers_out, err_headers_out, or subprocess_env.
tablename
? ?key
?tablename
and the name of a key
tablename
,
returns the value of the key in the table, or an empty
string.
tablename
? ?key
? ?value
?tablename
? ?list
?value
in
the table
tablename
under the key
key
.
list
contains
a list of zero or more pairs of key-value pairs to be
set into the table
tablename
.
tablename
? ?key
?key
,
exists in table
tablename
,
else 0.
tablename
? ?key
?key
from the table
tablename
.
tablename
?tablename
.
tablename
?tablename
.
tablename
?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 for being sent over http is as easy as determining the file's URI and letting Apache's 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 Apache has right to access).
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. The client is responsible for deciding how to handle the data accordingly to the "content-type" headers and its internal design. For example when browsers give up trying to display a certain "content-type" they display a download dialog box 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. This information might be passed on to scripts or programs for processing. In this case a real file representing the data doesn't exist and the content is generated on demand by the server. In other circumstances you may need to dynamically inhibit the download of specific files and hide them away, Your scripts may expunge from the pages every link to these files (your pages are dynamic, aren't they?) and move them 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.
This code is reported as an example of how to control the protocol using the headers command.
# 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 a web browser to run in backround JavaScript functions. JavaScript functions can be run as callbacks of events generated by a user interaction but they can also react to other I/O events, for example network events. Modern browsers endow JavaScript with the ability to build http GET/POST requests to be sent to a remote webserver. Generally these requests refer to scripts (e.g. Tcl scripts run by Rivet) which inherit as variables the arguments encoded in the request. The output produced by these scripts is sent back to the browser where callbacks functions extract information and hand it down to functions that directly manipulate a page's DOM. Therefore through Ajax becomes possible to build web applications that are more responsive and flexible: instead of going through the cycle of request-generation-transfer-display of a whole page, Ajax scripts request from a webserver only the essential data to be displayed. Ajax emphasizes the requirement of separation between data and user interface, saves the server from sending over the same html code and graphics if only a fraction of a page has to be updated, allows the programmer to design flexible solutions for complex forms and makes possible to find new innovative approaches to simple problems (e.g. Google tips that show up as you type in a query). A downside of this approach is the large number of complexities, subtleties and incompatibilities that still exist in the way different versions of popular browsers handle the DOM elements of a page.
JavaScript can handle the communication between client and server through an instance of a specialized object. For quite a long time 2 approaches existed, the non-IE world (Firefox,Safari,Opera...) used the XMLHttpRequest class to create this object, whereas IE (before IE7) used the ActiveXObject class. With the release of IE7 Microsoft introduced native support for XMLHttpRequest class objects thus enabling programmers with a unique method for the development of dynamic pages.
By creating an instance of this class a POST or GET request can be sent to the server and the response is stored in a property ('returnedText') of the communication object. It's become widely customary to encode these responses in XML messages. You can invent your own message structure (either based on XML or anything else), but one has to be aware 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 assigned with a reference to an object that represents the DOM of the XML response. By means of the XML W3C DOM interface the programmer can easily manipulate the data embedded in the XML message.
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. The script sends back to the client two types of responses: a catalog of the composers or a single record of a composer.
# # Ajax query servelet: a pseudo database is built into the dictionary 'composers' with the # purpose of emulating the role of a real data source. # The script answers with 2 types of responses: a catalog of the record ids and a database # entry matching a given rec_id. The script obviously misses the error handling and the # likes. Just an example to see rivet sending xml data to a browser. The full Tcl, JavaScript # and HTML code are available from http://people.apache.org/~mxmanghi/rivet-ajax.tar.gz # This example requires Tcl8.5 or Tcl8.4 with package 'dict' # (http://pascal.scheffers.net/software/tclDict-8.5.2.tar.gz) # # A pseudo database. rec_id matches a record in the db set composers [dict create 1 {first_name Claudio middle_name "" last_name Monteverdi \ lifespan 1567-1643 era Renaissance/Baroque} \ 2 {first_name Johann middle_name Sebastian last_name Bach \ lifespan 1685-1750 era Baroque } \ 3 {first_name Ludwig middle_name "" last_name "van Beethoven" \ lifespan 1770-1827 era Classical/Romantic} \ 4 {first_name Wolfgang middle_name Amadeus last_name Mozart \ lifespan 1756-1791 era Classical } \ 5 {first_name Robert middle_name "" last_name Schumann \ lifespan 1810-1856 era Romantic} ] # 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 [dict keys $composers] { set first_name [dict get $composers $nm first_name] set middle_name [dict get $composers $nm middle_name] set last_name [dict get $composers $nm last_name] append xml " <composer key=\"$nm\">$first_name " if {[string length [string trim $middle_name]] > 0} { append xml "$middle_name " } append xml "$last_name</composer>\n" } append xml "</catalog>\n" } composer { append xml "<composer>\n" if {[var exists rec_id]} { set rec_id [var get rec_id] if {[dict exists $composers $rec_id]} { foreach {k v} [dict get $composers $rec_id] { append xml "<$k>$v</$k>\n" } } } append xml "</composer>\n" } } # 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.
DIO — Database Interface Objects
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
?DIODisplay — Database Interface Objects Display Class
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.
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.
Currently has only been tested with Postgresql, MySql and Oracle. 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 form package is a utility for generating html forms. A form object command saves the programmer from typing the cumbersome html code of input elements, working out a solution for better standardization and readability of the code. form requires that only the minimum necessary to distinguish the element is typed, greatly simplyfing the development of forms. Options to the command are treated as a list of parameter-value pairs that become the defaults for the corresponding attributes of the form.
A form object has specialized menthods to generate all of the standard input fields, i.e. text, password, hidden, generic button, submit or reset buttons and image. form creates select input fields, radiobutton and checkbox boolean options groups. Also new inputs introduced with HTML5 are supported: color, date, datetime, datetime-local, email, file, month, number, range, search, tel, time, url, week.
Other input elements can be generated using the general purpose 'field' method.
form — a Tcl command object for creating HTML forms
form_name
?-option1 value_1
? ?-option2 value_2
? ?...?
creates and returns a new Tcl command named form_name
.
![]() | Note |
---|---|
At the time of writing only the 'get' method is implemented |
form_name
?default_values
?URL
?
Form object commands follow the usual syntax of Tcl commands with a ?subcommand
? argument playing the
role of a switch among various functionalities of the command. Form objects also need the ?name
? parameter
which is to become the value of the 'name' attribute in an input field. This argument is the key that has to be
used by the server-side script to retrieve the input field value.
value1
? ?-option2 value2
? ?...?Options passed to a subcommand are copied into the tag as attribute="value" pairs. Some subcommands (e.g. form, radiobuttons and checkboxes) treat specific options in a way that fits the specific organization and function of these fields.
Exceptions to this general syntax are the field and end subcommands. field is an abstract input field creation method and requires an additional parameter specifiyng the type of field to create. Every concrete input field generation command uses this subcommand internally to print the final html.
get | post
? ?-name form_name
? ?-defaults default_values
? ?-action URL
? ?args?response
form myform -defaults response -method get -name formname myform start myform text text_entry -size 20 myform select option_selected -values {opt1 opt2 opt3 opt4} myform submit submit -value Search myform end
![]() | Note |
---|---|
Currently, only the 'get' method is implemented |
form_name
?default_values
?URL
?type
? and ?name
?,
including any default key-value pairs defined for this field
type and optional key-value pairs included with the statement
values
? ?-labels labels
? ?args?values_list
?labels_list
?form myform -defaults response -method get -name formname myform start myform text text_entry -size 20 myform radiobuttons fruit -values {big medium small} \ -labels {Watermelon Orange Strawberry} \ -class myradiobclass myform submit submit -value Search myform end
<input type="radio" name="fruit" class="myradiobclass" value="big" />Watermelon <input type="radio" name="fruit" class="myradiobclass" value="medium" />Orange <input type="radio" name="fruit" class="myradiobclass" value="small" />Strawberry
response
array has a variable for the name 'fruit' the corresponding
radiobutton field is automatically checked. The options ?values? and ?labels?
are used internally and don't get into the tag attributes. If a ?labels?
option is not given, labels are assigned using the ?values? list.
label
? ?-value value
? ?args?values_list
?labels_list
?myform checkbox options -value opt1 -label "Option 1" myform checkbox options -value opt2 -label "Option 2" myform checkbox options -value opt3 -label "Option 3" myform checkbox options -value opt4 -label "Option 4"
<input type="checkbox" name="options" label="Option 1" value="opt1" />Option 1 <input type="checkbox" name="options" label="Option 2" value="opt2" checked="checked" />Option 2 <input type="checkbox" name="options" label="Option 3" value="opt3" />Option 3 <input type="checkbox" name="options" label="Option 4" value="opt4" />Option 4
form myform -defaults response -method get -name feedsearch myform start myform submit submit -value Search
<form...> <input type="submit" name="submit" value="Search" /> </form>
The package is based on the Calendar class, a class capable of printing an ascii calendar table that closely resembles the output of the typical Unix cal command. The internal code is written entirely in Tcl, therefore doesn't rely on the existance of cal on the system. XmlCalendar inherits the basic methods and adds XML tagging to the table. XmlCalendar prints an XML calendar table whose header, weekdays banner and days rows tags are configurable. Also specific days or specific weeks can be given arbitrary attributes.
Calendar core methods are based on the cal procedure written by Richard Suchenwirth and published on the Tcl Wiki
![]() | Note |
---|---|
The Calendar package uses Tcl dict command to manage markup information. Hence either Tcl8.5 or Tcl8.4 with package dict are required. |
Calendar — Utility class the builds and prints a calendar table
calendar_name
The main public command for a calendar object is emit that returns a calendar table
The method emit when invoked with a single argument takes it as an year number and prints the whole calendar of that year. When invoked with 2 arguments takes the first as a month, either expressed in its shortened form ('Jan','Feb',...) or as a number in the range 1-12. The second argument is a year number.
month | year
?set cal [Calendar #auto] set current_month [$cal emit] puts $current_month Jun 2010 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
XmlCalendar — Prints XML formatted calendar tables
calendar_name
?-option1 option_list
? ?-option2 option_list
? ?...?An XmlCalendar object is created and returned. XmlCalendar objects print XML formatted calendar tables. The markup can be customized using the configuration options.
Configuration options accept odd-length lists as values. An option_list has the following structure
tag_name attr11 val1 attr2 val2 ...
The first element of an option list is a tag name, the remaining terms are therefore an even-length sublist which is interpreted as a sequence of attribute-value pairs that will in turn become attributes of the tag.
month | year
? -opt1 val1
-opt2 val2![]() | Note |
---|---|
This option was added for completeness, but it's not implemented yet |
HtmlCalendar — Concrete class derived from XmlCalendar
calendar_name
?-option1 option_list
? ?-option2 option_list
? ?...?Concrete XmlCalendar class for printing html calendar tables. The markup of the class is xhtml compliant and prints a code fragment for inclusion in a webpage. The following is the class definition.
::itcl::class HtmlCalendar { inherit XmlCalendar constructor {args} {XmlCalendar::constructor $args} { $this configure -container table \ -header thead \ -body tbody \ -banner tr \ -banner_month {th colspan 3 style "text-align: right;"} \ -banner_year {th colspan 4 style "text-align: left;"} \ -weekdays tr \ -weekday_cell th \ -days_row tr \ -days_cell td } }
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
or you have a question, idea, or comment about the Rivet code itself send email to
<rivet-dev@tcl.apache.org>
. To subscribe to the list, post email to
<rivet-dev-subscribe@tcl.apache.org>
.
The mailing list archives are available at http://mail-archives.apache.org/mod_mbox/tcl-rivet-dev/
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).
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.