Graceful Degradation

One of the challenges of developing an effective Web-site editor is anticipating all the ways users can possibly mess things up! This process is referred to as "graceful degradation." Here, I'll address two standard graceful-degradation issues: data storage during error checking, and file locking.

Temporary Data Storage

In a traditional data-entry environment (such as Access or FoxPro), information is never written directly to the file while it is being entered or before it is checked for errors. Typically, the data is held in temporary variables while it is being entered. Then, once the user instructs the program to save the data, the program performs the necessary edit checks before saving the data to the file. If the program detects errors, the user can correct them without retyping everything and overwriting the existing data.

Maintaining graceful degradation in a stateless environment like the Internet poses a unique challenge because you cannot store data in temporary variables while edit checks are performed. In the case of the Web-site editor, this problem becomes an issue when a user enters the wrong username/password to update his site. Since it is vital that the user edit only the most-recent version of the data, the HTML form used to edit the site data includes a meta no-cache tag to prevent the editor from displaying old data.

Consequently, if the user were to press the browser's "Back" button after being notified that she had entered the wrong username/password, her changes would be lost. To address this problem, we designed the script to store the data in a temporary file and create a flag file that lets the editor know that the temporary data should be displayed in the event the user tries to edit the site again in the same user session.

To implement this feature, we first define a user session. The only instance in which the script needs to display data from the temporary data file is when a user enters a bad username/password combination and then tries to edit the data again in a single user session. In this script, the user's remote IP address (found in the environment variable REMOTE_ADDR) is stored in a preference file (pref.txt) while the data is displayed for editing. When the script is run again, it compares the user's remote IP address to the one on file. If they match up, it's the same user session. Otherwise, this is a new user, and the permanent site data will be displayed for editing. This is the same technique that analysis packages use to determine user sessions in access-log reports (see, for instance, The code for creating the preference file is found in Listing One.

Listing Two shows the authentication segment of the Web-site editor code that creates the temporary data file. First, the function reads the data from STDIN through the read_parse function (code not included). Then the function checks whether the data being saved is older than the existing data. If so, the data is stored to a temporary data file (tempdata.txt) by calling the writedatafile function (code not included). Once the temporary data file is created, the script validates the username and password entered in the form against those on file for that site. If the check fails, a blank file called passwdfail.txt is created, and the script grinds to a halt after encouraging the user to press the "back" button and enter the correct username and password.

If the user presses the "back" button, his changes will be displayed via the selectdatafile function in Listing Three. This function is the first one executed in the script when it is asked to display site data. It determines whether the script should pull the site data from the permanent file or the temporary file and assigns the appropriate filename to the $source variable. First, the function tests for the existence of the passwdfail.txt file. If the file is not found, the $source variable points to the permanent data file; if it is, the function deletes it. If the user cancels out of the update, deleting the file will prevent the script from displaying the temporary data if he enters the editor during the same user session. Once the passwdfail.txt file is deleted, the function compares the user's remote IP address to the one in the preferences file. If they match up, this is the same user session,so the$source variable points to the temporary data file; if not, this is a new user session, and the $source variable points to the permanent data file.

By thoroughly tracking user activity a script can degrade gracefully in this scenario -- even on the stateless Internet.

Simulated File Locking

Any time data-entry programs are used in a network environment, file locking is an issue: Who has rights to update a data file when multiple users have accessed the data to update it? In an environment that has state, a file can be locked when a first user opens it, and unlocked when the user finishes making updates. Unfortunately this will not work on the Internet, because the user could exit the browser without executing a script to unlock the file. The only graceful way to handle this problem on the Internet is to track the date/time the record was last modified in a lastmod field in the data file and allow the user to save his changes only if the date/time stamp on his changes matches the one in the data file. This is the method we used in the Diocese's Web-site editor.

When the editor opens a data file to retrieve the data, it stores all the records in an associative array called %elements. The date/time the file was last modified is stored in $elements{'lastmod'} and written into a hidden field called lastmod in the update form (Listing Four). Before saving the changes, the script first compares the date/time stored in the data file to those stored in the hidden field. If they match, the data is stored; if not, another user has saved his changes after the current user accessed the data file. Consequently, this user's changes cannot be saved, and the user is notified of the discrepancy.

When we originally looked into resolving this problem, the idea of locking out the first user if another user subsequently accessed the data, modified it, and saved it seemed unfair to the initial user. However, since there is no way to effectively lock a file while a user is updating its contents, this method is the only graceful way that we have found so far to resolve this fundamental problem.