Tclhttpd Error Handling

This is a drop-in replacement for TclHttpd's Not Found handler.

It provides additional functionality, before falling back to the default (which is to return a substituted file /notfound.tml)

First, some well-known suffixes are searched for in global/centralised locations: .css in /css, .js in /jscript, .{gif,jpg} in /images ... to which the request is redirected.

Otherwise a file called .nf is sought in directories from the current up to root, in order. The .nf file is substituted as a template.

Finally, if there are no .nf files, the /notfound.tml URL is returned, as the default behavior.

This is useful to permit, say, creation of new files on a wiki. The .nf template is free to perform whatever processing it wants (e.g. creating a new template for the file which wasn't found, then redirecting to the failed URL.)

    # notfound handler to provide centralised repository of .css and image files
    # also searches path from docroot down for files called .nf, which it 
    package require struct
    rename Doc_NotFound Doc_Notfound_org
    proc Doc_NotFound {sock} {
        global Doc Referer
        upvar #0 Httpd$sock data
        #Stderr "Not Found $data(suffix)"
        # try to redirect some well-known types .css, .jpg etc
        # to centralised repositories
        set redirect ""
        switch [file extension $data(suffix)] {
            .css {
                set redirect [file join /css [file tail $data(suffix)]]
            .js {
                set redirect [file join /jscript [file tail $data(suffix)]]
            .gif -
            .jpg {
                set redirect [file join /images [file tail $data(suffix)]]
        if {($redirect ne "") && ([string trim $data(suffix) /] ne [string trim $redirect /])} {
            Redirect_QuerySelf $sock $redirect
        } else {
            # fallback to using a .nf file in each directory on the path
            # as a template to generate the desired file
            if {[catch {Doc_GetPath $sock} path]} {
                set path {}
            foreach dir [::struct::list::Lreverse $path] {
                set file [file join $dir .nf]
                if {[file exists $file]} {
                    global Template
                    set html [TemplateInstantiate $sock $file $data(path) $data(suffix) dynamic \
                    # If the content type was set, use it.  Otherwise, use the default.
                    if {[info exists data(contentType)]} {
                        set ctype $data(contentType)
                    } else {
                        set ctype "text/html"
                    if {$dynamic} {
                        return [Httpd_ReturnData $sock $ctype $html]
                    } else {
                        # Use ReturnFile so remote end knows it can cache the file.
                        # This file may have been generated by TemplateInstantiate above.
                        return [Httpd_ReturnFile $sock $ctype $data(path)]
            # finally, default to the old behavior
            Doc_Notfound_org $sock