|Delphi Clinic||C++Builder Gate||Training & Consultancy||Delphi Notes Weblog||Dr.Bob's Webshop|
CGI and ISAPI server-side applications communicate using HTTP, which is a state-less protocol. This means that in order to save "state-information", we must do something special. In fact, there are three common ways to safe state information: FAT URLs, hidden fields and cookies. DrBobCGI supports all three, but I want to focus on passing (and reading) cookies now.
Let's assume we have a CGI application or ISAPI.DLL, called WebApp42, which starts by asking our name, and which needs to "maintain" the "value" of our name for the remainder of the session. If no name can be maintained, then WebServ needs to ask for the name everytime a user re-connects (hence one of the reasons for the need to maintain state). In fact, cookies can remain persistent even over sessions (days later).
Cookies are sent by the server to the browser. When using cookies, the initiative is with the web server, but the client has the ability to deny or disable a cookie. Sometimes, servers even send cookies when you don't ask for them, which can be a reason why some people dislike cookies (like I did for years, for example). There comes a time, however, when cookies are really useful, for example when maintaining state information beyond a single session. In these cases, when information must be retained for a period of time, cookies are just about the only possible solution.
Assuming we use a regular "standard" 32-bit version of Delphi (not the WebBroker components), then we need to use a low-level way to set the value of a new cookie. Fortunately, cookies are set in the HTTP header that a CGI application (or ISAPI DLL) needs to return. The syntax is as follows (the uppercase fields denote values that can be specified by the user):
Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secureBoth the NAME and the VALUE can be anything set by the user. So, we can have Name=Bob or Answer=42 or 1=2. Note that the "NAME=VALUE" pair is the only required attribute of a Set-Cookie command. The DATE in expires=DATE defined the date after which the cookie is invalid (i.e. after which the cookie will no longer be available). DATE must be formatted as follows:
Day, DD-MMM-YYYY HH:MM:SS GMTFor example:
Mon, 01-Feb-1999 07:11:42 GMTNote that GMT is the only legal time zone, enforcing consistency of many (international) visitors and web servers. This means that we may need to convert our time to the GMT timezone, and format the Date according to the above specifications, but that's just about the biggest problem (if any) we'll face when using cookies. The DOMAIN in domain=DOMAIN specifies the internet domain name of the host from which the current URL is fetched. If the domain of the URL is the same as the DOMAIN for a specific cookie in the cookie-list (on disk), then the PATH (in path=PATH) of that cookie is checked as well to see if the cookie indeed should be sent along with the URL fetched from the domain and path as specified. The default value of DOMAIN is the host name of the server that generated the cookie, and the default value of PATH is a single "/" character (meaning everything matches). If you leave both of them empty, then a cookie set for any page in your website will be valid (i.e. sent along with) any other page of your website as well. This may or may not be what you intended, but at least the option is open to you. One warning: the PATH is case-sensitive (I guess the DOMAIN is as well), so be sure to remember that a cookie generated for HOME.HTM will not be sent back to home.htm (if you specified HOME.HTM as specific path, that is). Finally, the "secure" attribute specifies whether or not to use the cookie using a secure channel (i.e. using HTTPS = HTTP over SSL). If secure is not specified, the cookie is considered (mostly) harmless, and will be sent in the clear over unsecured channels.
program WebApp42; uses DrBobCGI, SysUtils; begin writeln('content-type: text/html'); writeln('Set-Cookie: Name=Bob; path=/'); // non-persistent cookie writeln; writeln('<html>'); writeln('<body>'); writeln('<h1>Cookies</h1>'); writeln('<hr>'); writeln(CookieValue('')); writeln('</body>'); writeln('</html>'); end.Note that when you execute the above program, you need to refresh the browser to see the cookie result from CookieValue (the first time, only the cookie will be set, but no value will be send back - this will be done the second time around, when you actually pick up the cookie). That's not a bug but a feature of cookies, of course.
Now that we know how to set the value (and other attributes) of a cookie, it's time to find out how to get (or read) the values from the cookies back from the cookie-file on disk. Fortunately, the hard work (getting the cookies from the cookie-file on disk) is done for us by the web browser, which passes the result in the header of the resulting HTML document (again) using the following syntax:
Cookie: NAME=VALUE; NAME=VALUEIn our specific case, we would get "Cookie: Name=Bob" only, but all matching cookie NAME=VALUES will be sent along with the URL we requested (note that "matching" here refers to matching DOMAIN and PATH, and only for cookies that are not expired yet). Reading cookie values is nothing more than parsing the single "Cookie:" line and obtaining the NAME=VALUE pairs, just like regular CGI GET or POST variables. And in fact, I've used my existing code (unit DrBobCGI) that could process GET and POST variables, and extended it with Cookie support.
Now that we know how to set cookies (using a Set-Cookie statement) and get cookie values back (the values assigned to the HTTP_COOKIE environment string), it's time to pick up the DrBobCGI unit and enhance it with cookie support - the CookieValue function. I've also made sure that when you use a combination of GET and POST, they'll both end up in the Value function. What remains to be done is support for multi-values input fields (i.e. two mentions of the same name with a different value), but that's something for version 4.