C语言写CGI程序

1、CGI概述 
CGI(公用网关接口)规定了Web服务器调用其余可执行程序(CGI程序)的接口协议标准。Web服务器经过调用CGI程序实现和Web浏览器的交互, 也就是CGI程序接受Web浏览器发送给Web服务器的信息,进行处理, 将响应结果再回送给Web服务器及Web浏览器。CGI程序通常完成Web网页中表单(Form)数据的处理、数据库查询和实现与传统应用系统的集成等工 做。CGI程序能够用任何程序设计语言编写,如Shell脚本语言、Perl、Fortran、Pascal、C语言等。可是用C语言编写的CGI程序具 有执行速度快、安全性高(由于C语言程序是编译执行且不可被修改)等特色。 

CGI接口标准包括标准输入、环境变量、标准输出三部分。 

1.标准输入 

CGI程序像其余可执行程序同样,可经过标准输入(stdin)从Web服务器获得输入信息,如Form中的数据,这就是所谓的向CGI程序传递数据的 POST方法。这意味着在操做系统命令行状态可执行CGI程序,对CGI程序进行调试。POST方法是经常使用的方法,本文将以此方法为例,分析CGI程序设 计的方法、过程和技巧。 

2.环境变量 

操做系统提供了许多环境变量,它们定义了程序的执行环境,应用程序能够存取它们。Web服务器和CGI接口又另外设置了本身的一些环境变量,用来向CGI 程序传递一些重要的参数。CGI的GET方法还经过 环境变量QUERY-STRING向CGI程序传递Form中的数据。 

3.标准输出 

CGI程序经过标准输出(stdout)将输出信息传送给Web服务器。传送给Web服务器的信息能够用各类格式,一般是以纯文本或者HTML文本的形式,这样咱们就能够在命令行状态调试CGI程序,而且获得它们的输出。 

下面是一个简单的CGI程序,它将HTML中Form的信息直接输出到We b浏览器。 

引用 

#include < stdio.h > 
#include < stdib.h > 
main() 

int,i,n; 
printf (〃Contenttype:text/plainnn〃); 
n=0; 
if(getenv(〃CONTENT-LENGTH〃)) 
n=atoi(getenv(CONTENT-LENGTH〃)); 
for (i=0;i putchar(getchar()); 
putchar (′n′); 
fflush(stdout); 




下面对此程序做一下简要的分析。 
prinft (〃Contenttype:text/plainnn〃); 
此行经过标准输出将字符串〃Contenttype:text/plainnn〃传送给Web服务器。它是一个MIME头信息,它告诉Web服务器随后的 输出是以纯ASCII文本的形式。请注意在这个头信息中有两个新行符,这是由于Web服务器须要在实际的文本信息开始以前先看见一个空行。 
if (getenv(〃CONTENT-LENGTH〃)) 
n=atoi (getenv(〃CONTENT-LENGTH〃)); 
此行首先检查环境变量CONTENT-LENGTH是否存在。Web服务器在调用使用POST方法的CGI程序时设置此环境变量,它的文本值表示Web服 务器传送给CGI程序的输入中的字符数目,所以咱们使用函数atoi() 将此环境变量的值转换成整数,并赋给变量n。请注意Web服务器并不以文件结束符来终止它的输出,因此若是不检查环境变量CONTENT-LENGTH, CGI程序就没法知道何时输入结束了。 
for (i=0;i putchar(getchar()); 
此行从0循环到(CONTENT-LENGTH-1)次将标准输入中读到的每个字符直接拷贝到标准输出,也就是将全部的输入以ASCII的形式回送给Web服务器。 
经过此例,咱们可将CGI程序的通常工做过程总结为以下几点。 
1.经过检查环境变量CONTENT-LENGTH,肯定有多少输入; 
2.循环使用getchar()或者其余文件读函数获得全部的输入; 
3.以相应的方法处理输入; 
4.经过〃Contenttype:〃头信息,将输出信息的格式告诉Web服务器; 
5.经过使用printf()或者putchar()或者其余的文件写函数,将输出传送给Web服务器。 
总之,CGI程序的主要任务就是从Web服务器获得输入信息,进行处理,而后将输出结果再送回给Web服务器。 

2、环境变量 

环境变量是文本串(名字/值对),能够被OS Shell或其余程序设置 ,也能够被其余程序访问。它们是Web服务器传递数据给CGI程序的简单手段,之因此称为环境变量是由于它们是全局变量,任何程序均可以存取它们。 

下面是CGI程序设计中经常要用到的一些环境变量。 
HTTP-REFERER:调用该CGI程序的网页的URL。 
REMOTE-HOST:调用该CGI程序的Web浏览器的机器名和域名。 
REQUEST-METHOD:指的是当Web服务器传递数据给CGI程序时所采用的方法,分为GET和POST两种方法。GET方法仅经过环境变量(如 QUERY-STRING)传递数据给CGI程序,而POST方法经过环境变量和标准输入传递数据给CGI程序,所以POST方法可较方便地传递较多的数 据给CGI程序。 

SCRIPT-NAME:该CGI程序的名称。 
QUERY-STRING:当使用POST方法时,Form中的数据最后放在QUERY-STRING中,传递给CGI程序。 
CONTENT-TYPE:传递给CGI程序数据的MIME类型,一般为〃applica tion/x-www-form-url encodede〃,它是从HTML Form中以POST方法传递数据给CGI程序的数据编码类型,称为URL编码类型。 
CONTENT-LENGTH:传递给CGI程序的数据字符数(字节数)。 
在C语言程序中,要访向环境变量,可以使用getenv()库函数。例如: 
if (getenv (〃CONTENT-LENGTH〃)) 
n=atoi(getenv (〃CONTENT-LENGTH〃)); 
请注意程序中最好调用两次getenv():第一次检查是否存在该环境变量,第二次再使用该环境变量。这是由于函数getenv()在给定的环境变量名不存在时,返回一个NULL(空)指针,若是你不首先检查而直接引用它,当该环境变量不存在时会引发CGI程序崩溃。 

3、From输入的分析和解码 

1.分析名字/值对 

当用户提交一个HTML Form时,Web浏览器首先对Form中的数据以名字/值对的形式进行编码,并发送给Web服务器,而后由Web服务器传递给CGI程序。其格式以下: 
name1=value1&name2=value2&name3=value3&name4=value4&... 
其中名字是Form中定义的INPUT、SELECT或TEXTAREA等标置(Tag)名字,值是用户输入或选择的标置值。这种格式即为URL编码,程 序中须要对其进行分析和解码。要分析这种数据流,CGI程序必须首先将数据流分解成一组组的名字/值对。这能够经过在输入流中查找下面的两个字符来完成。 
每当找到字符=,标志着一个Form变量名字的结束;每当找到字符& ,标志着一个Form变量值的结束。请注意输入数据的最后一个变量的值不以&结束。 
一旦名字/值对分解后,还必须将输入中的一些特殊字符转换成相应的ASCII字符。这些特殊字符是: 
+:将+转换成空格符; 
%xx:用其十六进制ASCII码值表示的特殊字符。根据值xx将其转换成相应的ASCII字符。 
对Form变量名和变量值都要进行这种转换。下面是一个对Form数据进行分析并将结果回送给Web服务器的CGI程序。 

引用 
#include < stdio.h > 
#include < stdlib.h > 
#include < strings.h > 
int htoi(char *); 
main() 

int i,n; 
char c; 
printf (〃Contenttype: text/plainnn〃); 
n=0; 
if (getenv(〃CONTENT-LENGTH〃)) 
n=atoi(getenv(〃CONTENT-LENGTH〃)); 
for (i=0; i < n;i++){ 
int is-eq=0; 
c=getchar(); 
switch (c){ 
case ′&′: 
c=′n′; 
break; 
case ′+′: 
c=′ ′; 
break; 
case ′%′:{ 
char s[3]; 
s[0]=getchar(); 
s[1]=getchar(); 
s[2]=0; 
c=htoi(s); 
i+=2; 

break; 
case ′=′: 
c=′:′; 
is-eq=1; 
break; 
}; 
putchar(c); 
if (is-eq) putchar(′ ′); 

putchar (′n′); 
fflush(stdout); 

/* convert hex string to int */ 
int htoi(char *s) 

char *digits=〃0123456789ABCDEF〃; 
if (islower (s[0])) s[0]=toupper(s[0]); 
if (islower (s[1])) s[1]=toupper(s[1]); 
return 16 * (strchr(digits, s[0]) -strchr (digits,′0′) 

+(strchr(digits,s[1])-strchr(digits,′0′)); 



上面的程序首先输出一个MIME头信息给Web服务器,检查输入中的字符数,并循环检查每个字符。当发现字符为&时,意味着一个名字/值对的结 束,程序输出一个空行;当发现字符为+时,将它转换成空格; 当发现字符为%时,意味着一个两字符的十六进制值的开始,调用htoi()函数将随后的两个字符转换为相应的ASCII字符;当发现字符为=时,意味着一 个名字/值对的名字部分的结束,并将它转换成字符:。最后将转换后的字符输出给Web服务器。 
4、产生HTML输出 

CGI程序产生的输出由两部分组成:MIME头信息和实际的信息。两部分之间以一个空行分开。咱们已经看到怎样使用MIME头信息〃Cont enttype:text/plainnn〃和printf()、put char()等函数调用来输出纯ASCII文本给Web服务器。实际上,咱们也可使用MIME头信息〃C ontenttype:text/htmlnn〃来输出HTML源代码给Web服务器。请注意任何MIME头信息后必须有一个空行。一旦发送这个MIME 头信息给We b服务器后,Web浏览器将认为随后的文本输出为HTML源代码,在HTML源代码中可使用任何HTML结构,如超链、图像、Form,及对其余CGI 程 序的调用。也就是说,咱们能够在CGI程序中动态产生HTML源代码输出 ,下面是一个简单的例子。 


引用 
#include < stdio.h > 
#include < string.h > 
main() 

printf(〃Contenttype:text/html/n/n〃); 
printf(〃< html >/n〃); 
printf(〃< head >< title >An HTML Page From a CGI< /title >< /head >/n〃); 
printf(〃< body > 
n〃); 
printf(〃< h2 > This is an HTML page generated from with i n a CGI program.. .< /h2 >/n〃); 
printf(〃< hr >< p >/n〃); 
printf(〃< a href="../output.html#two" >< b > Go back to out put.html page < /b >< /a >/n〃); 
printf(〃< /body >/n〃); 
printf(〃< /html >/n〃); 
fflush(stdout); 




上面的CGI程序简单地用printf()函数来产生HTML源代码。请注意在输出的字符串中若是有双引号,在其前面必须有一个后斜字符, 这是由于整个HTML代码串已经在双引号内,因此HTML代码串中的双引号符必须用一个后斜字符来转义。 

在HTML中,当客户填写了表单,并按下了发送(submit)按钮后,表单的内容被发送到了服务器端,通常的,这时就须要有一个服务器端脚原本 对表单的内容进行一些处理,或者是把它们保存起来,或者是按内容进行一些查询,或者是一些别的什么。没有了CGI,WEB的世界就彻底失去了它的交互性, 全部的信息都变成单向的了,而不可以有任何的反馈。 


有的人认为能够用java script来代替CGI程序,这实际上是一个概念上的错误。java script只可以在客户浏览器中运行,而CGI倒是工做在服务器上的。他们所作的工做有一些交集,好比表单数据验证一类的,可是java script是绝对没法取代CGI的。但能够这样说,若是一项工做即可以用java script来作,又能够用CGI来作,那么绝对要使用java script,在执行的速度上,java script比CGI有着先天的优点。只有那些在客户端解决不了的问题,好比和某个远程数据库交互,这时就应该使用CGI了。 


简单的说来,CGI是用来沟通HTML表单和服务器端程序的接口(interface)。说它是接口,也就是说CGI并非一种语言,而是能够被其余语言 所应用的一个规范集。理论上讲,你能够用任何的程序语言来编写CGI程序,只要在编程的时候符合CGI规范所定义的一些东西就能够了。因为C语言在平台无 关性上表现不错(几乎在任何的系统平台下都有其相应编译器),并且对大多数程序员而言都算得上很熟悉(不像Perl),所以,C是CGI编程的首选语言之 一。这儿咱们介绍的,就是如何使用C来编写CGI程序。 


做为CGI编程的最为简单的例子,就是进行表单的处理。于是在这篇文章中,咱们主要介绍的就是如何用C来编写CGI程序来进行表但处理。 

GET表单的处理 

对于那些使用了属性“METHOD=GET”的表单(或者没有METHOD属性,这时候GET是其缺省值),CGI定义为:当表单被发送到服务器断后,表 单中的数据被保存在服务器上一个叫作QUERY_STRING的环境变量中。这种表单的处理相对简单,只要读取环境变量就能够了。这一点对不一样的语言有不 同的作法。在C语言中,你能够用库函数getenv(定义在标准库函数stdlib中)来把环境变量的值做为一个字符串来存取。你能够在取得了字符串中的 数据后,运用一些小技巧进行类型的转换,这都是比较简单的了。在CGI程序中的标准输出(output)(好比在C中的stdout文件流)也是通过重定 义了的。它并无在服务器上产生任何的输出内容,而是被重定向到客户浏览器。这样,若是编写一个C的CGI程序的时候,把一个HTML文档输出到它的 stdout上,这个HTML文档会被在客户端的浏览器中显示出来。这也是CGI程序的一个基本原理。 

咱们来看看具体的程序实现,下面是一段HTML表单: 

< form ACTION="/cgi-bin/mult.cgi" > 
< P >请在下面填入乘数和被乘数,按下肯定后能够看到结果。 
< INPUT NAME="m" SIZE="5" > 
< INPUT NAME="n" SIZE="5" >< BR > 
< INPUT TYPE="SUBMIT" values="肯定" > 
< /form > 


咱们要实现的功能很简单,就是把表单中输入的数值乘起来,而后输出结果。其实这个功能彻底能够用java script来实现,但为了让程序尽可能的简单易懂,我仍是选择了这个小小的乘法来做为示例。 


下面就是处理这个表单的CGI程序,对应于form标签中的ACTION属性值。 


引用 

#include < stdio.h > 
#include < stdlib.h > 
int main(void) 

char *data; 
long m,n; 
printf("%s%c%c ","Content-Type:text/html;charset=gb2312",13,10); 
printf("< TITLE >乘法结果< /TITLE > "); 
printf("< H3 >乘法结果< /H3 > "); 
data = getenv("QUERY_STRING"); 
if(data == NULL) 
printf("< P >错误!数据没有被输入或者数据传输有问题"); 
else if(sscanf(data,"m=%ld&n=%ld",&m,&n)!=2) 
printf("< P >错误!输入数据非法。表单中输入的必须是数字。"); 
else 
printf("< P >%ld和%ld的成绩是:%ld。",m,n,m*n); 
return 0; 




具体的C语法就很少讲了,咱们来看看它做为CGI程序所特殊的地方。 


前面已经提到标准输出的内容就是要被显示在浏览器中的内容。第一行的输出内容是必须的,也是一个CGI程序所特有的:printf("%s%c%c ","Content-Type:text/html",13,10),这个输出是做为HTML的文件头。由于CGI不只能够像浏览器输出HTML文本, 并且能够输出图像,声音之类的东西。这一行告诉浏览器如何处理接受到的内容。在Content-Type的定义后面跟有两行的空行,这也是不可缺乏的。因 为全部CGI程序的头部输出都是相近的,于是能够为其定义一个函数,来节省编程的时间。这是CGI编程经常使用的一个技巧。 


程序在后面调用了用了库函数getevn来获得QUERY_STRING的内容,而后使用sscanf函数把每一个参数值取出来,要注意的是sscanf函数的用法。其余的就没有什么了,和通常的C程序没有区别。 


把程序编译后,更名为mult.cgi放在/cgi-bin/目录下面,就能够被表单调用了。这样,一个处理GET方式表单的CGI程序就大功告成了。 


POST表单处理 


下面咱们来考虑另一种表单传送方法:POST。假设咱们要实现的任务是这样的:把表单中客户输入的一段文本内容添加到服务器上的一个文本文件的后面。这 能够看做是一个留言版程序的雏形。显然,这个工做是没法用java script这种客户端脚原本实现,也算得上真正意义上的CGI程序了。 


看起来这个问题和上面讲的内容很相近,仅仅是用不一样的表单和不一样的脚本(程序)而已。但实际上,这中间是有一些区别的。在上面的例子中,GET的处理方法 能够看做是“纯查询(pure query)”类型的,也就是说,它与状态无关。一样的数据能够被提交任意的次数,而不会引发任何的问题(除了服务器的一些小小的开销)。可是如今的任务 就不一样了,至少它要改变一个文件的内容。于是,能够说它是与状态有关的。这也算是POST和GET的区别之一。并且,GET对于表单的长度是有限制的,而 POST则否则,这也是在这个任务中选用POST方法的主要缘由。但相对的,对GET的处理速度就要比POST快一些。 


在CGI的定义中,对于POST类型的表单,其内容被送到CGI程序的标准输入(在C语言中是stdin),而被传送的长度被放在环境变量 CONTENT_LENGTH中。于是咱们要作的就是,在标准输入中读入CONTENT_LENGTH长度的字符串。从标准输出读入数据听起来彷佛要比从 环境变量中读数据来的要容易一些,其实则否则,有一些细节地方要注意,这在下面的程序中能够看到。特别要注意的一点就是:CGI程序和通常的程序有所不 同,通常的程序在读完了一个文件流的内容以后,会获得一个EOF的标志。但在CGI程序的表单处理过程当中,EOF是永远不会出现的,因此千万不要读多于 CONTENT_LENGTH长度的字符,否这会有什么后果,谁也不知道(CGI规范中没有定义,通常根据服务器不一样而有不一样得处理方法)。 


咱们来看看到底如何从POST表单收集数据到CGI程序,下面给出了一个比较简单的C源代码: 


引用 
#include < stdio.h > 
#include < stdlib.h > 
#define MAXLEN 80 
#define EXTRA 5 
/* 4个字节留给字段的名字"data", 1个字节留给"=" */ 
#define MAXINPUT MAXLEN+EXTRA+2 
/* 1个字节留给换行符,还有一个留给后面的NULL */ 
#define DATAFILE "../data/data.txt" 
/* 要被添加数据的文件 */ 
void unencode(char *src, char *last, char *dest) 

for(; src != last; src++, dest++) 
if(*src == "+") 
*dest = " "; 
else if(*src == "%") { 
int code; 
if(sscanf(src+1, "%2x", &code) != 1) code = "?"; 
*dest = code; 
src +=2; } 
else 
*dest = *src; 
*dest = " "; 
*++dest = ""; 

int main(void) 

char *lenstr; 
char input[MAXINPUT], data[MAXINPUT]; 
long len; 
printf("%s%c%c ","Content-Type:text/html;charset=gb2312",13,10); 
printf("< TITLE >Response< /TITLE > "); 
lenstr = getenv("CONTENT_LENGTH"); 
if(lenstr == NULL || sscanf(lenstr,"%ld",&len)!=1 || len > MAXLEN) 
printf("< P >表单提交错误"); 
else { 
FILE *f; 
fgets(input, len+1, stdin); 
unencode(input+EXTRA, input+len, data); 
f = fopen(DATAFILE, "a"); 
if(f == NULL) 
printf("< P >对不起,意外错误,不可以保存你的数据 "); 
else 
fputs(data, f); 
fclose(f); 
printf("< P >很是感谢,您的数据已经被保存< BR >%s",data); 

return 0; 



从本质上来看,程序先从CONTENT_LENGTH环境变量中获得数据的字长,而后读取相应长度的字符串。由于数据内容在传输的过程当中是通过了编码的,因此必须进行相应的解码。编码的规则很简单,主要的有这几条: 

1. 表单中每一个每一个字段用字段名后跟等号,再接上上这个字段的值来表示,每一个字段之间的内容用&连结; 
2. 全部的空格符号用加号代替,因此在编码码段中出现空格是非法的; 
3. 特殊的字符好比标点符号,和一些有特定意义的字符如“+”,用百分号后跟其对应的ACSII码值来表示。 

例如:若是用户输入的是: 
Hello there! 
那么数据传送到服务器的时候通过编码,就变成了data=Hello+there%21 上面的unencode()函数就是用来把编码后的数据进行解码的。在解码完成后,数据被添加到data.txt文件的尾部,并在浏览其中回显出来。 
把文件编译完成后,把它更名为collect.cgi后放在CGI目录中就能够被表单调用了。下面给出了其相应的表单: 

< form ACTION="/cgi-bin/collect.cgi" METHOD="POST" > 
< P >请输入您的留言(最多80个字符):< BR >< INPUT NAME="data" SIZE="60" MAXLENGTH="80" >< BR > 
< INPUT TYPE="SUBMIT" values="肯定" > 
< /form > 

事实上,这个程序只能做为例子,是不可以正式的使用的。它漏掉了很关键的一个问题:当有多个用户同时像文件写入数据是,确定会有错误发生。而对于一个这样 的程序而言,文件被同时写入的概率是很大的。所以,在比较正式的留言版程序中,都须要作一些更多的考虑,好比加入一个信号量,或者是借助于一个钥匙文件 等。由于那只是编程的技巧问题,在这儿就很少说了。 


最后,咱们来写一个浏览data.txt文件的的CGI程序,这只须要把内容输出到stdout就能够了: 

引用 
#include < stdio.h > 
#include < stdlib.h > 
#define DATAFILE "../data/data.txt" 
int main(void) 

FILE *f = fopen(DATAFILE,"r"); 
int ch; 
if(f == NULL) { 
printf("%s%c%c ", "Content-Type:text/html;charset=gb2312",13,10); 
printf("< TITLE >错误 < /TITLE > "); 
printf("< P >< EM >意外错误,没法打开文件< /EM >"); } 
else { 
printf("%s%c%c ", 
"Content-Type:text/plain",13,10); 
while((ch=getc(f)) != EOF) 
putchar(ch); 
fclose(f); } 
return 0; 


这个程序惟一要注意的是:它并无把data.txt 包装成HTML格式后再输出,而是直接做为简单文本(plain text)输出,这只要在输出的头部用text/plain类型代替text/html就能够了,浏览器会根据Content-Type的类型自动的选择 相应的处理方法。 


要触发这个程序也很简单,由于没有数据要输入,因此只需一个按钮就能够搞定了: 

< form ACTION="/cgi-bin/viewdata.cgi" > 
< P >< INPUT TYPE="SUBMIT" values="察看" > 
< /form > 

到这儿,一些基本的用C编写CGI程序的原理就将完了。固然,就凭讲的这些内容,还很难编写出一个好的CGI程序,这须要进一步的学习CGI的规范定义,以及一些其余的CGI编程特有的技巧。 

这篇文章的目的,也就是要你了解一下CGI编程的概念。事实上,如今的一些主流的服务器端脚本编程语言如ASP,PHP,JSP等,都基本上具有了CGI 编程的大部分的功能,但他们在使用上的,确实是比不管用什么语言进行CGI编程都要容易的多。因此在进行服务器端编程的时候,通常都会首先考虑使用这些脚 本编程语言。只有当他们也解决不了,好比要进行一些更为底层的编程的时候,才会用到CGI。html

出处:http://blog.csdn.net/clearver/article/details/5209332java