Web Development


Chapter 6 . Preserving Application State

Unlike other platforms, a Panther application that is running on the Web does not completely control the sequence of its own screens. The inherent Web behavior is for an application to transmit a screen to the browser, break the connection and terminate. The application restarts when the screen is submitted back to the HTTP server, retaining no memory of the previous transmission. Also, the user can disrupt the application's screen sequence by using browser controls, such as the Back push button, to view screens outside the control of the Panther application. The user can also shut down the browser program and never submit the screen back to the server.

However, a typical database application requires continuity across multiple ex changes of data. For example, an application accepts a user name on the first HTML form. After receiving the user name, the application selects database information for that user name and returns it in another HTML form to the browser. When the browser submits the second form with updated information, the application might need the user name from the first form to save the changes. Because the HTTP server is stateless, it has no information about the previous form.

The following sections discuss features and options that let you save the state of a Panther Web application.


Caching Data

In order to pick up where it left off, Panther caches data for a screen before it generates its HTML and transmits it to the browser. A screen's data needs to be cached differently depending whether the METHOD attribute is set to POST or GET.

Posting Screens Back to the Server

By default, Panther generates screens with the METHOD attribute set to POST. With this method, each screen in the application has a push button which submits the screen back to the web application server.

If the screen is posted back to the server before the cache expires, Panther finds the screen's data cache, restores it, and executes the next step in the application. An application can cache screen data either on the browser or on the server; the application specifies its caching method through the initialization option BrowserData, whose default is set to enable server caching.

The user of a Web application can interact with the screen in the following ways. Your data caching options determine the application's behavior in these situations:

The user fills in the screen and posts it without delay or side trips to other screens. Between receiving and posting the screen, the user redisplays another Panther screen through non-Panther controls such as the browser's Back and Forward push buttons.

The user posts the screen, then redisplays it through browser controls and posts it again. (Some browsers detect this action and prompt the user for confirmation of the repost.)The user posts the screen but its data cache has expired. The user quits the browser program and never posts the screen.

If browser caching is enabled, cached data is encoded in the HTML document that is generated for the Panther screen and sent to the browser. When the screen is posted, the browser returns the encoded data cache along with user-entered data. Browser caching must be used if the https server and the http server are unable to share a common cache file directory.

Browser caching offers these benefits:

However, browser caching can (depending on your application) greatly increase the size of the HTML document and the amount of time it takes to download the screen.

If server caching is enabled, cached data is maintained in the cache file directory when a screen is transmitted to the browser. The cache file directory is specified by the initialization option CacheDirectory. When a screen is posted back to the server, the data is retrieved from the cache file and mapped into the Panther screen along with user entries.

If the initialization option RetainCacheFiles is set to 0 (no), Panther automatically deletes cache files after the corresponding screen is posted back to the server.

If the initialization option RetainCacheFiles is set to 1 (yes), Panther retains the cache files until its lifetime exceeds the time limit specified by the ExpireTime initialization option. By default, ExpireTime is set to 120 (two hours). If a screen's cache file is retained, the user can redisplay the screen with the browser's Back push button and repost it.

All server cache files can be retained indefinitely if RetainCacheFiles is set to 1 (yes) and ExpireTime is set to a negative number.

You can also use the monitor utility to delete cache files manually.

If the application lets users go to other Web resources through action list boxes or calls to sm_web_invoke_url, it must retain cache files so the user can return to the Panther Web application.

If the cache file is unavailable when the screen is posted, Panther transmits the smrepost.scr screen, which reports the error that the cache file is unavailable. This behavior can be changed with global variable @web_posted_screen, which contains the name of the most recently posted screen.

One use for this variable is in smrepost.scr. As part of the screen entry procedure for this screen, you can test for the value of @web_posted_screen and specify the application's behavior based on its value.

Getting Screens from the Server

Instead of submitting screens back to the server with a POST, you can use a GET if you retain the current state information on the server and program the screen to store the cache filename and ask for it on the GET as part of the URL.

This allows you to use hyperlinks to retrieve screens, to have state information available in a series of HTML templates, and for all screens in a frameset to share the same state information.

In order to use this method, the web initialization file must have EnableWebid set to 1. Just before the HTML is generated for the screen, obtain the name of the next cache file to be generated using the webid application property and store it in a variable. The following JPL command stores the name in a variable called my_cache.

my_cache=@app()->webid

To retrieve the next screen using a GET, use @webid in the URL:

http://myserver.com/cgi-bin/myapp/next.scr?@webid=my_cache

To use this feature, BrowserData must be set to 0, which allows cache files to be generated. It is also recommended that RetainCacheFiles be set to 1, especially if using multiple screens in a frameset.

Since there are security issues using @webid in the URL which would allow non-application users to retrieve the state information, you can use the previous_form application property to store the name of the last screen that was accessed and then check it on screen entry. The following screen entry procedure calls a security error screen if the previous screen value is not correct:

if (@app()->previous_form != "first.scr")
{
call sm_jform("security_error.scr")
}

Cached Data

Panther automatically caches the following data about an application's state:

If no cached data is available for a screen, Panther posts an error message. Regardless of whether data is cached, Panther always updates the @web_posted_screen variable after each screen posting to contain the name of the posted screen. A screen's entry procedure can use this variable to find out which screen preceded it and thereby determine the application's behavior.

Several objects that are cached can be used to save the application's state–hidden widgets, send bundles, and context globals. These are discussed in the following sections.

When Panther builds the cache file before HTML generation, it saves the values of all hidden widgets on the active screen. When Panther reopens the screen on a subsequent POST, it updates the screen's hidden widgets with their cache values. In fact, all widgets are updated at once: visible widgets are updated with their browser-supplied values; hidden widgets are updated with their cache values. This occurs after normal screen entry processing and before the web_enter event executes.

When Panther builds the cache file before HTML generation, it saves the values of bundles created by the send command. A bundle is accessible if no receive command has been issued on it, or the receive command was issued with the keep option. When Panther opens the cache on a subsequent POST, it restores all bundles before it opens the screen. This occurs before the screen opens so that bundle data is available to the screen's unnamed JPL procedure or any later event.

For example, an application's first screen might put the contents of an address array into the addr bundle:

send bundle "addr" data address

When a subsequent request needs the address values, it executes this receive command and puts the bundle into the whereami field:

receive bundle "addr" data whereami

User-specific information can be saved in JPL context global variables. Each context global is private to a single user of a Web application server; to create one, call sm_web_save_global on a JPL global variable previously created with the global command. When Panther builds the cache file before HTML generation, it saves the values of all context globals. On opening the cache on a later POST, Panther recreates the context globals and initializes them to the cached values. This occurs before the screen is opened, so all context globals are available to the screen's unnamed JPL procedure or later events.

Panther saves context globals in subsequent cache files until the application calls sm_web_unsave_global or sm_web_unsave_all_globals, which remove context globals one at a time or all at once.

For example, the HTML form that accepts the user name might execute the following during processing for the POST event:

proc enter (screen, status)
if ( status & K_WEBPOST)
{
// Create a JPL global
global current_user(31)

// Set it to the value of the widget called user.
current_user = user
// Make current_user a context global so it is
// saved between transmissions.
call sm_web_save_global("current_user")
}

After this procedure executes, requests can refer to the current_user global. For example, if a subsequent request selects database values and a later request saves changes to them, the save screen might include the user name in its status messages:

proc save_changes
call sm_tm_command("save")
if ( sm_tm_inquire(TM_STATUS) == 0 )
{
message = "Thank you, " ## current_user \
## ". Your changes have been saved."
}
else 
{
message = "Sorry, " ## current_user \
## ". Your changes could not be saved."
}
msg emsg message
return

For more about JPL variables, refer to "Variables" in Application Development Guide; also to the global command.


Saving State Data in Cookies

Cookies are pieces of information from the browser side of a connection. After a cookie is set by an HTML document, it is stored on the browser and can be retrieved when that browser contacts the same HTTP server.

Cookies are best used for simple, persistent, client state information, such as a user ID, the date, or the number of times the client visits a specific URL. If the cookie specification includes an expiration date, this information is saved on the browser and is available in subsequent browser sessions by the same user.


Unpreserved State Information

Some information on an application's state is not automatically preserved. If the application requires this information, it should save it with one of the methods described earlier.

LDB

No LDB (local data block) data is stored in the cache. Any changes made to the LDB are known to all users of the Web application server.

Window Stack

If other screens are on the window stack when HTML is generated, no information about these screens is saved in the cache.

For example, consider an application where screen A's submit button opens screen B and screen B contains the following screen entry function:

proc enter (screen, status) 

// Add user name from screen a to title.
title = "Welcome " ## a!user_name

This entry procedure fails when screen B is submitted because screen A no longer exists on the window stack. If the value is not actually needed on post, the problem is corrected by testing for K_WEBPOST:

proc enter (screen, status) 

if !( status & K_WEBPOST )
{
// Add user name from screen a to title.
title = "Welcome " ## a!user_name
}

If the value from screen A is actually needed during the post of screen B, the value should be saved in a context global or as send data.

Property Changes

The cache does not save any property changes in by the application. For example, a screen contains a grid with 200 onscreen rows and a push button that executes this procedure:
proc get_data 

call sm_tm_command("SELECT")
if (grid->num_occurrences < grid->onscreen_rows)
{
grid->onscreen_rows = grid->num_occurrences
}
return

The screen also has another push button that calls this procedure:

proc save_changes 
call sm_tm_command("SAVE")

When the screen opens to execute the get_data button, it executes a select and sets the number of grid rows to the number of rows found by transaction manager. For example, assume that 50 rows are found. When the user later presses the save_changes button, the screen opens and the save command executes. However, when the HTML is generated the second time, the grid shows 200 rows, not 50. To maintain the grid size for a subsequent post, apply the property change each time HTML is generated: save the desired grid size between transmissions with a context global or send bundle; or move the property change to an event that is processed for all events:

proc web_enter 
if (grid->num_occurrences < grid->onscreen_rows)
{
grid->onscreen_rows = grid->num_occurrences
}
return