Session Management

In a normal desktop application, a user continuously works in one application. If a second user wants to work in that application, he will have to launch the application on his own machine; he, too, will then have an instance of the application for his own exclusive use. Only if certain data are accessed will conflicts occur; these conflicts can be resolved through tried and proven strategies like the blocking of records.

The situation is completely different on a Web server. Here, only one instance of the application is running and all users access the same (sole) instance at random. However, every user expects the impression to prevail that he has sole use of the application. He expects the application to know who he is when he presses the "Save" button and what part of the program he is in at that moment.

While in the desktop world the users are automatically kept separate by virtue of the physical conditions prevailing in that environment, in Web applications we will have to take care of this issue ourselves. As a first step we have to create a possibility to recognize a user when he sends his second request to the server - the session ID. This recognition is of little use if we have no further information about the user. We must be able to associate information about a user with his session ID. This is called session state.

Since we have to store user information on the server, we use up server resources. In this case they are memory space and hard disk space. Even if the space required for the individual user is minimal – in due course it expands to considerable size. Thus, there has to be a point in time when these data will be deleted. In a desktop application we normally do this the moment we terminate an application, i.e. at log-out. The Web server knows no such moment. The user sends his request – the server sends a response. This is where the mutual commitment and the communication stop.

Now it is up to the user to initiate a new communication. On the server side we have no possibility of checking whether the user wants to do this. There is only one solution available to us – to set a time limit for the session information. The server re-sets an internal clock every time the user accesses it. Once the clock runs for a certain duration indicating there has been a corresponding period of inactivity, all session information is deleted. We call this time limit session timeout. In the AFP, the standard setting is 30 minutes. You may adjust this value in afp.config across the board.

All we need to know now is how to report the session ID to the user and above all how to ensure that it will be returned. Netscape invented the cookie principle. Contrary to a widely held view among users cookies by themselves are harmless and take no active actions. Actually, they are minute files that are delivered by a Web server and stored in the machine of the user. Any time the machine accesses the server the user's browser will send along the cookie. The server itself has no direct access. Rather, the server has to rely on the user to save the data and to send them along.

AFP 3.0 utilizes so-called session cookies, in contrast to earlier versions. These cookies are held in the browser memory, they are never stored. As soon as the browser is terminated these cookies are lost. Even if a second browser instance is launched it is unaware of the cookies of the first instance. Since it is impossible to use session cookies to build user profiles over a prolonged period, browsers will let you optionally activate session cookies even if all other types of cookies are blocked.

Moreover, AFP 3.0 won't send a cookie to the browser once it has been accepted. Even users who have a dialogue displayed every time a cookie is sent will now have to answer this question only once and not multiple times for each page as has been the case up to now.

Even if AFP cookies belong to the most innocuous category as far as firewalls, browser etc. are concerned, you still can't rely on cookies 100 percent of the time. Many users deactivate cookies completely and this is unlikely to change in the near future. AFP 3.0 provides an additional option so these users won't be excluded. AFP will write only the session ID to the cookie; this ID can be used to find all the other information. The session ID is a character string of 44 resp. 10 characters in length that can be transmitted in different ways.

Instead of relying on the browser you can attach a session ID to any URL as a normal QueryString parameter. All incoming request are checked for this particular parameter; if a value is found it can be used as session ID. If a browser hands in both, the HTTP cookie takes precedence. Some of you might recall this technique; in AFP 2.4 it was utilized as AFPCookie. In the meantime it is supported by all server products one way or the other, even by Microsoft's ASP.NET.

The benefit of this technique is obvious – it always works, even if a user does not want to allow cookies. There is a drawback to this, too: as a developer you have to ensure that the session ID is present in each link of your documents. If you forget to use Session.Url() in just one link, the session will be aborted at that point and the user will be assigned a new ID. In practice this means that the user will have to log on again.

Every time a user wants access, AFP 3.0 will establish the user's session ID and reactivate that session. This reactivation is limited to conducting a search of the corresponding record in the file Session.dbf and updating the time of last access. If a non-existent session ID is handed in or the session had already expired a new session will be initiated.

The primary purpose of the session management in AFP 3.0 is not the management of a group of variables; rather, it serves as a kind of identity card that can be used in various ways. Aside from the session ID which is administered automatically there is also a session directory. Any kind of file may be deposited there. After three minutes, AFP will automatically delete all files or directories in that directory that do not start with a valid session ID.

The .afp takes advantage of this to save all variables with the initial G in a MEM file. Every time a page is invoked these variables will load automatically and will be saved at conclusion. You can therefore set up a separate set of variables for each user without great effort. Since VFP will temporarily save these variables to the hard disk you may not assign object references to them. VFP releases these objects the next time a page is requested. This could lead to the strange effect that objects do not stick when being created in the .afpa.code file, but do seem to work when being created in the page directly. To store object references use the App.SetData() and App.GetData() pair of functions.

Since saving and loading will also affect performance you can deactivate this behavior selectively for certain pages through

Document.lSaveVariables = .F.

Document.lLoadVariables = .F.

You may use session-specific data like these for more things than just variables. If you generate files in other programs you can use names like these. This will ensure that temporary files like PDF documents are auto-deleted.

If you have to save data in tabular form these files aren't merely an advantage – they are outright indispensable. In an AFP application you would normally use a cursor for this. In Web server applications this is rarely possible. If multiple AFP instances have been loaded, user requests will go to the next available instance but not necessarily to the same instance that was accessed during the previous request. Since the cursor can only be open in one instance, these data are no longer available. That aside, the cursor has to be closed eventually. This is why you should always use tables for data in tabular format. AFP 3.0 will automatically delete these tables at the expiry of a session.

lcName = Session.GetSessionFileName("Data.dbf")

If not File(m.lcName)

  Create Table (m.lcName) ( cField C(50) )

  Use

EndIf

Use (m.lcName) Again Alias Data in Select("Data")

* … Do something with Data here

Use in Data

This will also let you generate files in other programs and then show these files in the browser without having to publicize a directory that all users can access. If e.g. you generated a PDF file, you can display it in the browser as follows:

<%

  lcPDF = FileToStr(Session.GetSessionFileName("Sample.pdf"))

  Response.ContentType = "application/pdf"

  Response.AddHeader( "Content-Disposition", "inline" )

  Response.BinaryWrite(m.lcPDF)

<%