http:// / colors.shtml

Selecting a color scheme

The "General" menu at the top of the page has options to select which stylesheet you want to use. Note that clicking one of these links will set the cookie and immediately return you to the page you were previously visiting, but using your new stylesheet preference.

Ever since I started this site, I've been receiving complaints about the color scheme. Supposedly, it's been scientifically proven that using a light background and dark text is easier on peoples' eyes, and using yellow as the link color makes it almost impossible to print the pages, because yellow doesn't show up very well against a white background- and nobody wants to waste the ink/toner to print a black background.

So I changed the colors, to a "normal" color scheme with black text on a white background. Personally, I think it looks too generic, but if it made the complaints stop...

In less than a day I was flooded with messages from people who didn't like the new colors, and who wanted the old colors back. This was not, however, a universally held sentiment- I also got a few messages from people who liked the new color scheme, and thanked me for changing it. (One even went so far as to tell me what font sizes and kerning values I should be using, but that was an isolated case.)

It was obvious to me that the "right answer" was to come up with a way for the web site visitors to be able to select which color scheme they wanted. The most obvious way to store this preference is, of course, in a cookie. However, I wasn't sure how involved I was going to have to get, and how much extra load it was going to add to the server.

As it turns out, server-side include directives are able to use the HTTP_COOKIE variable, and access any cookies sent by the browser. The trick was to figure out the mechanics of how to send one of two different tags, based on the value of a cookie, without having to call any CGI scripts (because CGI scripts force a new process to be executed, which is expensive in terms of both CPU and time.)

Obviously, I got it working, after a bit of trial and error. It does require that every page on the site be a server-side include page (i.e. a ".shtml" file, which the pages on this site already were) and involves replacing the existing stylesheet tag at the top of each page with a server-side include tag.

How it works

The idea is that each page checks the cookie, and generates the appropriate "<link>" tag to use that stylesheet. The cookie is shared with other sites in the "" domain, although I haven't yet gone through the other sites and added the tag which uses the cookie to choose a stylesheet. The cookie itself is human-readable; it will be either "stylesheet=black" or "stylesheet=white", and will expire one year after you set it. It will not send a cookie with every page, therefore the one-year timer will not start over with every page you visit.

And if you don't have a cookie- if you're a first-time visitor, or if you don't select a stylesheet, or your browser doesn't accept cookies) then you will get the black background. Why? Because that's the one I prefer. (It is my web site, after all...)

As for how it works... there are two parts of it.

Setting the cookie

The link you click to set a stylesheet cookie runs a CGI script called stylesheet.cgi. (The text file is a symlink to the real stylesheet.cgi script- I had to give it a different name so apache would show it to you instead of running it.) This script uses the PATH_INFO environment variable to pick up any remaining parts of the URL. For example, if your browser sends a request for "/stylesheet.cgi/white", the PATH_INFO variable will contain "/white".

The script contians a list of valid stylesheet names- currently "black" and "white". These are the ONLY values it will accept- if there is no value, or if the value is not one of these, then it uses the first value on the list (which just happens to be "black") as the value.

The script also figures out when the cookie should expire, by taking the current time and adding one year, and uses the HTTP_REFERER environment variable to figure out what page you were looking at when you clicked on the link.

It then sends back an HTTP response to the browser, which sets the cookie and directs the browser to reload the page you were looking at before. If the cookie was changed, you'll see the effects of that change.

Using the cookie

Each page whose stylesheet is being automatically changed, has this tag in the "<head>" section of the page:

<!--#include virtual='/stylesheet.shtml' -->

You won't see the tag if you view the page source in your browser. This is a server-side include tag, which causes apache to read the indicated file, and act as though it where part of the current page. And because the file you're including is also an .shtml file, apache parses it as well- which means any server-side include commands within that file will also be processed before the page is sent.

The stylesheet.shtml file (again, the text file is a symlink to the real file, with a different name so apache will show it to you instead of running it) looks like this:

<!--#if expr="$HTTP_COOKIE = /stylesheet=white/" -->
<link rel="stylesheet" type="text/css" href="/jms1_white.css">
<!--#else -->
<link rel="stylesheet" type="text/css" href="/jms1_black.css">
<!--#endif -->

The "$HTTP_COOKIE" variable, if it exists, will contain all of the cookies which were sent with the current request. These cookies may be in any order within the variable, so we do a regular expression match instead of a normal string comparison. It looks for the specific value "stylesheet=white" within the list of cookies. If it finds it, the code will emit the "<link>" tag which uses the white stylesheet. Otherwise it emits the tag to use the black stylesheet.

Note that I could have saved a step by embedding the five lines from stylesheet.shtml into the top of every page. However, if I ever add another stylesheet to the mix, I only need to change this one file (and the stylesheet.cgi script) in order to add it, rather than having to edit every single page on the site (which is the whole reason I'm using server-side includes to begin with.)