tclhttpd Indirect domain

This is a fusion of tclhttpd Direct and Doc domains

It will try to find a match using the same technique as Doc, and fall back to a Direct domain if it can't. Additionally, the ${prefix}/* proc will be called on no match.

You will need today's CVS HEAD at least, but newer is always better. See tclhttpd for how to get tar and zip snapshots.

Enjoy! -- CMcC 20041120


To create an Indirect domain you call [Indirect::Url virtual directory prefix] where virtual is the URL path to this domain, directory is the file directory for templates and prefix is a prefix to be prepended to the command invocation.

This domain would be useful for applications which operate on directories and files - one could, for example, create a [search] process which searched the files in a directory, one could use the /* function to create new files on demand, as in Wikit.

prefix can be a simple string, an alias, a namespace prefix or an interp eval prefix.


    package require httpd::direct

    namespace eval Indirect {
        variable Indirect
    }

    proc Indirect::Url {virtual directory {prefix {}} {inThread 0}} {
        variable Indirect
        if {[string length $prefix] == 0} {
            set prefix $virtual
        }
        set Indirect($prefix) $virtual        ;# So we can reconstruct URLs
        Doc_RegisterRoot $virtual $directory
        Url_PrefixInstall $virtual [list Indirect::domain $prefix $directory] $inThread
    }

    proc Indirect::UrlRemove {prefix} {
        variable Indirect
        catch { Url_PrefixRemove $Indirect($prefix) }
        catch { unset Indirect($prefix) }
    }

    proc Indirect::domain {prefix directory sock suffix} {
        variable Indirect
        upvar #0 Httpd$sock data

        # Prepare argument data from the query data.
        Url_QuerySetup $sock

        set path [file join $directory [string trimleft $suffix /~]]
        set path [file normalize $path]
        set data(path) $path        ;# record this path for not found handling
        set data(directory) $directory

        # Look for a fresh template which generates the desired path
        if {[Template_Try $sock $path $prefix $suffix]} {
            # template has handled the request
            return 1
        }

        # Template_Try hasn't satisfied the request,
        # Look for an exact match.
        if {[file exists $path] && [file readable $path]} {
            # we have a file precisely matching the request
            Doc_Return $prefix $path $suffix $sock
            return 1
        }

        # no matching file - try a direct call
        set cmd [Direct_MarshallArguments $prefix $suffix]

        if {$cmd == ""} {
            # Negotiate an Acceptable available alternative file
            # if one is found, a redirect is provoked
            # (FIXME: is this according to the spec?)
            if {[Fallback_Try $prefix $path $suffix $sock]} {
                # we have found an Accept-able alternative
                # Fallback_Try generates a redirect
                return 1
            }

            # still no match - try the * command
            set cmd [Direct_MarshallArguments $prefix /*]
            if {$cmd == ""} {
                Doc_NotFound $sock
            }
        }

        # Eval the command.  Errors can be used to trigger redirects.
        set code [catch $cmd result]

        set type text/html
        upvar #0 $prefix$suffix aType
        if {[info exist aType]} {
            set type $aType
        }

        Direct_Respond $sock $code $result $type
    }

Example indirect domain

    Indirect::Url /indirect indirect_test ::indirect::

    namespace eval indirect {
        proc /moop {args} {
            return "<html><head><title>Moop</title></head><body>$args</body></html>"
        }

        proc /* {args} {
            upvar #0 Httpd[Httpd_CurrentSocket] data
            return "<html><head><title>Moop</title></head><body><h3>Not Found</h3><p>[array get data]</p></body></html>"
        }
    }

[Category TclHttpd]