A new project: counterCGI.

I set out to write some useful CGI executables after my first success with the testCGI example. This program tells you a lot about the 'I' part of CGI: the Common Gateway Interface.

So I decided it was time to write a counter for webpages, based on a CGI executable. Perhaps not a counter that is visible on screen, but one that works in the background. But perhaps not. We'll see what comes of it.

Below you see the source of what counterCGI looked like, yesterday evening. As was to be expected with new projects on new territory, it dind't work. It only produced errors. More about that later. First things first, so it's time for the....

Source of counterCGI

MODULE counterCGI;

FROM  Arguments	  	IMPORT  GetEnv, ArgTable;
FROM  InOut		IMPORT  WriteBf, WriteLn, WriteString;
FROM  Strings	  	IMPORT  Assign, CAPS, pos, String, StrEq;


TYPE   CGItype        = (QueryString, RequestMethod, none);
       ServerDataType = (Text, Html, Gif, Jpeg, PS, Mpeg);

VAR    content 	 	       : String;
       EnvTable		       : ArgTable;


PROCEDURE InformServer (dataType : ServerDataType);

BEGIN
   WriteString ('Content-Type : ');
   CASE  dataType  OF
     Text  :  WriteString ('text/plain');	      |
     Html  :  WriteString ('text/html');	      |
     Gif   :  WriteString ('image/gif');	      |
     Jpeg  :  WriteString ('image/jpeg');	      |
     PS	   :  WriteString ('application/postscript'); |
     Mpeg  :  WriteString ('video/mpeg');
   END;
   WriteLn;
   WriteLn
END InformServer;


PROCEDURE CheckType (str : String) : CGItype;

BEGIN
   CAPS  (str);					(*  Convert entire string to capitals.	 *)
     WriteString ("In CheckType now....");
     WriteString (str);
     WriteLn;
   IF  pos ('QUERY_STRING', str) = 0  THEN
      RETURN  QueryString
   ELSIF  pos ('REQUEST_METHOD', str) = 0  THEN  
      RETURN  RequestMethod
   ELSE
      RETURN none
   END
END CheckType;


PROCEDURE GetEnvVar (kind : CGItype; VAR  res : String) : BOOLEAN;

VAR   found 		 : BOOLEAN;
      i, j     		 : CARDINAL;
      TexBuf		 : String;
      type		 : CGItype;

BEGIN
   found := FALSE;
   i := 0;
   LOOP
      IF  EnvTable^ [i] = NIL  THEN  EXIT  END;
      Assign (TexBuf, EnvTable^ [i]^);
      WriteString (TexBuf);
      IF  CheckType (TexBuf) = kind  THEN
         found := TRUE;
         EXIT  
      END;
      INC (i);
   END;
   IF  NOT found  THEN  RETURN FALSE  END;
   i := 0;
   LOOP
      IF  TexBuf [i] = '='  THEN  EXIT  END;
      INC (i);
   END;
   INC (i);
   j := 0;
   LOOP
      res [j] := TexBuf [i];
      INC (i);
      INC (j);
      IF  j > HIGH (res)  THEN  EXIT  END;
      IF  i > HIGH (TexBuf)  THEN  EXIT  END;
      IF  TexBuf [i] = 0C  THEN  
         res [j] := 0C;
	 EXIT
      END
   END;
   RETURN TRUE
END GetEnvVar;


BEGIN
   GetEnv (EnvTable);
   IF  GetEnvVar (QueryString, content) = FALSE  THEN
      WriteString ('Environment string not found.');
      WriteLn;
      HALT
   END;
   InformServer (Text);
   WriteString ('The QUERY_STRING variable is : ');
   WriteString (content);
   WriteLn;
   WriteBf;
END counterCGI.
   
In itself, this sourcefile is correct. There is just one error triggering multiple other errors, causing Apache to go ape.

New features..

As you can see, this program already looks like a real Modula-2 program. It has new datatypes like:

TYPE   CGItype        = (QueryString, RequestMethod, none);
       ServerDataType = (Text, Html, Gif, Jpeg, PS, Mpeg);
   
These TYPEs are still preliminary. It's just to show what I'm up to and by looking at later versions you can see how good my current thoughts are. Or could have been... :o)
And a new procedure to match the new type. I want to make a 'MODULE Cgi' from which new CGI executables can just import type's and procedures. The 'InformServer' procedure is just one of them. This is how this procedure looks like now:
PROCEDURE InformServer (dataType : ServerDataType);

BEGIN
   WriteString ('Content-Type : ');
   CASE  dataType  OF
     Text  :  WriteString ('text/plain');	      |
     Html  :  WriteString ('text/html');	      |
     Gif   :  WriteString ('image/gif');	      |
     Jpeg  :  WriteString ('image/jpeg');	      |
     PS	   :  WriteString ('application/postscript'); |
     Mpeg  :  WriteString ('video/mpeg');
   END;
   WriteLn;
   WriteLn
END InformServer;
   
But you have no guarantee it will still be so in September... But the topic of this page is debugging, not gobbledygook.

Debugging countCGI

So I made this new program. I compiled it and it produced an executable. Which ran, but did not produce the webpage I was counting on:

    Internal Server Error

    The server encountered an internal error or misconfiguration and was unable to complete your request.

    Please contact the server administrator, root@kevlar.example.net and inform them of the time the error
    occurred, and anything you might have done that may have caused the error.

    More information about this error may be available in the server error log.

    Apache/1.3.20 Server at hydrogen.fruttenboel Port 80
   
Remember this page: you will have to get used to it while debugging....
This page is aimed at the user, not the developer. Still, it includes an important clue for finding the error: the error log file.

On my Slackware Linux system, that file happens to be /var/log/apache/error_log. And you can look at the last 10 lines by issuing the command 'tail /var/log/apache/error_log'. You can only do so as user 'root'. But give it a try and see what you get:

   [Wed Aug 25 01:03:11 2004] [error] [client 192.168.72.64] malformed header from script.

   Bad header=DOCUMENT_ROOT=/var/www/htdocs: /var/www/cgi-bin/counterCGI
   
Now, if you get this, your CGI isn't outputting data in the right format. The server is very picky about etiquette. If you don't fully behave as prescribed, it will kick you in the butt and do plain old nothing. Not what we were after in the first place.

Look carefully at the sources. When I saw the program go berserk in the first place, I added some WriteString lines in the functions GetEnvVar and CheckType. Just to see if the program worked as expected.
You can run a CGI program from the commandline as well. It will not find the environment strings you need to find. Unless you make one especially for this purpose, like so:

      bash$ QUERY_STRING=HiThere

      bash$ export QUERY_STRING
   
This will put a CGI style environment string in your Unix environment for debugging. Which showed the program worked well. But what the heck was causing the error when the data went to the Apache server?

To cut a long story short: I fooled Apache and Apache kicked me in the by now well known place. The webserver wants:

  1. A content-type string
  2. TWO linefeeds
  3. data as promised in (1)
What I did was feed Apache nonsense data coming from the debugging-WriteString-lines before I told it what kind of data to expect. And then it... well, you know.

The problem was solved by putting the 'InformServer' line just before the 'IF..' statement.

Wrapping up

As can be seen in the source, I wanted to send plain text data to the server. This proved to be a good idea for this webpage topic. If you want to debug a CGI executable do as follows:

Work offline as long as possible. First test your executable on your own, local, computer. Only if you are sure it works perfectly well, upload the file to your cgi-bin.
If in doubt, ask your webhost what their opinion is about home made CGI executables. Just tell them it was written in Mocka and they will agree since it is an unknown system to them and most geeks refuse to admit they never heard of Modula-2... :o)

Plus what changed as of Apache 2.x

As of Apache 2.0 the webserver demands the ContentType listed in another way. Less flexible. I guess some new coder invented something that was faster. And now you get kicked out when you issue

WriteString ('Content-Type : ');
since NO spaces are allowd around the colon.... Nobody informed me anout this new feature.

Page created on August 25, 2004