Linux CGI编程基础【整理】

 
 
 
 Linux CGI编程基础
 
1.为何使用CGI?
 
    如前面所见,任何的HTML均是静态网页,它没法实现一些复杂的功能,而CGI能够为咱们实现。如:a.列出服务器上某个目录中的文件,对目录中的文件进行操做;b.经过CGI实现串口通信;c.实现数据库接口;d.实现从摄像头读取一张图片显示在网页上… 等等
 
2. CGI是什么?
 
    CGI全称是 Common Gate Intergace ,在物理上,CGI是一段程序,它运行在Server上,提供同客户端 Html页面的接口。
 
3. CGI编程语言
 
    你能够用任何一种你熟悉的高级语言, C,C++,C shell,Perl和VB均可以。
 
4. CGI的安全性
 
    实际上CGI是比较安全的,至少比 那些没有数字签名的ActiveX控件要安全的多。除非你有意在程序里加入了破坏Server的命令, 不然通常不会有什么严重的后果。
简单的说来,CGI是用来沟通HTML表单和服务器端程序的接口(interface)。说它是接口,也就是说CGI并非一种语言,而是能够被其余语言所应用的一个规范集。理论上讲,你能够用任何的程序语言来编写CGI程序,只要在编程的时候符合CGI规范所定义的一些东西就能够了。因为C语言在平台无关性上表现不错(几乎在任何的系统平台下都有其相应编译器),并且对大多数程序员而言都算得上很熟悉(不像Perl),所以,C是CGI编程的首选语言之一。这儿咱们介绍的,就是如何使用C来编写CGI程序。
做为CGI编程的最为简单的例子,就是进行表单的处理。于是在这篇文章中,咱们主要介绍的就是如何用C来编写CGI程序来进行表但处理。
 
5.传送方法:
 
    所谓方法是指调用CGI程序的途径。事实上,要执行程序时,你用一种方法向服务器提出请求,此请求定义了程序如何接受数据。 下面介绍经常使用的两种方法:GET和POST 1.GET 当使用这种方法时,CGI程序从环境变量QUERY_STRING获取数据。
QUERY_STRING 被称为环境变量,就是这种环境变量把客户端的数据传给服务器。为了解释和执行 程序,CGI必需要分析(处理)此字符串。
    POST 使用POST方法时,WEB服务器经过stdin(标准输入),向CGI程序传送数据。服务器 在数据的最后没有使用EOF字符标记,所以程序为了正确的读取stdin,必须使用CONTENT_LENGTH 。当你发送的数据将改变
Web服务器端的数据或者你想给CGI程序传送的数据超过了1024 字节,这是url的极限长度,你应该使用POST方法。 实现方法:
    GET实现方法
    <form name=“guyi‘s form” action=“http://www.yourname.com/cgi/your.cgi” method=GET>
 
    POST实现方法:
    <form method=post>
 
6. 表单编码方式:
 
    form的enctype属性为编码方式,经常使用有两种:application/x-www-form-urlencoded和multipart/form-data,默认为application/x-www-form-urlencoded。
当action为get时候,浏览器用x-www-form-urlencoded的编码方式把form数据转换成一个字串(name1=value1&name2=value2...),而后把这个字串append到url后面,用?分割,加载这个新的url。
当action为post时候,浏览器把form数据封装到http body中,而后发送到server。
若是没有type=file的控件,用默认的application/x-www-form-urlencoded就能够了。
可是若是有type=file的话,就要用到multipart/form-data了。浏览器会把整个表单以控件为单位分割,并为每一个部分加上Content-Disposition(form-data或者file),Content-Type(默认为text/plain),name(控件name)等信息,并加上分割符(boundary)。
 
    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("Content-type: text/html\n\n");
    printf("<TITLE>Mult Result</TITLE>");
    printf("<H3>Mult Result</H3>");

    data = getenv("QUERY_STRING");
    if(data == NULL)
        printf("<P>Don't transfer data or transfer error");
    else if(sscanf(data,"m=%ld&n=%ld",&m,&n)!=2)
        printf("<P>Error, invalid format, data have to number");
    else
        printf("<P>%ld and %ld result: %ld", m, n, m * n);
    printf("<br><h>Thank you to use the boa webserver</h1>");

    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。假设咱们要实现的任务是这样的:把表单中客户输入的一段文本内容添加到服务器上的一个文本文件的后面。这能够看做是一个留言版程序的雏形。显然,这个工做是没法用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 >form submit failed");
   } else {
       FILE *f;
       fgets(input, len+1, stdin);
       unencode(input+EXTRA, input+len, data);
       f = fopen(DATAFILE, "a");
       if(f == NULL)
           printf("< P >sorry, happened error, can't save your data");
       else
           fputs(data, f);
       fclose(f);
       printf("< P >Thanks very much, had saved your data< BR >%s",data);
   }
   return 0;
}
 
 
 
 从本质上来看,程序先从CONTENT_LENGTH环境变量中获得数据的字长,而后读取相应长度的字符串。由于数据内容在传输的过程当中是通过了编码的,因此必须进行相应的解码。编码的规则很简单,主要的有这几条:
    1. 表单中每一个每一个字段用字段名后跟等号,再接上上这个字段的值来表示,每一个字段之间的内容用&连结;
    2. 全部的空格符号用加号代替,因此在编码码段中出现空格是非法的;
    3. 特殊的字符好比标点符号,和一些有特定意义的字符如“+”,用百分号后跟其对应的ACSII码值来表示。
    例如:若是用户输入的是:
        Hello there!
    那么数据传送到服务器的时候通过编码,就变成了data=Hello+there%21 上面的unencode()函数就是用来把编码后的数据进行解码的。在解码完成后,数据被添加到data.txt文件的尾部,并在浏览其中回显出来。
    把文件编译完成后,把它更名为collect.cgi后放在CGI目录中就能够被表单调用了。下面给出了其相应的表单:
 
    <FORMACTION="/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>Error</TITLE> ");
        printf("<P><EM>have error, can't open file</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。

最后提供一个提交表单,并收到反馈的CGI实例:
<!--pass.html-->
<html> 
<head><title>user login verify</title></head> 
<body>
<!--下面的action是表单提交后在服务器端执行的gic程序(即c的可执行程序)-->
<!--cgi可执行程序放在 /var/www/cgi-bin/目录下-->
<form name="form1" action="/cgi-bin/pass.cgi" method="GET"> 
<table align="center"> 
    <tr><td align="center" colspan="2"></td></tr>
    <tr>
       <td align="right">User</td>
       <td><input type="text" name="Username"></td>
    </tr>
    <tr>
       <td align="right">Passwd</td>
       <td><input type="password" name="Password"></td>
    </tr>
    <tr>
       <td><input type="submit" value="LogIn"></td>
       <td><input type="reset" value="Cancel"></td>
    </tr>
</table>
</form>
</body>
</html>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char* getcgidata(FILE* fp, char* requestmethod);

int main()
{
    char *input;
    char *req_method;
    char name[64];
    char pass[64];
    int i = 0;
    int j = 0;
       
//  printf("Content-type: text/plain; charset=iso-8859-1\n\n");
    printf("Content-type: text/html\n\n");
    printf("The following is query reuslt:<br><br>");

    req_method = getenv("REQUEST_METHOD");
    input = getcgidata(stdin, req_method);

    // 咱们获取的input字符串可能像以下的形式
    // Username="admin"&Password="aaaaa"
    // 其中"Username="和"&Password="都是固定的
    // 而"admin"和"aaaaa"都是变化的,也是咱们要获取的
    
   // 前面9个字符是UserName=
   // 在"UserName="和"&"之间的是咱们要取出来的用户名
       for ( i = 9; i < (int)strlen(input); i++ ) {
           if ( input[i] == '&' ) {
            name[j] = '\0';
               break;
        }                   
        name[j++] = input[i];
      }

    // 前面9个字符 + "&Password="10个字符 + Username的字符数
    // 是咱们不要的,故省略掉,不拷贝
       for ( i = 19 + strlen(name), j = 0; i < (int)strlen(input); i++ ) {
        pass[j++] = input[i];
      }
    pass[j] = '\0';

    printf("Your Username is %s<br>Your Password is %s<br> \n", name, pass);
       
    return 0;
}

char* getcgidata(FILE* fp, char* requestmethod)
{
    char* input;
    int len;
    int size = 1024;
    int i = 0;
       
    if (!strcmp(requestmethod, "GET")) { //从这里能够看出来,GET在cgi中传递的Username="admin"&Password="aaaaa"被放置在环境变量QUERY_STRING中了。
        input = getenv("QUERY_STRING");
        return input;
      } else if (!strcmp(requestmethod, "POST")) {
        len = atoi(getenv("CONTENT_LENGTH"));
        input = (char*)malloc(sizeof(char)*(size + 1));
        
           if (len == 0) {
            input[0] = '\0';
            return input;
           }
              
        while(1) { //从这里能够看出来,POST在cgi中传递的Username="admin"&Password="aaaaa"被写入stdin标准输入流中了。
            input[i] = (char)fgetc(fp);
               if (i == size) {
                input[i+1] = '\0';
                return input;
             }
                     
               --len;
               if (feof(fp) || (!(len))) {
                i++;
                input[i] = '\0';
                return input;
            }
               i++;
           
         }
    }
    return NULL;
}
It's over, Every Body, Come ON!
 
 
 
 
相关文章
相关标签/搜索