Differences between revisions 4 and 5
Revision 4 as of 2005-10-06 09:42:09
Size: 6441
Comment:
Revision 5 as of 2005-10-14 00:00:19
Size: 6490
Editor: lucianoes
Comment:
Deletions are marked like this. Additions are marked like this.
Line 3: Line 3:
Rivet, Tcl module for apache web server, allows to improve performance for big Tcl web application greatly. Web application instances that live inside apache child processes, are loaded once and run many times to generate web documents requested. Each of these instances can have persistent database connections. 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.
Line 5: Line 5:
Some time there appears a need to run several Rivet applications inside one apache server. There's some problem with it. By default there's one single Tcl interpreter is created for every apache child process. Different applications (or several instances of the same application) may interfere in single Tcl interpreter. They can overwrite each others procedures and global variables. 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.
Line 7: Line 7:
One more problem appears while you are in process of creating and testing your application. Source code changes fast, and needs to be reloaded into apache. Rivet handles this for scripts that are executed directly. But big application typically has many library scripts. This library is sourced into interpreter upon application loading or lately, when the need appears. There's no way for Rivet to know when and how to reload these library scripts. 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.
Line 9: Line 9:
The simplest way is to restart apache every time you have changed source code. It is very unhandy. The simplest way is to restart Apache every time you change source code. That is very unhandy.
Line 11: Line 11:
To resolve both above problems we can use slave interpreters. Slave is created when needed for each application and destroyed when sources are changed. Code for different applications doesn't interfere. Every application restarts automatically without restarting whole apache server. 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.
Line 13: Line 13:
Uri structure used is: `http://site.domain/path/web.tcl/document?parameters` The URI structure used is: `http://site.domain/path/web.tcl/document?parameters`
Line 15: Line 15:
`path` element distinguishes between applications inside same apache server. The `path` element distinguishes between applications inside the same Apache server.
Line 17: Line 17:
web.tcl is a main script for application. It does the following: web.tcl is a main script for the application. It does the following:
Line 23: Line 23:
 * gets "document" part of uri and uses it as prefix of name for some library procedure
 * runs this procedure to generate document requested
 * 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
Line 26: Line 26:
Every page of the application is generatated by some tcl procedure. To identify valid document generation procedure all their names are ended with _doc suffix. Every page of the application is generatated by some Tcl procedure. To identify valid document generation procedures, all their names have the _doc suffix.
Line 29: Line 29:
There are some problems when implementing this approach. Rivet isn't a Tcl package. It can't be loaded into slave interpreter. Hence Rivet commands aren't accessible from slaves. Also, Rivet commands "load_env", "load_headers", "load_cookies" load environment items into array variables of '''parent''' interpreter. These variables aren't accessible from slaves. To overcome this we should create appropriate commands in parent interpreter and command aliases in slaves. 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.

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)