CGI之C语言篇

为何要进行CGI编程? 

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


   有的人认为能够用JavaScript来代替CGI程序,这实际上是一个概念上的错误。JavaScript只可以在客户浏览器中运行,而CGI倒是工做在服务器上的。他们所作的工做有一些交集,好比表单数据验证一类的,可是JavaScript是绝对没法取代CGI的。但能够这样说,若是一项工做即可以用JavaScript来作,又能够用CGI来作,那么绝对要使用JavaScript,在执行的速度上,JavaScript比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" VALUE="肯定" > 

< /FORM > 


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


   下面就是处理这个表单的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",10,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",10,10),这个输出是做为HTML的文件头,10转换成字符%c输出的则是换行,不懂的能够去查ASCII表。由于CGI不只能够像浏览器输出HTML文本,并且能够输出图像,声音之类的东西。这一行告诉浏览器如何处理接受到的内容。在Content-Type的定义后面跟有个空行,这也是不可缺乏的。由于全部CGI程序的头部输出都是相近的,于是能够为其定义一个函数,来节省编程的时间。这是CGI编程经常使用的一个技巧。 


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


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


   POST表单处理 


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

< /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" VALUE="察看" > 

   < /FORM > 


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


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

出处:c++