TclHttpd Templates

TclHttpd uses subst to generate web pages from .tml files, optionally caching the results. Templates are a module invoked from Tclhttpd Document Domain, but recently it has become possible to use the Template module from other code.


Template Pseudo Code

1. a global page() array is populated with useful values from the environment

Used in a template, the following outputs the template path:

 $page(template)

2. a CGI environment is set up in env() (nb. this is a hangover from cgi and is relatively inefficient. It can be turned off by setting Template(env) to 0)

Used in a template, the following outputs the client browser string:

 $env(HTTP_USER_AGENT)

3. Query data is read from the connection, and stored in page(query)

4. all .tml files back to the document root are sourced into the template interpreter (which may be the global interpreter, or another interpreter dedicated to template expansion)

5. the ${template}.tml file is substed

6. errors from the subst are handled

7. cookies (if any) are saved

8. unless [Template_Dynamic] was called to inhibit the behavior, or the template was requested explicitly, the subst result is cached in the file with the name the user requested. What is the difference between this and [Doc_Dynamic]?

9. the generated content is returned to the client


Extended Templates - Aggregation

In a recent modification/hack, a ${template}.tml is permitted to be a directory. In that case, a file ${template}.tml/index.tml is evaluated, and its result returned to the client (after potentially being cached.)

This means that a template can, conceptually, be a collection of other files, and provides a kind of file context for processing. An example of use might be to provide a summary or assembled page from these components.

Since TclHttpd will default to caching the result of evaluating ${template}.tml/index.tml as ${template}.html, and will compare the cached version against the last modification time of the whole ${template}.tml/ directory, this provides an efficient mechanism for aggregation.

Note that any directory's index.html file will now be compared by modification time to that of the whole directory, when considering generation from index.tml, thus any directory can be considered as a collection and aggregated, but since changes to a subdirectory do not propagate to its parent directory, index.tml aggregation doesn't propagate.

Use of index.tml, in combination with ${template}.tml/ generation, you can control the propagation of changes from a sub-collection to collections in which they are contained.

Addendum: In retrospect, the dir.tml/ hack is a bad way to achieve the intended goal. Since index.tml or similar can be constructed for a directory, and will now be re-evaluated when anything in the directory is newer, simple aggregation can be achieved as described above. The problem dir.tml/ was intended to solve was the problem of propagating changes in an aggregate directory upward, to allow effective caching of aggregates of aggregates, however this can more simply be achieved by having the code in index.tml touch some file in its parent directory. This is much less ugly than .tml/ directories. I will leave the functionality there (since otherwise a dir.tml is a simple error) but deprecate the usage. -- CMcC 20040919


Hints and Tricks

Access to per-socket data: A lot of tclhttpd context is stored in a global array called Httpd$sock where socket is the file descriptor of the current connection. To access this data, you can use upvar #0 Httpd[Httpd_CurrentSocket] data and reference the array as data.

Working Directory: to discover the directory containing the current template file use file dirname $page(filename). TclHttpd doesn't itself presume anything about the current working directory.

Redirection:

Example:

 [Doc_Redirect https://www.tcl-lang.org/]

Generalised Template proposal

CMCc: A while back someone asked on the mailing list about using [Doc_$mime] procs to generate content which doesn't exist at the time of a client's request.

It is currently possible to specify a generic template by adding the template extension via [Mtype_Add] and writing the matching [Doc_$mime] proc. [Fallback_Try] will then detect the template match when the requested file isn't found, and redirect to that template. The [Doc_$mime] proc for that file extension can then check if the file needs template processing (ie: if the path has the magic extension)

However, the resultant template won't be automatically re-evaluated when it's newer than the generated file, because an exact match is always preferred to a generated one, except in the case of .tml->text/html

A more general solution would be to provide an array mapping from desired extension to template extension(s), which would be checked when we're about to return a matching file, and used to generate a file of the desired type when the original is missing.

If a template file is newer than its generated file, then the template could be regenerated and this value returned to the client.

This is a fairly straight-forward generalisation of the current tmlExt value, which is currently hard coded to match .html files to .tml templates.

I propose to do this. The additional processing cost will be to incur a check for template mod time whenever a possibly-templated file is returned.

Revised proposal for generalised templating.

Instead of an array mapping extension to template extension, the form $file.$ext.$tmplExtension will be used to generate content to satisfy an extension $file.$ext. E.g. The template $file.html.tml will generate $file.html.

This is equivalent to the previous proposal, with a hard coded array mapping .$ext.tml to .$ext, but has the advantage of being consistent with the current case, and not requiring as much change to the code.

$file.tml will generate $file.html as a special case.


tclhttpd session templates presents templating within cookie-session interpreter context ... effectively each user gets their own individual persistent template evaluation context.