How to make an interactive CGI program in C++

A CGI program runs in a special environment:

An interactive CGI program should be designed to run incrementally:

The link to a CGI program from your container web page should be of this form: <a href=myprog.cgi?step1> (where myprog is of course the name of the CGI program). The "?step1" at the end of the URL tells the program that it is being run for the first time, so it should not expect any user inputs; it will have to ask for them.

Here is a basic C++ program that could be used in this way. Throughout this document, I will be adding to this basic program so that eventually it carries out a complete interactive computation.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int step_number_specified=0;
int step_number=0;

void read_query_string(void)
{ char *qs=getenv("QUERY_STRING");
  if (qs==NULL) qs="";
  if (strncmp(qs, "step", 4)==0)
  { step_number_specified=1;
    step_number=atol(qs+4); } }

void main(void)
{ read_query_string();
  if (step_number==1)
  { printf("Content-Type: text/html\r\n");
    printf("\r\n");
    printf("<html><head><title>CGI demo stage one</title></head>\r\n");
    printf("<body><h1>CGI demo stage one</h1>\r\n");
    printf("So far, so good...\r\n");
    printf("</body></html>\r\n"); }
  else
  { printf("Content-Type: text/plain\r\n");
    printf("\r\n");
    printf("ERROR IN USAGE\r\n");
    if (!step_number_specified)
      printf("No \"?stepN\" at end of URL\r\n");
    else
      printf("Invalid step %d specified in URL\r\n", step_number);
    printf("\r\n"); } }

If we assume that this program is called cgidemo.cpp, and is in your public_html directory, all you need to do is:
  1. compile it with this command:   CC cgidemo.cpp -o cgidemo.cgi
  2. make it accessible with this command:   chmod 711 cgidemo.cgi
  3. run it by typing this URL into a web browser:   http://rabbit.eng.miami.edu/students/username/cgidemo.cgi?step1

Examining the program, you can see that there isn't much to it. Four standard libraries are included. Although this is a C++ program, I still prefer to use the C libraries to keep things clean and simple.

The first thing the program does (in main) is to read the query string. This function accesses the environment variable QUERY_STRING; if it exists, its value will be whatever followed the ? in the URL. The strncmp function does a limited string compare, so it is asking if the first four characters after the ? were step. If they were, it uses the standard function atol to convert the rest of the string into an integer.

If that integer (step_number) is an expected value (so far, only step 1 is recognised), the main program is happy. It sends a simple little HTML page back to the web browser. Notice that every line of output is properly terminated with \r\n. Notice also that the first two lines of output are unusual; they are the HTTP header and are absolutely required. The first says Content-Type: text/html, it informs the web-borwser that it is about to receive some standard HTML and should handle all the markup tags appropriately. The HTTP header MUST be terminated with a blank line.

After the HTTP header indicating that HTML is to follow, simply make the program print a normal HTML document. It will be displayed exactly as you'd expect.

If there is something wrong with the input, the program demonstrates the other simple way of sending output. If the content type is text/plain (html has been replaced by plain), then the remainder of the document (after the required blank line) is displayed by the web browser exactly as it appears, in a typewriter-like font. This is usually the easiest way to handle error conditions or provide yourself with debugging information. Try typing the URL without the ?step1 to see the results.

That's all there is to a very simple CGI program. This one isn't very interactive; all it does is react to the presence or absence of ?step1 at the end of the URL, but it is not difficult to go further, as you are about to see...

Getting useful user input

If the program is to receive input from the user, it must provide a form with input elements on the HTML page that it prints. A form is simply a section of HTML surrounded by <FORM ...> and </FORM> tags.

The opening <FORM> tag should specify that when the submit button is pressed the same CGI program should be called again, this time with ?step2 at the end of the URL, and that it should have the contents of the form posted to it.

Between the form tags, there will usually be some text input elements where the user can type some data, and a submit-button input element that the user presses when ready to proceed. Input elements are given names that allow the CGI program to refer to them individually.

To continue the demonstartion, we will make the program calculate the average of some numbers provided by the user. In order to produce the right number of input boxes for the numbers to be typed, the program must first ask how many numbers are to be entered. For variety it will also ask what the user's favourite colour is.

The following code segment replaces the if (step_number==1) section of the example program; everything else remains unchanged for the time being.
  if (step_number==1)
  { printf("Content-Type: text/html\r\n");
    printf("\r\n");
    printf("<html><head><title>CGI demo stage one</title></head>\r\n");
    printf("<body><h1>CGI demo stage one</h1>\r\n");
    printf("This program will calculate the average of some numbers<br>\r\n");
    printf("<form action=cgidemo.cgi?step2 method=post>\r\n");
    printf("  How many numbers would you like to enter? <input type=text name=number size=10><br>\r\n");
    printf("  What is your favourite colour? <input type=text name=favcol size=20><br>\r\n");
    printf("  <input type=submit value=\" DO IT \">\r\n");
    printf("</form>\r\n");
    printf("</body></html>\r\n"); }

After recompiling and rechmodding, accessing the same URL again produces this display:

CGI demo stage one

This program will calculate the average of some numbers
How many numbers would you like to enter?
What is your favourite colour?


Of course, if you press the "DO IT" button, you get the "Invalid step 2 specified in URL" error message because we haven't yet told the program how to handle step two, but at least we are one step closer.

The following code segment follows the if (step_number==1) section of the example program; everything else remains unchanged for the time being.
  else if (step_number==2)
  { printf("Content-Type: text/html\r\n");
    printf("\r\n");
    printf("<html><head><title>CGI demo stage two</title></head>\r\n");
    printf("<body><h1>CGI demo stage two</h1>\r\n");
    read_inputs();
    printf("You said you would enter %d numbers<br>\r\n", input_number);
    printf("and your favourite colour is %s<br>\r\n", input_favcol);
    printf("</body></html>\r\n"); }

Obviously we also need to define the read_inputs function. Its job must be to process the inputs provided from the original form, and set the global variables input_number and input_favcol accordingly. This function is the last one that needs to be defined to make fully functional interactive CGI programs.

The read_inputs function is not very difficult. All the contents of the form are provided to a CGI program on the standard input stream; each input element is represented by its name, followed by an = sign, followed by the value as typed by the user. Multiple input elements are separated by & signs. So, if the user enters 12 and blue on the form before pressing "DO IT", the CGI program will receive "number=12&favcol=blue" as input.

Here is a simple implementation of read_inputs that will do the job:

int input_number=0;
char input_favcol[100];

void read_inputs(void)
{ char name[100], value[100];
  int stage=0, pos=0;   
  name[0]=0; value[0]=0;
  while (1)
  { int c=getchar();
    if (c==EOF || c=='&')
    { if (stage==1)
      { if (strcmp(name, "number")==0) input_number=atol(value);
        else if (strcmp(name, "favcol")==0) strcpy(input_favcol, value); }
      stage=0;
      pos=0;  
      name[pos]=0;
      if (c==EOF) break; }
    else if (c=='=')
    { stage=1;
      pos=0;  
      value[pos]=0; }
    else if (stage==0 && pos<99)
    { name[pos]=c;
      pos+=1;
      name[pos]=0; }
    else if (stage==1 && pos<99)
    { value[pos]=c;
      pos+=1;
      value[pos]=0; } } }

The function maintains two strings, one for the name of the input element it is processing, and one for its value. The variable stage tells it whether it is currently reading a name or a value. The variablepos tells it how many characters of the name or value it has already read.

If it meets the end of input or an ampersand (&), that means that it has completely read one name and value pair. It compares the name of the input element against the names of all the expected input elements, and copies (or converts) the value string into the appropriate variable.

If it meets an equals (=) sign, that means it has finished reading an inout element's name, and should now start reading its value, so the stage and pos variables are updated and it continues.

If it meets any other character, it is added to the end of either the name string or the value string, depending on which is currently being processed.

For complex inputs, other special processing may be required. If the user types an & or = or any of the other characters with special meanings, that character will not appear in the input; instead it will be replaced by a percent (%) sign, followed by two hexadecimal digits giving the character's ASCII code. In this example, we assume that all inputs will be composed of simple letters and digits. After recompiling and rechmodding, accessing the same URL again produces the expected input display. If we enter 12 and blue as the inputs, then press "DO IT", we see the results of the second stage of processing:

CGI demo stage two

You said you would enter 12 numbers
and your favourite colour is blue


It should be clear now how to complete the program. Stage two should not simply repeat what the inputs were, it should print out a new HTML form, with an input element for each of the (e.g. 12) numbers that are to be input. It is easy to generate unique names for each of the input elements (see the sample code below). This new form should of course have as its submit action the same URL, but with ?step3 at the end.
  else if (step_number==2)
  { printf("Content-Type: text/html\r\n");
    printf("\r\n");
    printf("<html><head><title>CGI demo stage two</title></head>\r\n");
    printf("<body><h1>CGI demo stage two</h1>\r\n");
    printf("Please enter the %d numbers to be averaged:<br>\r\n", input_number);
    printf("<form action=cgidemo.cgi?step3 method=post>\r\n");
    for (int i=0; i<input_number; i+=1)
      printf("  %d: <input type=text name=n%d size=10><br>\r\n", i, i);
    printf("  <input type=hidden name=number value=%d><br>\r\n", input_number);
    printf("  <input type=hidden name=favcol value=\"%s\"><br>\r\n", input_favcol);
    printf("  <input type=submit value=\"Calculate\">\r\n");
    printf("</form>\r\n");
    printf("</body></html>\r\n"); }

The program generates a new form with input elements named n0, n1, n2, ..., n11 to receive the list of input numbers.

As the original inputs are still likely to be useful, and it would not be reasonable to expect the user to retype them at every stage, we can arrange for them to be built in to the form as hidden or invisible inputs that will automatically be sent back to the program along with the new data when the submit (calculate) button is pressed.

We will obviously also have to add some processing ability for step three. Assuming that the read_inputs function is modified to read the 12 numeric inputs into a suitably named array, this will be easy:
  else if (step_number==3)
  { printf("Content-Type: text/html\r\n");
    printf("\r\n");
    printf("<html><head><title>CGI demo stage three</title></head>\r\n");
    printf("<body><h1>CGI demo stage three</h1>\r\n");
    read_inputs();
    printf("The average of these %d numbers:<br><tt>\r\n", input_number);
    int sum=0;
    for (int i=0; i<input_number; i+=1)
    { printf(" %d", input_n[i]);
      sum+=input_n[i]; }
    printf("</tt><br>is <tt>%.3f</tt><br>\r\n", (double)sum/input_number);
    printf("and your favourite colour is still %s<br>\r\n", input_favcol);
    printf("</body></html>\r\n"); }

The change to the read_inputs function is very small. Apart from declaring the array int input_n[100];, we simply change the part that actually assigns values to variables thus:
      { if (strcmp(name, "number")==0) input_number=atol(value);
        else if (strcmp(name, "favcol")==0) strcpy(input_favcol, value);
        else if (name[0]=='n' && name[1]>='0' && name[1]<='9')
        { int index=atol(name+1);
          input_n[index]=atol(value); } }

And that gives the final version of the program. A run-through from the beginning would look something like this:

CGI demo stage one

This program will calculate the average of some numbers
How many numbers would you like to enter?
What is your favourite colour?


CGI demo stage two

Please enter the %d numbers to be averaged:
0:
1:
2:
3:
4:


CGI demo stage three

The average of these 5 numbers:
10 17 11 13 9
is 12.000
and your favourite colour is still blue


Naturally, you can make the output nicer by using a better selection of fonts, by putting input elements in a <table> and whatever else you feel like, and of course, the program could do something a lot more complex than averaging a few numbers, but nothing more complex that was shown in this example would be needed.


The complete example program is shown here
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int step_number_specified=0;
int step_number=0;

void read_query_string(void)
{ char *qs=getenv("QUERY_STRING");
  if (qs==NULL) qs="";
  if (strncmp(qs, "step", 4)==0)
  { step_number_specified=1;
    step_number=atol(qs+4); } }

int input_number=0;
char input_favcol[100];
int input_n[1000];

void read_inputs(void)
{ char name[100], value[100];
  int stage=0, pos=0;
  name[0]=0; value[0]=0;
  while (1)
  { int c=getchar();
    if (c==EOF || c=='&')
    { if (stage==1)
      { if (strcmp(name, "number")==0) input_number=atol(value);
        else if (strcmp(name, "favcol")==0) strcpy(input_favcol, value);
        else if (name[0]=='n' && name[1]>='0' && name[1]<='9')
        { int index=atol(name+1);
          input_n[index]=atol(value); } }
      stage=0;
      pos=0;
      name[pos]=0;
      if (c==EOF) break; }
    else if (c=='=')
    { stage=1;
      pos=0;
      value[pos]=0; }
    else if (stage==0 && pos<99)
    { name[pos]=c;
      pos+=1;
      name[pos]=0; }
    else if (stage==1 && pos<99)
    { value[pos]=c;
      pos+=1;
      value[pos]=0; } } }

void main(void)
{ read_query_string();
  if (step_number==1)
  { printf("Content-Type: text/html\r\n");
    printf("\r\n");
    printf("<html><head><title>CGI demo stage one</title></head>\r\n");
    printf("<body><h1>CGI demo stage one</h1>\r\n");
    printf("This program will calculate the average of some numbers<br>\r\n");
    printf("<form action=cgidemo.cgi?step2 method=post>\r\n");
    printf("  How many numbers would you like to enter? <input type=text name=number size=10><br>\r\n");
    printf("  What is your favourite colour? <input type=text name=favcol size=20><br>\r\n");
    printf("  <input type=submit value=\" DO IT \">\r\n");
    printf("</form>\r\n");
    printf("</body></html>\r\n"); }
  else if (step_number==2)
  { printf("Content-Type: text/html\r\n");
    printf("\r\n");
    printf("<html><head><title>CGI demo stage two</title></head>\r\n");
    printf("<body><h1>CGI demo stage two</h1>\r\n");
    read_inputs();
    printf("Please enter the %d numbers to be averaged:<br>\r\n", input_number);
    printf("<form action=cgidemo.cgi?step3 method=post>\r\n");
    for (int i=0; i<input_number; i+=1)
      printf("  %d: <input type=text name=n%d size=10><br>\r\n", i, i);
    printf("  <input type=hidden name=number value=%d><br>\r\n", input_number);
    printf("  <input type=hidden name=favcol value=\"%s\"><br>\r\n", input_favcol);
    printf("  <input type=submit value=\"Calculate\">\r\n");
    printf("</form>\r\n");
    printf("</body></html>\r\n"); }
  else if (step_number==3)
  { printf("Content-Type: text/html\r\n");
    printf("\r\n");
    printf("<html><head><title>CGI demo stage three</title></head>\r\n");
    printf("<body><h1>CGI demo stage three</h1>\r\n");
    read_inputs();
    printf("The average of these %d numbers:<br><tt>\r\n", input_number);
    int sum=0;
    for (int i=0; i<input_number; i+=1)
    { printf(" %d", input_n[i]);
      sum+=input_n[i]; }
    printf("</tt><br>is <tt>%.3f</tt><br>\r\n", (double)sum/input_number);
    printf("and your favourite colour is still %s<br>\r\n", input_favcol);
    printf("</body></html>\r\n"); }
  else
  { printf("Content-Type: text/plain\r\n");
    printf("\r\n");
    printf("ERROR IN USAGE\r\n");
    if (!step_number_specified)
      printf("No \"?stepN\" at end of URL\r\n");
    else
      printf("Invalid step %d specified in URL\r\n", step_number);
    printf("\r\n"); } }