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 \ $Template(templateInterp)] # 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 } }