Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Web Development

Access Counters for Web Pages


Web Development: Access Counters...

Access Counters

for Web Pages

Server-side includes versus the CGI protocol

Andrew Davison

Andrew is a lecturer in the department of computer science at the University of Melbourne, Australia. He can be contacted at ad@cs .mu.oz.au.


An access counter is a useful addition for any World Wide Web page, since it allows both page authors and users to gauge a page's popularity. Naturally, the access counter should be incremented automatically and be pleasing to the eye. In this article, I'll present two approaches to creating access counters, one based on NCSA-supported server-side includes (SSIs), and another that utilizes the Common Gateway Interface (CGI) protocol. For more details on SSIs and CGI-based access counters, see "Using Server Side Includes," by Matt Kruse (Dr. Dobb's Journal, February 1996) and "Tracking Home Page Hits," by Ann Lynnworth (Dr. Dobb's Sourcebook on Web Development, November/December 1995), respectively. Of course, there are other ways of coding access counters, many of them collected at http://www.yahoo.com/Computers/World_ Wide_Web/Programming/Access_Counts/.

An SSI Access Counter

SSIs are supported by the NCSA HTTPD server and have the form shown in Example 1(a). Such commands are embedded in an HTML file and are processed before the file is displayed. The results of the processing will replace the command as the file is displayed by the browser. The features supported by SSIs include

  • Inserting the contents of another file.
  • Embedding the current date or time.
  • Adding file information (for example, the size of the HTML file or the last time it was modified).

The SSI #exec command is very useful and can be used to execute UNIX Bourne-shell scripts and CGI scripts. For example, the #exec in Example 1(b) executes a shell script. This command will insert details about the current users on the system, filtered to include only lines that mention "andrew."

CGI scripts are executed as in Example 1(c), which executes the CGI script called ssi_vis in the cgi-bin directory at the WWW server's site. The crucial difference between #exec and a normal CGI-script evaluation is that with #exec, the output will not replace the WWW page containing the command but will be inserted into the page. (More details on SSIs can be found at http://hoohoo.ncsa.uiuc .edu/docs/tutorials/includes.html.)

There are four drawbacks to using SSIs. First, since the WWW server must parse each file, the server will take longer to send the file. Normally, the Website administrator avoids this by configuring the server to distinguish between ordinary HTML files and those using SSI features. Typically, this is achieved by specifying that only files ending with the extension ".shtml" contain SSIs, and so only those need to be parsed. SSIs appearing in ordinary HTML files (those ending with ".html") are treated as comments.

Another drawback with SSIs is that the #exec command can be very insecure, and may be used to damage a web site. However, if the web server is properly set up, this can be limited by executing scripts as owned by user Nobody, which limits their destructive capabilities. Also, processing of SSI features can be restricted to a specified directory (or directories). However, the best safeguard is to disable the #exec command altogether.

The third drawback is that SSIs are not widely supported. The popular CERN server, for instance, does not have SSI capabilities, although it may in the future. A fix for this is available at http://sw.cse.bris.ac.uk/WebTools/fakessi.html. (fakessi is a Perl script that simulates SSI functionality.)

The final SSI drawback is the way that arguments are passed to the CGI script named in an SSI #exec command. With an ordinary CGI script, the argument can be appended to the end of the script name, preceded by a "?". In Example 2(a), for instance, the argument of the ssi_vis script is pub. Unfortunately, this will not work with the #exec command. Instead, the argument must be added to the end of the filename that contains the #exec command when that file is accessed. For example, assume that the file examp.shtml contains the SSI command in Example 2(b). The argument expected by ssi_vis must be added to the end of examp.shtml when it is accessed, as in Example 2(c).

So why are SSIs worth considering at all? The main reason for using the #exec command is that it makes coding the CGI script straightforward.

The ssi_vis.c Program

Listing One is the C program ssi_vis.c, which takes a single argument assumed to be the name of a file holding an integer. ssi_vis.c reads the value in from the file, prints it, increments the value, and writes the new value back to the file. (The program is also available at http://www.cs.mu.oz.au/~ad/code/counters/ssi_vis.c.)

ssi_vis.c assumes that the counter file is in the directory /home/staff/ad/www_public/code/counter, and ends with the extension .cnt. The file must already exist, contain a number, and be writable by anyone.

The examp.shtml file (see Listing Two) illustrates calling the compiled program (called ssi_vis). The counter filename is passed to ssi_vis as an argument to examp.shtml, either through an href call in another document, as in Example 2(c), or directly when examp.shtml is invoked in Example 2(d). When examp.shtml is processed, it appears on the screen as in Figure 1. Note that the count appears at the location of the #exec command in examp.shtml.

A CGI Access Counter

Using an ordinary CGI script as an access counter allows the code to be used by many different servers. The drawback is that its output completely replaces the calling HTML file in the browser's window.

For instance, assume that you have a CGI access-counter program called "viscount.c" (its compiled version is called "viscount") that, like ssi_vis.c, takes a single argument, which is the name of the file holding the counter. Then an HTML file could contain the line in Example 3(a). The CGI protocol dictates that viscount should generate a complete WWW page, which will replace the original file in the browser's window. This is undesirable for access counters, since the author probably wants the contents of the WWW page and the counter value to be on the screen together. The other drawback of this approach is that the counter web page will appear only when the user clicks on the "Show Counter" anchor string.

A better approach is to embed the CGI call inside an img tag, as in Example 3(b). One immediate advantage is that img tags are executed automatically when a WWW page is loaded. In addition, the output from the script joins the original page in the browser window without replacing it. Unfortunately, this output must be in a recognized image format, such as GIF or JPEG.

Further details on the CGI protocol can be found at http://hoohoo.ncsa.uiuc.edu/cgi, but we now have enough information to develop a strategy for writing viscount.c. It will read in, increment, and update a counter value in a way similar to ssi_vis.c, but will output the value as a GIF image. When the script is called from an img tag, the GIF will be displayed automatically, without replacing the calling file.

Before explaining the details of viscount.c, I'll briefly describe the gd graphics library, which is used to generate the output image.

The gd Graphics Library

The gd library (Version 1.2 at the time of writing) offers a set of extremely useful C data structures and functions for manipulating GIFs, including functions for working with (colored) text, lines, polygons, and circles. The library was developed by Thomas Boutell ([email protected]) for the Quest Protein Database Center at Cold Spring Harbor Labs. gd can be downloaded via anonymous ftp as a compressed tar file from isis.cshl.org in the subdirectory pub/gd.

Most of the library features important for our purposes are illustrated in mk_err.c in Listing Three (also at http://

www.cs.mu.oz.au/~ad/code/counters/mk_err.c).

mk_err.c takes two arguments, a string and a filename, and generates a GIF that is placed in filename-err.gif. The image (which is meant to be used as an error message) contains the string, preceded by "ERROR:", drawn in a large black font against a yellow background, complete with black border. For instance, the call mk_err "use viscount?filename" wrong-args produces the file wrong-args-err.gif; see Figure 2.

In mk_err.c, the errstr variable holds the input string preceded by "ERROR: ". The image being built, im, is initialized using gdImageCreate(), which takes as its arguments the width and height of the image. im is of type gdImagePtr, the key gd data structure for image representation.

The image's background color is set by the first call to gdImageColorAllocate(). The second, third, and fourth arguments of the function are the RGB (red, green, blue) values for the color (0 means off; 255 means full on). There are many utilities for working out these values; I used the UNIX utility xv. The second call to gdImageColorAllocate() defines the color black, which is used to paint the border of the image. The border is actually two rectangles (for a thickness of two pixels) drawn around the edge of the image. gdImageRectangle() takes the x- and y-coordinates of the rectangle's upper-left corner as its second and third arguments. Its fourth and fifth arguments define the lower-right corner.

The call to gdImageString() is the most complicated--it specifies the font (gdFontGiant) and locates errstr in the middle of the image. The starting coordinate is determined by accessing the image's width (im->sx), the width of a font character (gdFontGiant->w), the image's height (im->sy), and the height of a font character (fdFontGiant->h). The image is written out to a file using gdImageGif(), which automatically saves it in GIF format.

The gd library used for this example is located in the directory /home/staff/ad/gd1.2, and the header files for general gd functions (gd.h) and the font (gdFontg.h) come from that directory. In addition, the compilation of mk_err.c with gcc utilizes the -L option to specify the location of the library. The full call to gcc is gcc mk_err.c -o mk_err -L/home/staff/ad/gd1.2 -lgd -lm. It is necessary to include both the gd and maths libraries.

The viscount.c Program

At this point, we can begin coding the CGI script (viscount.c), which reads a counter from a file, outputs a GIF representing the number, and stores the incremented counter back to the file. Listing Four presents viscount.c, also available at http://www.cs.mu.oz.au/ ~ad/code/counters/viscount.c. The compiled program, viscount, is called from list.html, shown in Listing Five (also http://www.cs.mu.oz.au/~ad/code/counters/list.html). The important line in list.html is <img src="http://www .cs.mu.oz.au/cgi-bin/viscount?pub">. viscount treats its input argument as a filename and assumes (in a similar manner to ssi_vis) that the file is in /home/ staff/ad/www_public/code/counters and ends with the .cnt extension. Also, as before, the counter file must be writable by anyone and contain a single integer. In the example, if pub.cnt contains the value 212 when list.html is invoked, then it will appear as in Figure 3.

A number of files and directories must be present in the directory specified in CNT_PATH. For the filename input argument, there must be a corresponding .cnt file and a GIF file (which can initially contain anything, and must be writable by anyone). There are two directories: /numbers and /errs. /numbers must contain ten GIF files--0.gif, 1.gif,...9.gif--each holding a GIF for the number in the title. These GIFs should be roughly the same size as the values in XIMAG and YIMAG defined in viscount.c. The dimensions of the GIFs can be calculated and adjusted using a tool such as xv. The /errs directory must contain GIF files holding error messages, which are required because viscount can communicate with the user only by outputting a GIF. These files can be easily constructed using mk_err.c.

When invoked, viscount opens the counter file and reads the value into count. Note that a read error is reported using serv_err(), which outputs a GIF to stdout. serv_err() utilizes write_gif(), which uses UNIX file descriptors to output the GIF. Using stream I/O might seem a reasonable alternative (for instance, using file pointers and functions like printf()); however, when I tried this approach, the browser failed to display the image between 5 and 10 percent of the time. It seems that reliable GIF output can only be guaranteed by using file descriptor-based functions.

Once count has been given a value, fill_digits() pulls the number apart into its component digits and assigns each to an element of the digits array. This array is passed to concat_ images(), which uses the GIFs from the /numbers subdirectory to build a single GIF corresponding to the count value.

The count image, cnt_im, is big enough to hold NUMSLOT digits in the x-direction, where the image for each individual digit is XIMAGxYIMAG large. An added feature is a scaling factor for the overall image.

cnt_im is filled from the left with GIFs for the digit "0" until the start position of the left-most digit in the digits array is reached. This ensures that cnt_im has a digit in all of its NUMSLOT positions. Each digit of the digits array is represented in cnt_im by using the corresponding /numbers GIF, loaded into num_im. num_im is built via a call to load_image(), which utilizes gdImageCreateFromGif() to extract a gd-formatted image from the specified GIF file.

gdImageCopyResized() is used to insert num_im into the right place in cnt_im, scaling the loaded image so that it fits smoothly. The third and fourth arguments of gdImageCopyResized() are the x- and y-coordinates in cnt_im where the upper-left corner of num_im will be placed. The fifth and sixth arguments make up the upper-left starting coordinates in num_im (in other words, all of num_im is drawn into cnt_im). The seventh and eighth arguments are the width and height that num_im should be scaled to before insertion. The ninth and tenth arguments are its original width and height.

cnt_im is completed with a black border and saved to a GIF file whose name is the filename supplied to viscount.c. This file is immediately read in by print_gif(), which transmits it to stdout, so that it joins the HTML file in the browser window. The output of the GIF is preceded by a Content-type line to signal that a GIF is being sent.

A Brief Comparison

Viscount.c has two main advantages over the SSI-based approach used in ssi_vis.c. First, its use of images allows the access-counter output to be more interesting than the text output from ssi_vis.c. For instance, viscount.c uses different colored GIF images. A more fanciful variation might display the counter in the form of a speedometer dial. This would require some major changes to concat_images(), but could be accomplished with the line- and circle-drawing features of the gd library.

Second, the CGI approach is more portable than the SSI encoding approach, since it is based on features supported by more servers.

The increased number of files needed to support image processing in the CGI approach is a disadvantage, but it permits the style of the image to be adjusted. The code is also more complicated than that found in ssi_vis.c, since it manipulates images.

Example 1: (a) Format of a typical SSI supported by the NCSA HTTPD server; (b) #exec executes a shell script; (c) executing the ssi_vis CGI script.

(a)     <!--#command arg1="value1" arg2="value2" ... -->

(b)     <!--#exec cmd="who | grep andrew"  -->

(c)     <!--#exec cgi="/cgi-bin/ssi_vis" -->

Example 2: (a) The argument of the ssi_vis script is pub; (b) the file examp.shtml contains this SSI command; (c) adding the argument expected by ssi_vis to examp.shtml when it is accessed; (d) invoking examp.shtml.

(a)     /cgi-bin/ssi_vis?pub

(b)     <!--#exec cgi="/cgi-bin/ssi_vis" -->

(c)     <a href="examp.shtml?pub">Counter Example</a>

(d)     netscape examp.shtml?pub

Example 3: (a) Building a CGI counter; (b) embedding the CGI call inside an img tag.

(a)     <a href="http://www.cs.mu.oz.au/cgi-bin/viscount?pub">Show Counter</a>

(b)     <img src="http://www.cs.mu.oz.au/cgi-bin/viscount?pub">

Figure 1: Screen display produced when examp.shtml is processed.

Figure 2: Output of the call mk_err "use viscount?filename" wrong-args.

Figure 3: Screen display produced when list.html is invoked.

Listing one

/* ssi_vis.c  -- by Andrew Davison, ([email protected]), October 1995 */
/* Output a counter value to an HTML file using server-side includes. For 
example, inside examp.shtml, the count will replace the command:
<!--#exec cgi="/cgi-bin/ssi_vis" -->. A counter filename argument (e.g. pub) 
must be supplied when the SSI HTML document is accessed. For example 
<a href="examp.shtml?pub">Counter Example</a>. There should be a file called 
pub.cnt in CNT_PATH which is writable by anyone. The count is read from the 
counter file, output, incremented, and written back to the file.
*/
#include <stdio.h>

#define PATHLEN 70      /* max length of a filename with path */
#define CNT_PATH "/home/staff/ad/www_public/code/counters"
    /* location of the ".cnt" files; change to suit */
void serv_err(char *msg);
int main(int argc, char *argv[])
/* argv[1] contains the counter filename */
{
  char fnm[PATHLEN];
  FILE *fp;
  int count;
  if (argc != 2)
    serv_err("use ssi_vis?filename");
  else {
    sprintf(fnm, "%s/%s.cnt", CNT_PATH, argv[1]);
    if ((fp = fopen(fnm, "r")) == NULL)
      serv_err("counter file cannot be read");
    else {
      fscanf(fp, "%d", &count);
      fclose(fp);
      printf("%d", count);
      count++;
      if ((fp = fopen(fnm, "w")) == NULL)
        serv_err("counter file cannot be updated");
      else {
        fprintf(fp, "%d", count);
        fclose(fp);
      }
    }
  }
  return 0;
}
void serv_err(char *msg)
/* Output an HTML line reporting an error */
{
  printf("<br><b>ERROR:</b> %s.<p>\n", msg);
  exit(-1);
}

Listing Two

<!-- This example will only work with servers which support server side 
includes, such as the NCSA server. The server must also have been set 
up to process ".shtml" files. -->

<html>
<head>
<title>Simple Counter</title>
</head>
<body>
<h1>Simple Counter</h1>
<br>
You have accessed this page:
<!--#exec cgi="cgi-bin/ssi_vis" -->
 times.<p>
</body>
</html>

Listing Three

/* mk_err.c -- by Andrew Davison, ([email protected]), October 1995 */
/* Usage: mk_err "string" filename. Creates a GIF file called filename-err.gif
which contains the string preceded by "ERROR: ". The black text is in gd's 
giant font, with a yellow background. There is a black border. */

/* Makes use of the gd graphics library, 
by Thomas Boutell and the Quest Protein Database Center 
at Cold Spring Harbor Labs. 
COPYRIGHT 1994,1995 BY THE QUEST PROTEIN DATABASE CENTER 
AT COLD SPRING HARBOR LABS. */
/* compilation: gcc mk_err.c -o mk_err -L/home/staff/ad/gd1.2 -lgd -lm   */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "/home/staff/ad/gd1.2/gd.h"
#include "/home/staff/ad/gd1.2/gdfontg.h"

#define YIMAG   40      /* height of the gif image */
#define LENFACTOR  12   /* scaling factor for the gif's width */

int main(int argc, char *argv[])
/* arg[1] is the error string; arg[2] is the filename */
{
  char *errstr;
  int errlen;
  int black, yellow;
  gdImagePtr im;
  char *errfnm;
  FILE *fp;

  if (argc != 3) {
    printf("Usage: mk_err \"string\" filename\n");
    exit (-1);
  }
  errstr = (char *)malloc(sizeof(char)*(strlen(argv[1])+8));
  sprintf(errstr, "ERROR: %s", argv[1]);
  errlen = strlen(errstr);

  im = gdImageCreate((LENFACTOR*errlen), YIMAG);
  /* yellow background */
  yellow = gdImageColorAllocate(im, 255, 255, 0);
  black = gdImageColorAllocate(im, 0, 0, 0);
  /* a black border */
  gdImageRectangle(im, 0, 0, (LENFACTOR*errlen)-1, YIMAG-1, black);
  gdImageRectangle(im, 1, 1, (LENFACTOR*errlen)-2, YIMAG-2, black);
  /* a black centered error string */
  gdImageString(im, gdFontGiant,
    im->sx / 2 - (errlen * gdFontGiant->w / 2),
    im->sy / 2 - gdFontGiant->h / 2,
    errstr, black);
  errfnm = (char *)malloc(sizeof(char)*(strlen(argv[2])+9));
  sprintf(errfnm, "%s-err.gif", argv[2]);

  if ((fp = fopen(errfnm, "wb")) == NULL)
    printf("Cannot write to %s gif file\n", errfnm);
  else {
    gdImageGif(im, fp);
    fclose(fp);
  }
  gdImageDestroy(im);
  return 0;
}

Listing Four

/* viscount.c -- by Andrew Davison, ([email protected]), October 1995 */
/* viscount reads a counter from its input file, and builds a gif representing 
the counter. The gif is output, the counter is incremented, and written back 
to the file. The intention is that the gif will appear inside an IMG tag in a
WWW page. Usage: <img src="http://www.cs.mu.oz.au/cgi-bin/viscount?pub">
The 'viscount' script name must be followed by a "?" and the name of the file 
holding the access counter (i.e. "pub" in this case). This is assigned to 
countnm. The counter file is assumed to be in the directory CNT_PATH and be 
called <countnm>.cnt. There must also be a gif file called <countnm>.gif. Both
these files must be writable by anyone. The <countnm>.cnt file should 
initially contain the value 1. The <countnm>.gif file can initially contain 
any kind of picture. <countnm>.gif is built from a series of 'number' gifs
which should be in a subdirectory below CNT_PATH called /numbers. There should
also be an /errs subdirectory  holding gifs for possible 'viscount' errors.
A countnm gif can consist of at most NUMSLOTS digits.
*/
/* Makes use of the gd graphics library, by
   Thomas Boutell and the Quest Protein Database Center 
   at Cold Spring Harbor Labs.
   COPYRIGHT 1994,1995 BY THE QUEST PROTEIN DATABASE CENTER 
   AT COLD SPRING HARBOR LABS.
*/
/* compilation: gcc viscount.c -o viscount -L/home/staff/ad/gd1.2 -lgd -lm  */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>     /* for write(), close(), read() */
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>      /* for creat(), open() */
#include "/home/staff/ad/gd1.2/gd.h"

#define PATHLEN 90      /* max length of a filename with path */
#define NUMSLOTS 4      /* maximum number of digits in the countnm gif */
#define BUFSIZE 512     /* size of chunk to be read from gif file */

/* the original size of a digit GIF is 50 by 60 (approximately) */
#define XIMAG   50
#define YIMAG   60

/* scale factor for the countnm gif */
#define SCALE  2 

#define CNT_PATH "/home/staff/ad/www_public/code/counters"
   /* location of <countnm>.cnt, <countnm>.gif, 
      the /number and /errs gifs subdirectories; change to suit */
void fill_digits(int cnt, int ds[], int *p);
void concat_images(int ds[], int dignum, char *cnm);
void load_image(gdImagePtr *im, int num);
void save_image(gdImagePtr im, char *cnm);
void print_gif(char *cnm);
void serv_err(char *errnm);
void write_gif(int fd);
void wstring(char *s);

int main(int argc, char *argv[])
/* argv[1] is the access counter filename */
{
  char *countnm;          /* full name of ".gif" file holding the count */
  char cntfnm[PATHLEN];   /* full name of ".cnt" file */
  FILE *cfp;              /* file pointer to ".cnt" file */
  int count;
  int digits[NUMSLOTS];
  int dignum = 0;

  if (argc != 2)
    serv_err("wrong-args");
  countnm = argv[1];
  sprintf(cntfnm, "%s/%s.cnt", CNT_PATH, countnm);
  if ((cfp = fopen(cntfnm, "r")) == NULL)
    serv_err("cnt-noread");
  else {
    fscanf(cfp, "%d", &count);
    fclose(cfp);
  }
  fill_digits(count, digits, &dignum);
  concat_images(digits, dignum, countnm);
  print_gif(countnm);


  count++;
  if ((cfp = fopen(cntfnm, "w")) == NULL)
    serv_err("cnt-noupdt");
  else {
    fprintf(cfp, "%d", count);
    fclose(cfp);
  }
  return 0;
}
void fill_digits(int cnt, int ds[], int *p)
/* Divide cnt into its component digits and store them in the ds[] array. e.g.
for the number "214", "2" is stored in ds[0], "1" in ds[1], etc */
{
  if (cnt !=0) {
    fill_digits(cnt/10, ds, p);
    if ((*p) < NUMSLOTS)
      ds[*p] = cnt%10;
    else
      serv_err("cnt-toobig");
    (*p)++;
  }
}
void concat_images(int ds[], int dignum, char *cnm)
/* Build the counter image by first filling it with '0' gifs upto the beginning
of the number. Then add the gif corresponding to each digit in the ds[] array.
Finish by adding a black border.
*/
{
  int x;
  int im_pos = 0;
  gdImagePtr num_im, cnt_im;
  int black;
  int xsize = SCALE*XIMAG;    /* x size of the counter image */
  int ysize = SCALE*YIMAG;    /* y size of the counter image */

  cnt_im = gdImageCreate((xsize*NUMSLOTS), ysize);
  black = gdImageColorAllocate(cnt_im, 0, 0, 0);

  /* If necessary, fill from the left with '0' gifs */
  if (dignum < NUMSLOTS)
    for (x = NUMSLOTS-1; x  >= dignum; x--) {
      load_image(&num_im, 0);
      gdImageCopyResized(cnt_im, num_im, im_pos*xsize, 0, 0, 0,
                           xsize, ysize, num_im->sx, num_im->sy);
      im_pos++;
      gdImageDestroy(num_im);
    }
  /* Now add the gifs for the ds[] array values */
  for (x=0; x < dignum; x++) {
    load_image(&num_im, ds[x]);
    gdImageCopyResized(cnt_im, num_im, im_pos*xsize, 0, 0, 0,
                       xsize, ysize, num_im->sx, num_im->sy);
    im_pos++;
    gdImageDestroy(num_im);
  }
  /* a black border */
  gdImageRectangle(cnt_im, 0, 0, xsize*NUMSLOTS-1, ysize-1, black);
  gdImageRectangle(cnt_im, 1, 1, xsize*NUMSLOTS-2, ysize-2, black);

  save_image(cnt_im, cnm);
  gdImageDestroy(cnt_im);
}
void load_image(gdImagePtr *im, int num)
/* Load the 'number' gif corresponding to num into an image structure. The gif
should be in numbers/num.gif */
{
  char nm[PATHLEN];
  FILE *fp;
  sprintf(nm, "%s/numbers/%d.gif", CNT_PATH, num);
  if ((fp = fopen(nm, "rb")) == NULL)
    serv_err("gif-noread");
  else {
    *im = gdImageCreateFromGif(fp);
    fclose(fp);
  }
}
void save_image(gdImagePtr im, char *cnm)
/* Save the counter image into a gif file called cnm.gif */
{
  char nm[PATHLEN];
  FILE *fp;
  sprintf(nm, "%s/%s.gif", CNT_PATH, cnm);
  if ((fp = fopen(nm, "wb")) == NULL)
    serv_err("gif-nowrite");
  else {
    gdImageGif(im, fp);
    fclose(fp);
  }
}
void print_gif(char *cnm)
/* Load and print the gif file called cnm.gif. The intention is that the image
will appear inside an IMG tag in a WWW page. */
{
  char nm[PATHLEN];
  int fd;
  sprintf(nm, "%s/%s.gif", CNT_PATH, cnm);
  if((fd = open(nm, O_RDONLY)) == -1)
    serv_err("gif-noread");
  else { 
    wstring("Content-type: image/gif\n\n");
    write_gif(fd); 
    close(fd);
  }
}
void serv_err(char *errnm)
/* Output an error gif to the HTML page. Assumes the existence of the file 
/errs/errnm-err.gif, otherwise it quietly dies. */
{
  char nm[PATHLEN];
  int fd;
  sprintf(nm, "%s/errs/%s-err.gif", CNT_PATH, errnm);
  if((fd = open(nm, O_RDONLY)) != -1) {
    wstring("Content-type: image/gif\n\n");
    write_gif(fd);
    close(fd);
  }
  exit(-1);
}
void write_gif(int fd)
/* Use read() to access the file with the fd file descriptor. */
{
  char buffer[BUFSIZE];
  int nread;
  while ((nread = read(fd, buffer, BUFSIZE)) > 0)
    if (write(STDOUT_FILENO, buffer, nread) == -1)
      exit(0);
}
void wstring(char *s)
/* Write the s string to stdout */
{
  if (write(STDOUT_FILENO, s, strlen(s)) == -1)
    exit(0);
}

Listing Five

<html>
<head>
<TITLE>Published Stuff in 1995</TITLE>
</head>

<body>
<H1>Published Stuff in 1995</H1>

The access count for this page is:<p>
<img src="http://www.cs.mu.oz.au/cgi-bin/viscount?pub">
<p>

Further information can be found by clicking on its title (although
I haven't got round to that for all of them). <p>

<ul>
<li> Lee, J., and Davison, A. 1995.
"The BeBOP System", <i>ACSC'95,
18th Australasian Computer Science Conf.</i>, Adelaide,
South Australia, January. <p>

<li> Davison, A. (editor). 1995a. 
<i><a href="../../humour/humour.html">Humour The Computer</a></i>,
The MIT Press, May. <p>

<li> Davison, A. 1995b. 
"<a href="../../paper-links/forms.html">Programming with HTML Forms</a>",
<i>Dr. Dobb's Journal</i>, Vol. 20, No. 6, June, p.70-75. <p>

<li> Davison, A. 1995c. 
"<a href="../../paper-links/images.html">Clickable Images in HTML</a>",
<i>Dr. Dobb's Journal</i>, Vol. 20, No. 9, September,
p.18-27. <p>

<li> Loke, S.W. and Davison, A. 1995.
"Logic Programming and the World Wide Web", <i>INAP-95:
The 8th Symp. and Exhibition of Industrial Applications
of Prolog</i>, Tokyo, Japan, October. The paper in 
<code>gzip</code> compressed
postscript format (71K) is available
<a href="http://www.cs.mu.oz.au/~swloke/papers/paper1.ps.gz">here</a>.<p>

<li> Davison, A. 1995d.
"Teaching C after Miranda", <i>FPLE'95:
Symp. on Functional Programming Languages in Education</i>,
Mook, The Netherlands, December. <p>

<li> Davison, A. 1995e.
"<a href="../../paper-links/animate.html">Animation in Netscape</a>",
To appear in <i>Dr. Dobb's Journal</i>. <p>

</ul>

</body>
</html>


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.