10th March 2006
Amended 19th August 2006
Before switching to web development and the stateless HTTP protocol I spent several decades developing client-server systems which were statefull. These came in several flavours:
You should notice that the old block mode protocol with its ENTER key is quite similar to the new HTML form with its SUBMIT button.
This requires the user to create an instance of the application on the client device, which then connects to the central server where it creates an associated instance. Both these instances, on the client and the server, are allocated a portion of memory which may expand or contract. Both instances remain active until terminated by the user, which means that any resources (e.g. memory) which has been allocated to an instance are treated as "locked" and cannot be used by other instances. This usually means that the number of concurrent users is limited by the total number of resources which are available on the server.
During the processing of an application the user may perform many actions, such as navigating through menus, selecting transactions, jumping from one transaction to another, et cetera. Because the application instance does not die until the application is terminated, anything which is stored in memory during one action is instantly available to any subsequent actions. In other words the condition or "state" that the application was in at the end of one action is automatically carried forward into the next action.
It is also possible for the application to send information to the client at any time, without that information being requested.
This does not require the user to start an instance of the application as all access to the application is via a web browser. The application, which is running under a web server, knows nothing about any particular client until it receives an HTTP request. When it receives a request the following happens:
The idea behind a child process going to sleep instead of dying is to avoid the overhead of creating and activating a brand new process each time. With this method a process is created just once, and while it is not actively dealing with a request it sits in a queue of sleeping processes which are waiting to be reactivated.
It should be noted here that even if a child process does not die but merely goes to sleep it loses all memory of the request it just processed and the response which it generated. Even if the next request from the same client is given to the same child process there is no memory of any state left by the previous request. This is the stateless nature of the HTTP protocol.
This means that a client is only consuming resources on the web server during the processing of a request-response cycle. When a client receives a response there is usually a time delay before the next request is issued (if any), and during this time the web server can deal with many other requests from many other clients.
The advantage of this approach is that although there is a limit on the number of child processes which can be active at any one time, the number of active clients can be considerably greater due to the fact that the requests are usually staggered instead of transmitted all at the same instant.
The disadvantage of this approach is that the web server does not maintain any memory of the activities of any particular client, so each request is treated as a new request and not as a follow-up to a previous request.
There is no state maintained at the server end, but what about at the client end? It is true that the browser maintains a history, but this is nothing more than a history of requests that have been sent. If a request is re-transmitted it is treated as if it were a new request. The responses may be cached, but while stepping through the browser's cache there is no communication with the server, so what is being viewed is from the browser's memory on the client, not the server's memory.
As you can see, with a traditional client-server application state is maintained automatically. It does not require any manual intervention on the part of the programmer. This is not the case with a web application, but what manual steps are available to maintain state between requests?
All sessions are handled automatically within the Radicore framework and require no additional programmer intervention. Session data is stored within the database, not the file system, which provides additional security on the session data as well as allowing the application to be deployed across multiple servers in what is known as a server farm.
A session begins when a user logs on and terminates either when that user logs off or when that session data is erased from the server, such as when there has been no activity within that session for a predetermined time limit. It is also possible for a client to have multiple sessions open at the same time and for each session to be independent of the other. Session data is loaded into memory at the start of each script so that the saved values are readily available to the script. When the script terminates the session data is written back to the database to save any changes.
Data for the current script is stored within the $_SESSION array and is only deleted when the framework decides that data is no longer required. Each time a script executes the framework will examine the session array looking for saved data for the script. If it exists then the script will automatically resume from where it left off, otherwise it will start from an initial state.
How does the framework decide that a script's session data is no longer required? This is determined by the navigation direction when the user moves from one script to another. There are only two directions for navigation - forwards or backwards.
By default when a script no longer appears in the current chain of suspended/active scripts its session data will be erased. For scripts of type LIST this behaviour can be altered by using the Keep Data on Exit flag in the Update Task screen. This will enable the current selection criteria, sort order and page number to be saved so that they can be reinstated whenever that script is reactivated.
More information on this topic can be found in Appendix G: Maintaining state with sessions.
During the building of the RADICORE framework I encountered situations which could be dealt with in several different ways. These situations, and my response to these situations, are documented below.
There are two basic ways for a client to submit a request to a server - the GET method and the POST method. The GET method has all its data on display in the URL, so it is visible to the user and easily editable. With the POST method the data is not visible to the user, therefore difficult to edit. Also, requests via the GET method can be bookmarked so that they can be resubmitted at any time. POST requests cannot be bookmarked.
A GET method can be included anywhere in a web page as a hyperlink, but the POST method can only be used with a SUBMIT button.
Although it is possible for the same request details to be sent by either method, as a general rule the GET method should be read-only and all updates should be performed via the POST method. Not following this simple rule could lead to problems - when a search engine examines your site it attempts to follow any hyperlink, and if this request performs an update then an update will be performed. POST requests require a SUBMIT button, and these cannot be selected by any search engine.
In the RADICORE framework all GET methods are read-only, and updates are only performed via the POST method.
I have seen some systems which include the primary keys from their database tables in their URLs, which means that a user could edit the URL and change the primary key to another value. This may possibly allow a user to see a record which is supposed to be "off limits", so it presents a security risk.
In the RADICORE framework no primary keys are exposed in any URLs. The identity of the current selection is maintained in the session data on the server, so this potential security risk does not exist.
A typical user transaction has two passes - a GET request which assembles the current details and sends them to the user, and a POST request which accepts any changes from the user and than applies those changes (after successful validation, of course) to the database. I have seen some applications where the GET request is processed by one script and the associated POST request by another. This whole idea seems counter-intuitive to me as it is impossible to engineer in a traditional client-server application, and I see no advantage of doing it in a web application.
In the RADICORE framework each script for a user transaction deals with both the GET and POST methods. The advantage I see is that each script has its own area in the $_SESSION array, so when the POST method on a script is executed it is easy to locate the data that was available for the previous GET method on the same script.
In some client-server systems I worked on in the distant past it was standard practice for a single program unit to deal with several related screens. I have seen this practice carried forward into some web applications where a single script can deal with several different HTML documents. My preferred choice is to limit each program unit to a single screen. My reasons are documented in Component Design - Large and Complex vs. Small and Simple.
In the RADICORE framework each visible page has its own script, and each script deals with no more than one visible page.
The RADICORE framework has been designed for administrative web applications which can only be accessed by authorised users. This means that nobody is allowed to process a user transaction until their authority has been confirmed. This authorisation process has two steps:
In the RADICORE framework all the security details are maintained in and controlled by a Role Based Access Control (RBAC) system. When constructing the details for menu buttons or navigation buttons, any transactions (tasks) to which the current user does not have access are automatically eliminated from the display. This means that the user is only ever presented with options that he can actually access - if he can't access it then he won't see it, and if he can't see it he cannot select it.
Even if a user tries to edit the HTML source to access an unauthorised transaction, the framework will detect it and reject it.
In a lot of web applications the user is able to move from one transaction to another by means of hyperlinks. These use the GET method and have the current context, such as the identity of the record to be processed, built into the URL. This means that at any time the user can use a bookmarked URL, or edit the current URL, and jump to any transaction within the application.
In the RADICORE framework the ability to jump to other transactions in the application is via one of the following methods:
When I say "context" I mean the identity of the current database record, or the identities of several database records which may have been selected. LIST1 screens do not require context as they can retrieve any number of records. UPDATE1 screens on the other hand cannot function until they are supplied with the identity of a record on which they are to perform that function.
It does not matter whether the GET method or POST method is used, the request will be sent to the current script where it will be processed as follows:
task_idinto the a
This communication between parent and child transactions is particularly significant. It is up to the parent transaction to create the data in the $_SESSION array that will be used by the child. If the child transaction cannot find this data when it is activated it will immediately abort and jump back to the last valid transaction. If it can find data to be processed this data will be deleted when the transaction terminates so that it cannot be processed more than once.
In a lot of web applications where one screen shows several rows of data, one database record per line, and a method is required to jump to a child screen, the most common method is to make each field on each line into a hyperlink. If any of these hyperlinks is selected they will activate the same child screen for the current row on the parent screen. This method has the following disadvantages:
In the RADICORE framework there are no hyperlinks for jumping to child screens, only SUBMIT buttons. This has the following advantages:
One of the problems with a web application is that it cannot control what requests are sent from the client's browser. The web browser is totally under the control of the user, and requests may be generated in any of the following ways:
Apart from the first option it is possible that any of these requests could actually be invalid, such as attempting to repeat a request that should not be repeated. A database update which is inadvertently repeated may actually cause data corruptions from which it may be difficult to recover.
The most typical scenario is where the user visits a screen, performs an update, and is then taken to a second screen. The user then presses the browser's BACK button which causes the browser to re-issue the previous request, the one which updated the database. If you visit any web development newsgroups you will see a common question is "How do I disable the browser's BACK button?" The answer is always the same - "You can't!".
There is no way to prevent the client from ending an invalid request. All you can do is check each request for validity and take appropriate action if it is not.
In RADICORE the framework keeps track of all page requests in a variable called $page_stack. The current transaction is always in this stack. If a child transaction is activated then it is added to the stack after its parent, and its parent is treated as "suspended". If a transaction is terminated it is removed from the stack and its parent is reactivated. When a request is received it is compared with the current stack and checked for validity. This can lead to the following possibilities:
This is documented further in Back Button Blues.
Another problem which can cause unexpected results is the fact that the user may open up more than one browser window to access a particular web application. The following methods are possible:
The problem here is that the different browser windows will all share the same set of cookies. As the identity of the session on the server is by default held in a cookie on the client, by sharing the same session cookie the different browser windows will in effect share the same session data on the server. This may cause problems if the activity in one of the browser windows causes session data which is being used by another browser window to be modified or even deleted. By switching to another browser window the user may assume that the activities in that window can continue from where they left off, but if the session data held on the server has been modified then the results can be completely unexpected.
It is not possible to prevent the user from opening up multiple browser windows into the same web application, so the RADICORE solution is to give the user the opportunity to create a new session on the server for each browser window. The mechanism is as follows:
session_name = session_idwhere:
session_nameis a name which defaults to PHPSESSID.
session_idis a 40-character unique string generated on the server.
NNis a number in the range 1-99.
new sessionwhich, when pressed, has the following effect:
new sessionrequest terminates by sending the current page it has the following effect:
This is documented further in Client Clones and Server Sessions.
© Tony Marston
10th March 2006