Web pages implement Cookies: an API for storing string values keyed by names:
GetCookie(name)
-- returns a string valueSetCookie(name, value)
-- associates the string value with a nameThis web page dicusses implementing a Cookie-like facility as a CGI (written in C). That is, something like the familiar cookie API, but with the data stored on the web server rather than the browser, so cookie changes affect all future visits to the web page for everyone.
Web browsers implement "cookies" - little name and value pairs of data: give it a name, get back a value. That way, when the user comes back to a web page, it can restore its previous settings. But, browsers store the cookies in a cookie text file on the browser's computer. Move to different computer, look at the same web page, and your cookies aren't there. That's fine if that's what you want, but sometimes you'd like to make a change on one compute and see the change on a different computer.
The classical solution to this problem is just re-upload a modified web page. But that user interface sucks: if you want to make controlled minor changes, want to forbid major changes (in this application), and don't want to set up software beyond a web browser, it may not make sense to use a separate uploading application. You might use an on-line word processor like Google Documents, but that makes it too easy to make large changes to the document, changes you might not, in all cases, want.
CGI
- Common Gateway Interface: A spec for writing programs that can be called as subroutines by web servers.
My wife had, on her previous smartphone, a shopping list application. She had a master list of all the items she'd bought. She could run down it checking off the items to buy this time. She'd sync the list to her phone, view the list sorted in the aisle order of her favorite supermarket, and quickly buy the items on her list, checking them off as she went.
She changed to an iPhone, and asked me to re-implement the shopping list as a web page, so she could modify it sitting at her desktop computer, then view it on the iPhone.
Our web hosting company uses the Apache web server. It allows us to have CGIs on our web site. The CGIs must live in a folder named “cgi-bin
” and must have a “.cgi
” extension. When it runs my CGI, it runs it using my username, so it has access to my
files and only my files.
The host provides ftp access for moving files, but it also provides shell access and a C compiler, so I could write a CGI in C, upload the source, and compile it on the web host machine. This lets me test using the copy of Apache, pre-installed in OS X, which reads
from /Library/Webserver/Documents/
.
Before you spend your time writing CGIs, verify that you’ll be able to get the to run CGIs from two webservers: your development machine, and the machine of your hosting company.
Verify that you can get any CGI to work at all, by compiling the following C program, naming the result “HelloWorld.cgi
”, and putting in the cgi-bin
folder of your production web server and your test we server. For example,
on Macintosh OS X, put HelloWorld.cgi
inside the folder /Library/Webserver/CGI-Executables/
, turn on Personal Web Sharing
in System Preferences, and see if using a browser to look at http://localhost/cgi-bin/HelloWorld.cgi returns a web page saying:
“Hello World”.
#include <stdio.h> int main(){ printf("Content-type: text/text\n\nHello, World!\n") ; return 0; }
Common problem: the machine language of the program is downloaded, or worse dumped out, into the browser, instead of the program executing. If that happens, you'll need to see the Apache how-to page on .htaccesss and CGI access for more information.
Note that, Apache is configured by default on OS X to look in /Library/Webserver/CGI-Executables
, not /Library/Webserver/Documents/cgi-bin/
, as you would expect, for CGIs for the machine-global web site http://localhost, and it isn't configured for CGIs in the per-user sites at all.
The Apache configuration lives in
/etc/conf/httpd/
Panther(?)/etc/conf/apache2/
Tiger/private/etc/apache2/httpd.conf
Leopard(Different revisions of OS X have it in different places.) In that folder, httpd.conf is the main configuration file, while the files for the per-user sites are in a folder named “users
” inside the main configuration folder.
We’ll be using the Javascript primitive: XMLHTTPRequest
to talk to our CGI.
Even though it’s called XMLHTTPRequest
, it appears to let us send and receive arbitrary
text without any problem, at least for Firefox and Safari. Which is good, because we'll
be sending and receiving not XML, but Javascript. See JSON.org for more information on using
Javascript fragments as a communications protocol.
We use eval() to convert a raw text buffer into a javascript object.
You’d think this would be a security hole, but it isn’t that bad. XMLHTTPRequest
is only fetching from our own server, and if someone can spoof the DNS to get the browser to read bad javascript from our domain, well we’ve already lost: the bad guys could just send the bad javascript at the start. But, there is no way for the user of the web page to get something to round trip through the server that will mess up other users.
The Javascript to assign a value to a name is straightforward.
function SetServerCookie(name, value) { var http = new XMLHttpRequest(); var url = "http://example.com/cgi-bin/servercookie.cgi?"; http.open('POST', url, true); var sendBody = name + '=' + JSONEscapeString(cValue); http.send(sendBody); }
That JSONEscapeString
just puts in the backslash escapes and surrounds the string with double quotes.
function JSONEscapeString(s) { return '"' + s.split('\\'). join('\\\\'). split('\"'). join('\\\"') + '"'; }
The Javascript to fetch the value for a name is little more complex: We request the value frm the server, but the reply may take awhile, so we must provide a callback function. We create the callback function as needed so we can bind a few local variables. That way, when the server reply makes the callback run, it will have the data it needs. This way, we can multiple request to the server active simultaneously, and they won't step on each other.
GetServerCookie
itself will call a function with the value of name as a parameter.
function Foo(){ var cookieVal = GetCookie('myName'); if (cookieVal) { UseCookie(cookieVal); } }With server-side cookies, you'd rewrite this as:
function Foo(){ GetServerCookie('myName', UseCookie); }(If the cookie isn't set,
GetServerCookie
will call UseCookie('')
.)
// single char lowercase name, call func with value. function GetServerCookie(name, func){ var http = new XMLHttpRequest(); var url = "http://example.com/cgi-bin/servercookie.cgi?"; // The server-side script http.open('GET', url + name, true); http.onreadystatechange = function(){ HandleHttpFetch(http, func); }; http.send(null); }and the helper routine,
HandleHttpFetch
is:
function HandleHttpFetch(http, func){ if (4 == http.readyState) { func(eval(http.responseText)); } }
i.e., when the reply is complete, eval the reply text to get a string object, then pass it to func.
Create a starter cookies.txt file, so the CGI will modifying an existing file rather
than creating one from scratch. It can be empty.
Make sure you have the protections on the cookies.txt set correctly for your web server.
In my personal configuration, the copy on my local machine had to be set to the owner www
while the copy on my hosting service had to be set to my normal account.
Pick a location for your cookies.txt that is not readable for regular web pages. That makes it less likely that your server cookies will be used in a way you don't expect (like indexed by a search engine.)
This is a CGI, not a FastCGI. The difference is that a CGI starts from scratch on each HTTP request, reading in the entire file. A FastCGI runs until it quits, waiting for messages from the web server.
These cookies are totally public. A more sophisticated program might use a client-side cookie to hold a session id, put the session id on the http URL, and the CL that receives it could decide to continue the session, or force the user to log in again.
This page has presented straight C code for implementing simple, global, server side cookies.