[OS/2 e-Zine! - Now on CD (Click here).]

[Previous]
Rexx Files- by Dr. Dirk Terrell
 [Next]

This month I had a client who needed to implement a "shopping cart" type of application which enables customers to order items from his web site. Handling the order for one item is pretty straightforward using the CGI techniques discussed in the December, 1996 column. (Unless you are pretty familiar with CGI programming, I would suggest that you look at that article before continuing with this one.)

If you want to process an order that contains multiple items, things can get a little tricky. If the catalog is not too large, then you can put all of it on one page and let the customer check off multiple items and process them all in one submission to your CGI script. If the catalog is too large though, that method won't work because the page will take too long to download and be unwieldy to use. If you must break the catalog up into separate pages then you run into the problem of HTTP being a stateless protocol. Stateless simply means that there is no inherent connection between one access of a web server and another. If you call a CGI program twice, there is no way of connecting the results of the two accesses. With HTTP, a client makes a request of the server which the server satisfies in some way, and then the connection is broken. Obviously this presents a problem if you want people to be able to browse through a multiple page catalog and check off multiple items to be ordered. How can the server know what was previously ordered?

One solution to this problem is the use of cookies, a name chosen for no obvious reason that I can tell. Cookies are small bits of information stored by the client's browser and associated with your server. You have probably heard about cookies and their potential for misuse. I won't go into the ethics of using cookies here except to say that like most things, cookies themselves are not good or bad but how they are used can be. In the case of our need to track multiple connections, cookies provide a convenient solution.

If you use Netscape Navigator, open the file cookies.txt in your Netscape directory with a text editor like OS/2's Enhanced Editor (EPM). It will have entries that look like this:

www.netscape.com	FALSE	/	FALSE	942189160	NGUserID	cc98a714-18593-893105250-3
For what we're doing, the format of the cookies.txt file is not important. But what you can see is that a cookie is associated with a particular web server and only that web server can retrieve the contents of the cookie. A cookie has a name (NGUserID above) and a value (cc98a714-18593-893105250-3). In our case of needing to identify a person across multiple submissions to our CGI program, we used a cookie called SESSION_ID and made it a 12 digit random number.

So, how is the cookie created in the first place? It's really pretty simple, you simply return a line in the header of your response to the client of the form

Set-Cookie name=value; path=/somepath; expires=date and time; domain=domain_name
where name is the name of your cookie (case-sensitive) and value is the value associated with the cookie. The path is the path on the web server to be associated with the cookie. This could be a file (e.g. /catalog/page1.html or just / if it is to be associated with all paths on the server). And expires is a date and time (GMT) for the cookie to expire and be removed by the browser. If you do not specify an expiration date, the cookie expires when the client closes their web browser. Finally, domain is the domain to be associated with the cookie. This must contain at least two dots to prevent the possibility of someone's using ".com" for example, and getting all of your cookies from any site with ".com" in the address. A valid value might be ".os2ezine.com" or "www.os2ezine.com". You can leave off the domain and it will default to the address of the server setting the cookie. Here is a concrete example:
Set-Cookie: SESSION_ID=54635397865; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT path=/ domain=.os2ezine.com
The code to send back the header of the response to the web browser might look like:
Say 'Set-Cookie: SESSION_ID='||Session_ID||'; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT path=/ domain=.os2ezine.com'
Say "Content-type: text/html"
Say
Say
Say "CGI Results:<P>"
Once you have created a cookie on the client end, that cookie will be passed to your server anytime a request is made and the path in the cookie matches the requested document. The cookie will be passed in the environment variable HTTP_COOKIE. You will recall that in the first article we wrote our Rexx code so that it would run under OS/2 and Unix, which have different ways of retrieving the contents of environment variables. Under OS/2 Rexx, you use the value() function and under Unix you use the getenv() function. So, modifying our previous code a little bit, we can get the contents of any cookies that the client browser sends to us:
Select
   When OperatingSystem="OS/2" then do
      method = value("REQUEST_METHOD",,env)
      cookie = value("HTTP_COOKIE",,env)
      len = value("CONTENT_LENGTH",,env)
      if (method == "GET") Then Do
         query_string = value("QUERY_STRING",,env)
      end
   End
   Otherwise /* We're on a Unix machine */
      method=getenv("REQUEST_METHOD")
      cookie=getenv("HTTP_COOKIE")
      len = getenv("CONTENT_LENGTH")
      if (method=="GET")  then do
         query_string=getenv("QUERY_STRING")
      end
      If (method == "POST") & (len \= "") Then Do
        /* use POST method to pass parameters */
        post_string = charin(,,len) 
        query_string = post_string
      End
End /* Select */
After executing the above code, the variable cookie will contain the contents of any cookies that are appropriate to the current query by the browser. You can then parse the contents to get the values stored in the cookie. In my program, I needed to know if the client had been assigned a session ID and if not, to assign one. Here is the code I used:
If Pos("SESSION_ID",Cookie)<>0 then Do
   /* SESSION_ID exists in the cookie, so parse for its value */
   Parse Var Cookie . "SESSION_ID=" Session_ID .
   If Session_ID="SESSION_ID" then /* just in case we assigned a session ID before creating it */
         Session_ID=Left(Random(1,100000),6,"0")||Left(Random(1,100000),6,"0")
   End
Else Do
   /* There was no SESSION_ID in the cookie so create one */
   Session_ID=Right(Random(1,99999),5,"0")||Right(Random(1,99999),5,"0")
End
What this code does is check to see if there is a SESSION_ID in the cookie. If so, it parses the contents of the cookie to get the value stored in SESSION_ID. Then it checks to make sure that it doesn't have the value SESSION_ID which would result if we set the cookie before defining a value for our Rexx variable. If the cookie doesn't contain a value for SESSION_ID, then we create one. The value for the session ID is generated using the Random() function. Right() is used to tack zeroes onto the random number to make it have a length of five digits. Performing those calls twice and concatenating the results gives us a session ID that has twelve digits. You could, of course, generate the numbers in many other ways.

And that's all you have to do to use cookies in your CGI programs. The example code (.CMD, 1K) creates a random session ID and shows you the value of any cookies passed to your server. The code will serve as a good template for creating your own CGI programs that need to use cookies. You can find Netscape's documentation for cookies at http://www.netscape.com/newsref/std/cookie_spec.html.

* * *

Dr. Dirk Terrell is an astronomer at the University of Florida specializing in interacting binary stars. His hobbies include cave diving, martial arts, painting and writing OS/2 software such as HTML Wizard.


[Previous]
 [Index]
 [Feedback]
 [Next]

[Our Sponsor: Indelible Blue - OS/2 software and hardware solutions to customers worldwide.]

Copyright © 1998 - Falcon Networking ISSN 1203-5696