How to make an interactive CGI program in C++
A CGI program runs in a special environment:
- On Rabbit, CGI programs should have names that end in .cgi,
should be executable by everyone (chmod 711) and be in your
public_html directory or a subdirectory thereof.
- It only receives user input that was entered in a form on an HTML
page. If the form uses the preferred POST method, that input is available
in a simple format on standard input; i.e. you can read it using cin>>,
getchar(), or gets().
- The output it produces will be displayed by a web browser, so it must be
prefixed by a simple HTTP header. After the header, you may send either plain
text or (preferably) HTML.
- Extra input can be sent to a CGI program by including extra text in the
URL after a question mark '?'. This data is placed in an environment variable
called QUERY_STRING under unix systems, and is very easy to access.
- When a CGI program is running, it is run by (or "logged in" as) the web
server. This means it will not be permitted to read or write other files in
your directories unless they are so un-protected that everyone else on the
system would also be able to read or write them. For input data files, that's
OK, but it is nearly always a bad idea to make any files writable by
everyone. The best policy is not to have your CGI programs create files.
- Output is sent to a web browser using the NVT-ASCII system. This simply
means that every line of output must be terminated with TWO characters, not
the traditional unix single newline character. These two characters are
known as carriage-return and line-feed, and their C/C++ representations
are \r and \n. So just remember to terminate each line
of output with \r\n.
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:
- compile it with this command: CC cgidemo.cpp -o cgidemo.cgi
- make it accessible with this command: chmod 711 cgidemo.cgi
- 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
|
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
|
CGI demo stage two
Please enter the %d numbers to be averaged:
|
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"); } }