Differences between revisions 5 and 6
Revision 5 as of 2005-10-14 00:00:19
Size: 6490
Editor: lucianoes
Comment:
Revision 6 as of 2009-09-20 23:33:54
Size: 6490
Editor: localhost
Comment: converted to 1.6 markup
No differences found!

A way to create web application using Rivet

Rivet, a Tcl module for the Apache Web server, allows great improvement in the performance of big Tcl Web applications. Web application instances that live inside Apache child processes are loaded once and run many times to generate requested Web documents. Each of these instances can have persistent database connections.

Sometimes there is a need to run several Rivet applications inside one Apache server. There raises a problem. By default, one single Tcl interpreter is created for every Apache child process. Different applications (or several instances of the same application) may interfere in this single Tcl interpreter. They can overwrite each other's procedures and global variables.

Another problem occurs in the process of creating and testing an application. Source code changes fast and needs to be reloaded into Apache. Rivet handles this for scripts that are executed directly. But big applications typically have many library scripts. This library is sourced into the interpreter upon the application's loading or later, when it is needed. There's no way for Rivet to know when and how to reload these library scripts.

The simplest way is to restart Apache every time you change source code. That is very unhandy.

To solve both problems above, we can use slave interpreters. The slave is created for each application when needed and destroyed when the sources are changed. The code of different applications doesn't interfere. Every application restarts automatically without restarting the entire Apache server.

The URI structure used is: http://site.domain/path/web.tcl/document?parameters

The path element distinguishes between applications inside the same Apache server.

web.tcl is a main script for the application. It does the following:

  • checks if library scripts are changed
  • deletes slave interpreter if needed
  • creates new slave interpreter if needed
  • loads library scripts into it
  • gets "document" part of URI and uses it as a prefix of the name for some library procedure
  • runs this procedure to generate the requested document

Every page of the application is generatated by some Tcl procedure. To identify valid document generation procedures, all their names have the _doc suffix.

There are some problems with implementing this approach. Rivet isn't a Tcl package. It can't be loaded into a slave interpreter. Hence Rivet commands aren't accessible from slaves. Also, the Rivet commands "load_env", "load_headers" and "load_cookies" load environment items into the array variables of the parent interpreter. These variables aren't accessible from slaves. To overcome this, we should create appropriate commands in the parent interpreter and command aliases in the slaves.

Code examples

In apache config there should be a directive to set script for apache child initialization for Rivet, like this:

RivetServerConf ChildInitScript "source /etc/apache/rivet-childinit.tcl"

In "rivet-childinit.tcl" we add proc's to access environment items:

# a way for slave to access environment
proc rivet_env_exists {name} {return [info exists ::env($name)]}
proc rivet_env_get {name} {return $::env($name)}
proc rivet_cookie_exists {name} {return [info exists ::headers($name)]}
proc rivet_cookie_get {name} {return $::headers($name)}

Here is working example of web.tcl code:

#
# sample Tcl Rivet application
# main executable
#

# directory with library scripts
set lib_dir .

# list of library script names
set lib_files {library.tcl}

# slave interpreter path
set slave_path [pwd]

# make list of library script paths and modification times
set mtime_list {}
foreach file $lib_files {
  set path [file join $lib_dir $file]
  lappend mtime_list $path [file mtime $path]
}

# do we need to destroy slave?
if {[interp exists $slave_path]} {
  # get saved list of library scripts loaded into slave
  catch {interp eval $slave_path set restart_list} restart_list

  # are library scripts changed?
  if {$mtime_list != $restart_list} {
    puts stderr "deleting interpeter '$slave_path'"
    if {[catch {interp delete $slave_path} res]} {
      puts stderr $res
    }
  }
}

# do we need to create new slave?
if {![interp exists $slave_path]} {
    puts stderr "creating interpeter '$slave_path'"
    set i [interp create $slave_path]

    # make Rivet commands aliases in slave
    foreach cmd {var upload include parse headers makeurl load_headers load_env
load_cookies cookie rivet_env_exists rivet_env_get} {
        $i alias $cmd $cmd
    }

    # allow to find same packages in slave as in master
    $i eval [list set auto_path $auto_path]

    # load needed packages into slave
    $i eval {
        package require base64
    }

    # load library scripts into slave
    foreach {path mtime} $mtime_list {
      $i eval source $path
    }

    # remember loaded library scripts
    $i eval [list set restart_list $mtime_list]
}

# run application
interp eval $slave_path run

And here is working example of application library, with two simplest pages, default "index" page and "hello" page:

#
# sample Tcl Rivet application
# library script
#

# process request
proc run {} {
  # load request environment into PARENT interpreter
  load_env

  # get document name from uri
  if {[rivet_env_exists PATH_INFO]} {
    set docName [lindex [file split [rivet_env_get PATH_INFO]] 1]
  } else {
    set docName index
  }

  # run procedure to print document
  $docName\_doc
}

# print index page
proc index_doc {} {
  puts "This is index page, variables are: [var all]."
}

# print sample "Hello!" page
proc hello_doc {} {
  puts "This is Hello page!"
}

Some other details

  • "htmlgen" package is used for html generation
  • ".rvt" files aren't used. If ".rvt" is needed, one may create tcl procedure which calls Rivet parse command to parse ".rvt" file

  • more can be done to process errors in document generation procedures
  • base html tag it is very useful in every html page. For example, when <base href="http://site.domain/path/web.tcl/" /> is contained in index page, relative links on it are valid with no dependency of what uri used to access it, http://site.domain/path, http://site.domain/path/web.tcl or http://site.domain/path/web.tcl/index

RivetWishWay (last edited 2009-09-20 23:33:54 by localhost)