Table of Contents
This appendix describes how CGI applications work and provides bulletin board implementations in C and Perl.
CGI is used to process static HTML pages to deliver dynamic content.
A user can interact dynamically with an HTML web page by calling an application program. The application program processes the request and returns the result, which is displayed on the page as if the web page itself processed the request.
An electronic bulletin board system (BBS) will serve as an example to illustrate the use of CGI. A web bulletin board receives input data from a user. The BBS then uses that data to generate an HTML document and sends the document to the browser. The role of CGI in the BBS is to convert the user input into an HTML document. The HTML document is then displayed by the browser. Users can access the bulletin board to post data in real time through CGI. A bulletin board program acts as a "pencil" to write data directly on a web page.
This appendix implements a web service by using CGI. The example bulletin board is written in C while the guest book is written in Perl.
This example describes a bulletin application written in C for UNIX. This web page can post user text input and address by calling "board.cgi" from "board.html".
The following is a sample environment file.
<sample_cgi.m>
#Configuration file ( sample_cgi.m ) *DOMAIN webtob *NODE webmain WEBTOBDIR = "$WEBTOBDIR", SHMKEY = 72000, HTH=1, DOCROOT = "docs/", PORT = "8080" *HTH_THREAD hworker WorkerThreads = 8 *SVRGROUP cgig NODENAME = webmain, SvrType = CGI *SERVER cgi SVGNAME = cgig, MinProc = 1, MaxProc = 5 *URI uri1 Uri = "/cgi-bin/", SvrName = cgi, Svrtype = CGI *ALIAS alias1 URI = "/cgi-bin/", RealPath = "${WEBTOBDIR}/cgi-bin/"
The following is a sample HTML page code.
#board.html <HTML> <HEAD><TITLE>WebToB Board</TITLE></HEAD> <BODY> <H2><big>WebToB Board Upload</big></H2> <HR WIDTH=500 ALIGN=left><BR> <FORM METHOD=post ACTION="/cgi-bin/board.cgi"> <TABLE WIDTH=500 BORDER=0> <TR> <TD>Writer</TD> <TD><INPUT NAME=writer SIZE=20></TD> </TR> <TR> <TD>Title</TD> <TD><INPUT NAME=title SIZE=50></TD> </TR> <TR> <TD COLSPAN=2><B>Contents</B></TD> </TR> <TR> <TD COLSPAN=2> <TEXTAREA NAME=doc COLS=60 ROWS=10></TEXTAREA> </TD> </TR> <TR> <TD>E-Mail</TD> <TD><INPUT NAME=email SIZE=40></TD> </TR> <TR> <TD>Home Page</TD> <TD> <INPUT NAME=homepage SIZE=40 VALUE="http://"> </TD> </TR> </TABLE> <BR> <INPUT TYPE=submit VALUE=" Submit "> <INPUT TYPE=reset VALUE=" Clear "> </FORM> </BODY> </HTML>
The following is a sample CGI source code.
#attachment 3: board.c #include "qDecoder.h" int strcheck(char *str) { /* Check if string is NULL or length is 0 */ if (str == NULL) return 0; if (strlen(str) == 0) return 0; return 1; } int main(void) { char *name, *title, *doc; char *email, *homepage; /* Get input vlaues */ name = qValue("writer"); title = qValue("title"); doc = qValue("doc"); email = qValue("email"); homepage = qValue("homepage"); /* Check input values */ if (!strcheck(name)) qError("Type Your Name !"); if (!strcheck(title)) qError("Type Title !"); if (!strcheck(doc)) qError("Write Your Messages !"); if (strcheck(email)) if (!qCheckEmail(email)) qError("Type Your E-Mail !"); if (strcheck(homepage)) if (!qCheckURL(homepage)) qError("Type Your Homepage URL !"); /* Add to bulletin board here. */ /* Output the result - Check Input */ qContentType("text/html"); printf("<HTML>\n\n"); printf("<HEAD><TITLE>CGI Board TEST</TITLE></HEAD>\n\n"); printf("<BODY>\n"); printf("<H2> CGI Board TEST </H2>\n"); printf("<HR WIDTH=600 ALIGN=left>\n<BR>"); if (strcheck(email)) printf("<A HREF=\"mailto:%s\">%s</A>",email,name); else printf("%s", name); if (strcheck(homepage)) printf( "<SMALL>(<A HREF=\"%s\">%s</A>)</SMALL>", homepage, homepage ); printf("wrote<BR><BR>\n"); printf("<TABLE WIDTH=600 BORDER=1>\n"); printf("<TR><TD><B>Title</B> : %s</TD></TR>\n", title); printf("<TR><TD>%s</TD></TR>\n", doc); printf("</TABLE>\n\n<BR>Upload Successful !\n"); printf("</BODY>\n\n</HTML>"); qFree(); }
The following are the steps for configuration and checking the results.
Modify the sample_cgi.m environment file to correspond with the system.
Move HTML documents to the DocRooT directory.
Modify the ACTION="/cgi-bin/board.cgi" path in board.html to the path that points to board.cgi in its environment.
Move board.c, qDecoder.h, and qDecoder.c to the working directory where CGI is executed.
Once board.c is compiled, board.cgi is created. (cc or gcc must be installed)
$ cc –o board.cgi board.c qDecoder.c
or
$ gcc board.c qDecoder.c –o board.cgi
(create board.cgi)
Change permission of board.cgi to 755.
$ chmod 755 board.cgi
Start WebtoB and load the bulletin board in a web browser. (Use the IP address of the machine where WebtoB is currently running.)
http://192.168.63.2:8080/board.html
The following screen is displayed.
Fill out the form and click on [submit]. board.cgi is called and the following screen that shows the input is entered to the board is displayed.
Perl is a scripting language, which is not compiled. Perl allows for fast development because it can be executed and debugged as soon as it is written. This is the main reason why scripting languages are often preferred. Perl is also free for download. Introduction to Perl itself will not be covered here.
This section describes the environment file required to operate Perl in WebtoB. A sample guest book implemented in Perl is also shown.
The following is an example of an environment configuration file.
<sample_perl.m>
#Configuration file ( sample_perl.m ) *DOMAIN webtob1 *NODE webmain WEBTOBDIR = "/usr/local/webtob", SHMKEY = 72000, HTH=1, DOCROOT = "/usr/local/webtob/docs", PORT = "9989" *HTH_THREAD hworker WorkerThreads = 16 *SVRGROUP cgig NODENAME = webmain, SvrType = CGI *SERVER cgi SVGNAME = cgig, MinProc = 1, MaxProc = 5 *URI uri1 Uri = "/cgi-bin/", SvrName = cgi, Svrtype = CGI *ALIAS alias1 URI = "/cgi-bin/", RealPath = "/usr/local/webtob/docs/cgi-bin/"
The following example is a guestbook written in PERL.
#guestbook.cgi #!/usr/local/bin/perl # Base URL (Usually Homepage address) $Base_href = 'http://webmain.tmax.co.kr:9081/'; # Path to the file based on $base_href $Cgi_file = 'cgi-bin/guestbook.cgi'; # lock directory path $Lock_dir = 'lock/guestbook'; # File that contains BBS data $Bbs_file = 'book_data'; # Title to link $Link_name = 'Back to Home'; # URL to linkL ($base_href base) $Link_url = './'; # Title $Bbs_title = 'Guest Book'; # Administrator name $Bbsmaster_name = 'webtob'; # Administrator email address $Bbsmaster_email = 'webotb@tmax.co.kr'; # Maximum size of a message (byte) (Prevents SPAM) $Max_msg_size = 8000; $Titlecolor = '#800080'; $Bgcolor = '#fafff8'; $Textcolor = '#000000'; $Linkcolor = '#401080'; $Bbs_write_title = 'WRITE'; $Bbs_read_title = 'READ'; $Rem_name = ''; $Rem_email = ''; $Rem_msg = '' . '' . ''; $Btn_write = 'Send'; $Btn_read_old = 'Old Message'; $Btn_read_new = 'New Message'; $Rem_bbs_end = 'End of Messages'; $Rem_bbs_back = 'Go Top'; $Bbs_logo = ''; $Bbs_logo_addr = 'http://'; $Delimiter = chr(30); $Prev_num_skip = 0; ##### main routine ##### &read_file; &read_form; if ($ENV{'REQUEST_METHOD'} eq "POST" && $Name ne "" && $Msg ne "" ) { &remove_html_tags; # remove HTML tags &new_msg; # make new BBS msg, use &clock &save_msg; # save BBS msg } &show_header; # show header &show_entry_form; # show BBS entry form &show_bbs; # show BBS msgs &show_read_form; # show BBS read form &show_footer; # show footer exit; ##### sub routines ##### sub read_file { open (IN, "$Bbs_file") || &return_error (500, "File open error", "Cannot access BBS file"); @Bbs = <IN>; close (IN); } sub read_form { %Form = &read_input; $Name = $Form{'name'}; $Email = $Form{'email'}; $Msg = $Form{'msg'}; $Num_each = $Form{'num_each'}; $Num_skip = $Form{'num_skip'}; } sub remove_html_tags { $Name =~ s/\&/\&/g; $Name =~ s/\</\</g; $Name =~ s/\>/\>/g; $Email =~ s/\&/\&/g; $Email =~ s/\</\</g; $Email =~ s/\>/\>/g; $Msg =~ s/\&/\&/g; $Msg =~ s/\</\</g; $Msg =~ s/\>/\>/g; $Msg =~ s/\r\n/\n/g; # Windows(CR,LF) -> LF $Msg =~ s/\r/\n/g; # Mac (CR) -> LF $Msg =~ s/\n/<BR>/g; # LF -> <BR> } sub new_msg { my ($access_time) = &clock; # get date and time my ($new_msg); my ($site) = ($ENV{'REMOTE_HOST'} || $ENV{'REMOTE_ADDR'}); # delete msg larger than 4000 byte if (length($Msg) >= 4000) { $Msg = substr($Msg, 0, 4000); if (($Msg =~ tr/[\xA1-\xFE]//)%2 != 0) { chop $Msg; } } if ($Email !~ /([\w\-]+\@[\w\-+\.]+[\w\-]+)/) { $Email = ""; # check valid email address format } $new_msg = "$Name" . $Delimiter . # name "$Email" . $Delimiter . # email address "$site" . $Delimiter . # ip_address "$access_time" . $Delimiter . # access time "$Msg" . "\n"; # message @Bbs = ($new_msg, @Bbs); } sub save_msg { (&filelock ($Lock_dir) eq 'OK') || &return_error ("500", "Lock error", "Too many access or cannot create lock file"); open (OUT, ">$Bbs_file") || &return_error (500, "File write error", "Cannot access BBS file"); print OUT @Bbs; close (OUT); (&fileunlock ($Lock_dir) eq 'OK') || &return_error ("500", "Unlock error", "No lock file exist"); } sub show_header { print "Content-type: text/html\n\n"; print <<END_OF_HTML; <HTML> <HEAD> <BASE HREF="$Base_href"> <TITLE>$bbs_title</TITLE> </HEAD> <BODY BGCOLOR="$Bgcolor" TEXT="$Textcolor" LINK="$Linkcolor"> <P ALIGN="CENTER"><FONT COLOR="$Titlecolor" SIZE="+2"> <B>$Bbs_title</B></FONT> <br> <div align="left"><A HREF="$Link_url">$Link_name</A></div> <P ALIGN="RIGHT"> <HR> END_OF_HTML ; } sub show_entry_form { print <<END_OF_HTML; <a name="write"> </a> <FONT COLOR="#800080" SIZE="+1"> <B>$Bbs_write_title</B></FONT> <P> <FORM ACTION=\"$Cgi_file\" METHOD=\"POST\"> <B>Name</B> $Rem_name<BR> <INPUT NAME="name" TYPE="text" SIZE = "40" MAXLENGTH="64"> <P> <B>Email address</B> (optional) $Rem_email<BR> <INPUT NAME="email" TYPE="text" SIZE = "40" MAXLENGTH="72"> <P> <B>Message</B> $Rem_msg<BR> <TEXTAREA COLS="64" ROWS="10" WRAP="VIRTUAL" NAME="msg"> </TEXTAREA><BR> <INPUT TYPE="submit" VALUE="$Btn_write "> </FORM> <HR> END_OF_HTML ; } sub show_read_form { print <<END_OF_HTML; <font color="#800080" size="+1"><b>$Bbs_read_title</b></font> <br><br> <table border="0" cellspacing="10"> <tr valign="top"> <td> <FORM ACTION=\"$Cgi_file\" METHOD=\"POST\"> <INPUT TYPE="submit" VALUE="$Btn_read_old"><br> <INPUT TYPE="radio" NAME="num_each" VALUE="5" CHECKED>5 more <INPUT TYPE="radio" NAME="num_each" VALUE="20">20 more <INPUT TYPE="hidden" NAME="num_skip" VALUE="$Prev_num_skip"> </FORM> </td> <td> <FORM ACTION=\"$Cgi_file\" METHOD=\"POST\"> <INPUT TYPE="submit" VALUE="$Btn_read_new"><br> <INPUT TYPE="radio" NAME="num_each" VALUE="-5" CHECKED>5 more <INPUT TYPE="radio" NAME="num_each" VALUE="-20">20 more <INPUT TYPE="hidden" NAME="num_skip" VALUE="$Prev_num_skip"> </FORM> </td> <td></td> <td></td> <td> <FORM ACTION=\"$Cgi_file\" METHOD=\"POST\"> <INPUT TYPE="hidden" NAME="num_each" VALUE="top"> <INPUT TYPE="submit" VALUE="Go Top"> </FORM> </td> <td></td> <td> <FORM ACTION=\"$Cgi_file\" METHOD=\"POST\"> <INPUT TYPE="hidden" NAME="num_each" VALUE="all"> <INPUT TYPE="submit" VALUE="Show All"><br> (Warning: very long!) </FORM> </td> </tr> </table> <hr> END_OF_HTML ; } sub show_bbs { local( $bbs_no, $serial_no, @show_bbs, @data, $temp_email, $temp_msg ); $bbs_no = $#Bbs + 1; unless (defined ($Num_each)) {$Num_each = 5;} unless (defined ($Num_skip)) {$Num_skip = 0;} if ($Num_each eq "all") { $Num_each = $bbs_no - $Num_skip; } if ($Num_each eq "top") { $Num_each = 5; $Num_skip = 0; } if ($Num_skip >= $bbs_no) { &show_end_of_bbs; } if ($Num_skip + $Num_each > $bbs_no) { $Num_each = $bbs_no - $Num_skip; } if ($Num_each < 0 ) { $Num_skip = $Num_skip + $Num_each + $Num_each; $Num_each = 0 - $Num_each; if ($Num_skip < $Num_each) { $Num_skip = 0; } } else { $Prev_num_skip = $Num_skip + $Num_each; # skip data at next loading } $serial_no = $bbs_no - $Num_skip; # first serial No. @show_bbs = splice(@Bbs, $Num_skip, $Num_each); # displayed msgs while (@show_bbs) { @data = split (/$Delimiter/, shift(@show_bbs)); # use slice of message file print "[ $serial_no ] <B>"; print shift(@data); # $Name print '</B>'; if ($temp_email = shift(@data)) { # $Email print "<<a href=\"mailto:$temp_email\">$temp_email<\/a>>"; } print ' from '; print shift(@data); # $ip_address print ' at '; print shift(@data); # $access_time print ' <P>'; $temp_msg = shift(@data); $temp_msg =~ s/(http:\/\/)([\w\+\-\/\=\?\.\~\:\&\;\#]+)/ <a href=\"$1$2\" target=\"_new">$1$2<\/a>/g; $temp_msg =~ s/([\w\-]+\@[\w\-+\.]+[\w\-]+)/ <a href=\"mailto:$1">$1<\/a>/g; print $temp_msg; # $msg print "\n<HR>\n"; $serial_no --; # prepare next serial No. } } sub show_end_of_bbs { print "$Rem_bbs_end"; print '<P>'; print "<A HREF=\"$Cgi_file\">$Rem_bbs_back</A>"; print "<HR>\n"; } sub show_footer { print <<END_OF_HTML; <A HREF="$Link_url">$Link_name</A> <HR> <I>BBS Master : $Bbsmaster_name / <A HREF="mailto:$Bbsmaster_email">$Bbsmaster_email</A></I> <div align="right"> <B><I><A HREF="$Bbs_logo_addr">$Bbs_logo</A></I></B> </div> </body> </html> END_OF_HTML ; } #### other subroutines (shared with other perl cgi) ######## sub read_input { local ($buffer, @pairs, $name, $value, %FORM); if ($ENV{'REQUEST_METHOD'} =~ /^post$/i) { read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); } else { $buffer = $ENV{'QUERY_STRING'}; } @pairs = split(/&/, $buffer); foreach (@pairs) { ($name, $value) = split(/=/, $_); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg; $FORM{$name} = $value; } return %FORM; } sub clock { my ($date); @days = ('Sun','Mon','Tue','Wed','Thr','Fri','Sat'); ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); $mon ++; if ($hour < 10) { $hour = "0".$hour; } if ($min < 10) { $min = "0".$min; } if ($sec < 10) { $sec = "0".$sec; } $date = "19$year\.$mon\.$mday\.($days[$wday]) $hour\:$min\:$sec"; } sub return_error { my ($status, $keyword, $msg) = @_; print "Content-type: text/html\n"; print "Status: ", $status, " ", $keyword, "\n\n"; print "<TITLE>CGI program Error</TITLE>\n"; print "<H1>", $keyword, "</H1>\n"; print $msg, "\n"; print "<P>Please contact the webmaster for more information.\n"; print "<HR>"; print "<A HREF=\"http://heartkorea.com/\"><I>Guest Book</I></A>"; exit(1); } sub filelock { # parameter is filename for lock my ($lockdir) = @_; mkdir ($lockdir, 0777) && return 'OK'; -M $lockdir > 3/(24*60) && do { rmdir ($lockdir) || return 'FAIL'; }; for (1 .. 10) { mkdir ($lockdir, 0777) && return 'OK'; sleep (1); } return 'BUSY'; } sub fileunlock { my ($lockdir) = @_; -d $lockdir || return 'FAIL'; rmdir ($lockdir) && return 'OK'; return 'FAIL'; } #### end of program ########################################
The following are the steps for configuration and checking the results.
Modify the sample_perl.m environment file to correspond with the system
Get the Perl path by using the $which perl command.
Modify the guestbook.cgi file to correspond with the system.
Move guestbook.cgi to the directory where CGI is executed and change the directory permission to 755.
$chmod 755 guestbook.cgi
Create book_data to store guestbook.cgi. Change the file permission to 666.
$touch book_data $chmod 666 book_data
Create a lock directory and change the permission of the directory to 777.
$mkdir lock $chmod 777 lock
Start WebtoB and request the CGI file in the browser. (Use the IP address of the machine where WebtoB is currently running.)
http://IP address:port/cgi-bin/guestbook.cgi
After execution, the following screen is displayed.
Fill out the form and click [Send] to write to the guest book. In the example, the message from 'webtob' will be posted above the message from 'guest 2'. Messages from visitors are left in the book_data file. The following is the result screen.