freeCGI Tutorial: Maintaining state information

HTTP connection (which is used to transfer HTML documents) is state-less. Let's look at a sample interaction between a browser and a server to see what is mean by that.


Browser Server
Generate an request HTTP header
HTTP (as per RFC1945 for HTTP/1.0) contains a specific method such as POST or GET (other can also be used but these two are most common). Along with that, the browser includes additional HTTP information to let the server know what type it is and what the browsring capabilities are. Some browsers can also send HTTP Cookie information, but that is not supported by all browsers and may be turned off by users. Coincidently cookies are a good way to store state, but here we will seek an alternative method due to absence of complete compliance by all browsers.
 
  Parses the HTTP request header and sends HTTP response header
At this point the server knows what is needed and attempts to retrieve it from storage. If an error occurs then an error is sent in the header and possible error message will follow.
Receives response HTTP header and awaits the content
The response header contains useful information about what is to be sent by the server. MIME-type is mandatory and for HTML is will look like Mime-Type: text/html. Other MIME types may be used here, such as image/jpeg, audio/wav, et.al. Another useful field in the header is Content-Length that is needed for binary MIME-types and in some cases to speed up loading of text-based pages.
 
  Send the data of the MIME-type specified in the response HTTP header
This is the actual data from the content that was requested in the request HTTP header. At the end of this the server closes connection and forgets about this session.
Receives data and displays as needed
The browser at this point is probably not connected to the server and the session is over.
 

Hopefully it is clear from the above table that the conection between the server and the browser exists only for the duration of transfering headers and the actual data. Alas, all is not lost, there are several methods for maintaining state. Form variable and cookies and the most common. We will not discuss cookies in this tutorial (but rather defer it to a separate one).

Form variables
There are two ways to pass information back to the server, first let's see what they look like in HTML, we will then discuss them:

1. Let's look at use of HIDDEN form variables to maintain state:

<FORM ACTION="/cgi-bin/MyProgram.cgi" METHOD="GET">
  <INPUT TYPE=TEXT NAME=Username>
  <INPUT TYPE=PASSWORD NAME=Password MAXLENGTH=8>
  <INPUT TYPE=HIDDEN NAME=State VALUE=0>
  <INPUT TYPE=SUBMIT NAME=Login VALUE=Login>
</FORM>
When the user enters the name and password and hits the Login buttom the following request will be sent to the server:
GET /cgi-bin/MyProgram.cgi?Username=Homer&Password=Beer&State=0&Login=Login HTTP/1.0
Here we have 4 variables, one of them is state. Our CGI program will be able to generate forms from now on with the state that contains information about the user so that logging in is not done every time a connection is made. Let's look at another method, then we will see how we can code this.

2. Here is an example using explicit parameters:
<FORM ACTION="/cgi-bin/MyProgram.cgi?State=0" METHOD="POST">
  <INPUT TYPE=TEXT NAME=Username>
  <INPUT TYPE=PASSWORD NAME=Password MAXLENGTH=8>
  <INPUT TYPE=SUBMIT NAME=Login VALUE=Login>
</FORM>
Same thing, except the form variables are submitted via a POST method and the State is submitted via GET. Here what a sample HTTP header would look like for the following FORM (ommiting much of the HTTP header fields for clarity):
POST /cgi-bin/MyProgram.cgi?State=0 HTTP/1.0
Referer: http://www.myserver.com/LoginPage.html
Connection: Keep-Alive
User-Agent: Mozilla/4.01 [en] (WinNT; I)
Content-type: application/x-www-form-urlencoded
Content-length: 49
(must have a blank line here to signify end-of-header)
Username=Homer&Password=Beer&State=0&Login=Login
This may look a bit confusing, but if you look through RFC1945 must of HTTP confusion may be cleared up. We won't worry about the HTTP stuff, just that we need to know what the user is doing. Let's say after logon you have to know which user is submitting information, so that you can chanrge them differently. We need to determine state information about the user whenever a form is submitted.
Here is a code sample that can be used to preform just such a task:
  //Create state data to be saved
  //Assume that pccState is where you will store name and password
  const char *pccState = "Homer:Beer";
  const BYTE *pccEncryptionKey = "MyKey";   //You can do better than this :)
  int iKeyLength = 5;                       //Keys can contain any ASCII value
  
  //Specify which method to use for encryption and encoding
  UNIT uMethod = ACrypto::ectXOR_Convolver|AConverto::eat6Bit;

  //Create a temporary area for the state, the state can have any ASCII character
  //This is why you have to provide the length
  APairItem piTest;
  BYTE *pbState = aMemDup(NULL, iLength);
  int iStateLength = strlen(pccState);
  memcpy(pbState, pccState, iStateLength);

  //Preparing the posting of data
  //You may want to store the user's IP here for validation
  DWORD dwIP = cgiOut.cgiGetIP();    
  piTest.piSet("State");
  
  //This method of APairItem will encrypt and encode the data into a pair variable
  piTest.piSetValueChecked(pbState, iStateLength, dwIP, uMethod, pcbEncryptionKey, iKeyLength);

  //Cleanup, we don't need this anymore
  delete []pbState;

  //At this point you can do anything you want with the APairItem, let's add it to a form
  //We will assume that FORM header was already created
  cgiOut << ">INPUT TYPE=HIDDEN " << piTest << ">" << endl;

At this point we also need a way of retrieving the State variable when a user submits the form to us. We will assume that you have created an ACGI object and parsed FORM submissions.

  
  //Start by finding the State variable in the submission
  APairItem *piState = acgiOut.plGetItemByName("State");
  if (piState)
  {
    const BYTE *pcbNewData;
    DWORD dwIP = cgiOut.cgiGetIP();                 //You get this from CGI environment variables
    int iLength;                                    //Variable used to get the length of the new state
    int iTimeout = 100;                             //100 seconds timeout since the State was last created

    //Specify which method to use for encryption and encoding, must be consistent with creation
    UNIT uMethod = ACrypto::ectXOR_Convolver|AConverto::eat6Bit;

    //Decode our State variable
    pcbNewData = piTest.piDecodeCheckedValueAndGetUserData(iLength, dwIP, iTimeout, uMethod, pcbKey, iKeyLength);
    if (pcbNewData)
    {
      //Let's just display the output, you will of course do what you need with it
      cgiOut << "STATE=" << (const char*)pcbNewData;
      cgiOut << "  length=" << iLength << endl;
    }
    else
    {
      In case it was not found
      cgiOut << "=(null)?" << endl;
    }
  }

By now you should be a bit more comfortable with state variables. The classes you should look at are APairItem, ACrypto, AConverto, and ACGI; to get a better idea of the methods available and how to use them. Also refer to samples/test/state.cpp for a functional example of using state variables.

Enjoy!



Table Of Contents