Frontier Tutorials / Writing an ObjectNotFoundHandler / Mode 3 Example

Mode 3 Example

If you want to return something that should not be wrapped in the template and returned as an HTML page, you have to stuff the page table yourself. This is the most powerful approach available for ONFH handlers, and the most complicated.

The Example

In this example, we will return binary data from a table in the website.

Create a Download Table

At the top level of your site, create a new subtable named "#download".

Load a .zip file into this table from your disk (preferably a small one). If you don't have a utility for loading binary files, you can download and use the ImportBinaryFile script from my published custom menu.

Change the name of the binary entry to "binary1". (It can be called anything you want, of course; this change simplifies the following descriptions.)

Create the Script and the Reference

In your #tools table, create a script called "ReturnBinaryObject". It should look like this:

on ReturnBinaryObject( pageTbl = html.getPageTableAddress() )
space picturelocal
space picturelastResolvedAdr = pageTbl^.lastNomad
space pictureremainingPath = pageTbl^.remainingPath
space picture 
space pictureon NotFoundError( missingSubpath=remainingPath )
space picture« No matching page found; return 404-not found page.
space picturescriptError ("Can't process the request because there is no object named \"" + missingSubpath + "".")
space picture 
space picture« Try to process as this an download request?
space pictureif ! ( ( remainingPath beginsWith "download/" ) && ( string.countFields( remainingPath, "/" ) == 2 ) )
space picture« Not an download path.
space pictureNotFoundError()
space picture 
space picturewhile typeOf( lastResolvedAdr^ ) == addressType
space picturelastResolvedAdr = lastResolvedAdr^
space picture 
space picturelocal
space picturedownloadTbl = @lastResolvedAdr^.["#download"]
space pictureif ! defined( downloadTbl^ )
space picture« No download table here!
space pictureNotFoundError( "download" )
space picture 
space picture« Strip suffix, if any, but remember it.
space picturelocal
space pictureterminal = string.nthField( remainingPath, "/", 2 )
space pictureentryName = string.popSuffix( terminal, '.' )
space picturesuffix = terminal - ( entryName + "." )
space picture 
space picture« Does a matching entry exist?
space picturelocal
space pictureentryAdr = @downloadTbl^.[ entryName ]
space pictureif ! defined( entryAdr^ )
space picture« Download subtable not found
space pictureNotFoundError( terminal )
space picture 
space picture« Is the entry of the correct type?
space pictureif ( user.webserver.prefs.ext2MIME[ suffix ] != mainResponder.getODBMimeType( entryAdr ) )
space picture« MIME types don't match
space pictureNotFoundError( terminal )
space picture 
space picture« We found the object! Now serve it up.
space picture 
space picture« Copy out the object
space picturepageTbl^.responseBody = string( entryAdr^ )
space picturepageTbl^.responseHeaders.["Content-Type"] = mainResponder.getODBMimeType( entryAdr )
space picturepageTbl^.responseHeaders.["Last-Modified"] = date.netStandardString( clock.now() )
space picture 
space picture« Don't let mainResponder.respond overwrite our response
space picturescriptError ("!return")

This script does the following:

oparrowwithsubs picture We could simply ignore the suffix, or only recognize a request without suffix. However, by requiring the suffix and validating it against the entry type, we provide an extra hint (the suffix) to browsers when they download the file and a full filename with suffix when they save it.

oparrowwithsubs picture I have used scriptError("!return") because that is what UserLand uses in their scripts. However, mainResponder.respond treats only "!redirect" specially; all other scriptErrors beginning with "!" are treated identically--so you could actually use "!", "!outahere", or even "!george", if you prefer.

  1. Get the last resolved address and the remainder of the path from the page table.

  2. Check to make sure that the URL is, in fact, a download URL. We do this by checking that the remainingPath (1) begins with "download/", and (2) has only two directory levels in it. If it is not a download path, we throw a scriptError identical to that thrown by mainResponder.respond in the absence of an #objectNotFoundHandler.

  3. Extract the download entry name. Strip off any suffix (such as ".pdf"), but retain the suffix so that we can verify that any entry we find is of the requested type.

  4. Check for a matching entry. If such an entry is not found, throw a scriptError, as above.

  5. Check that the matching entry is of the requested type. We do this by converting the suffix and the entry's binary type to MIME types, and comparing the MIME types.

  6. Copy the entry out as the response body, and set the content-type and last-modified fields of the reply.

  7. Throw a special scriptError to bypass further mainResponder processing and return our response without further modification.

Trying It Out

At the top level of your site, set the value of "#objectNotFoundHandler" to the address of your new "ReturnBinaryObject" script. ReturnBinaryObject is now your objectNotFoundHandler script.

Now go to your browser and load the page "http://localhost/onfhTutorial/download/binary1.zip". Your browser should begin a download of the file; it may ask you how to deal with the downloaded file (it varies from browser to browser). If you "download" the file to disk, you should be able to open or unzip it with your favorite zip utility.

Caveat

The above description describes how to respond to a file-download URL, but does not address getting the URL into a page for a user to click on in the first place. See the ONFH Resources page for information on an available tool that handles both sides of this equation.

Let's look at how to mix modes.

Tutorial Contents
Writing an ObjectNotFoundHandler
ONFH Overview
ONFH Modes of Operation
About The Examples
Mode 1 Example
Mode 2 Example
Mode 3 Example
Mixing Modes
Misdirected URLs
ONFH Summary
ONFH Applications
ONFH Resources
Bonus: The "Penultimate" Master Script
Bonus: Mode 3 Utility Script
About the Author