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)!=)
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 ;
}
    具体的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+, "%2x", &code) != )
code = "?";
*dest = code;
src +=;
}
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",,);
printf("< TITLE >Response< /TITLE > ");
lenstr = getenv("CONTENT_LENGTH");
if(lenstr == NULL || sscanf(lenstr,"%ld",&len)!= || len > MAXLEN) {
printf("< P >form submit failed");
} else {
FILE *f;
fgets(input, len+, 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 ;
}
从本质上来看,程序先从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", , );
printf("<TITLE>Error</TITLE> ");
printf("<P><EM>have error, can't open file</EM>");
} else {
printf("%s%c%c ", "Content-Type:text/plain", , );
while((ch=getc(f)) != EOF)
putchar(ch);
fclose(f);
} return ;
}
这个程序唯一要注意的是:它并没有把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[];
char pass[];
int i = ;
int j = ; // 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 = ; i < (int)strlen(input); i++ ) {
if ( input[i] == '&' ) {
name[j] = '\0';
break;
}
name[j++] = input[i];
} // 前面9个字符 + "&Password="10个字符 + Username的字符数
// 是我们不要的,故省略掉,不拷贝
for ( i = + strlen(name), j = ; 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 ;
} char* getcgidata(FILE* fp, char* requestmethod)
{
char* input;
int len;
int size = ;
int i = ; 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 + )); if (len == ) {
input[] = '\0';
return input;
} while() { //从这里可以看出来,POST在cgi中传递的Username="admin"&Password="aaaaa"被写入stdin标准输入流中了。
input[i] = (char)fgetc(fp);
if (i == size) {
input[i+] = '\0';
return input;
} --len;
if (feof(fp) || (!(len))) {
i++;
input[i] = '\0';
return input;
}
i++; }
}
return NULL;
}
It's over, Every Body, Come ON!

Linux CGI编程基础【整理】的更多相关文章

  1. linux高级编程基础系列:线程间通信

    linux高级编程基础系列:线程间通信 转载:原文地址http://blog.163.com/jimking_2010/blog/static/1716015352013102510748824/ 线 ...

  2. 服务器编程入门(4)Linux网络编程基础API

      问题聚焦:     这节介绍的不仅是网络编程的几个API     更重要的是,探讨了Linux网络编程基础API与内核中TCP/IP协议族之间的关系.     这节主要介绍三个方面的内容:套接字( ...

  3. Linux 高性能服务器编程——Linux网络编程基础API

    问题聚焦:     这节介绍的不仅是网络编程的几个API     更重要的是,探讨了Linux网络编程基础API与内核中TCP/IP协议族之间的关系.     这节主要介绍三个方面的内容:套接字(so ...

  4. 第5章 Linux网络编程基础

    第5章 Linux网络编程基础 5.1 socket地址与API 一.理解字节序 主机字节序一般为小端字节序.网络字节序一般为大端字节序.当格式化的数据在两台使用了不同字节序的主机之间直接传递时,接收 ...

  5. Linux网络编程基础API

    第5章 Linux网络编程基础API 探讨Linux网络编程基础API与内核中TCP/IP协议族之间的关系,并未后续章节提供编程基础.从3个方面讨论Linux网络API. socket地址API.so ...

  6. linux高性能服务器编程 (五) --Linux网络编程基础api

    第五章 Linux网络编程基础api 1.主机字节序和网络字节序 字节序是指整数在内存中保存的顺序.字节序分为大端字节序.小端字节序. 大端字节序:一个整数的高位字节数据存放在内存的低地址处.低位字节 ...

  7. linux 网络编程 基础

    网络编程基础 套接字编程需要指定套接字地址作为参数,不同的协议族有不同的地址结构,比如以太网其结构为sockaddr_in. 通用套接字: struct sockaddr { sa_family_t ...

  8. linux网络编程基础--(转自网络)

    转自 http://www.cnblogs.com/MyLove-Summer/p/5215287.html Linux下的网络编程指的是socket套接字编程,入门比较简单. 1. socket套接 ...

  9. Linux网络编程基础

    1. Linux网络模型 ① OSI七层模型和Linux四层模型 ② 各种协议之间的关系及在Linux模型中的位置 ③ 协议封装:各种协议处于一种层层封装的关系 (1)Ethernet (2)IP * ...

随机推荐

  1. 第130天:移动端-rem布局

    一.关于布局方案 当拿到设计师给的UI设计图,前端的首要任务就是布局和样式,相信这对于大部分前端工程师来说已经不是什么难题了.移动端的布局相对PC较为简单,关键在于对不同设备的适配.之前介绍了一篇关于 ...

  2. 多线程---handlerthread

    当我们需要工作线程来操作的时候,很多时候会有同步问题,UI更新问题. Handle机制就是为了解决这个问题而产生的. android允许每个线程都有自己的消息队列,同时也可以是主线程消息队列. 但是很 ...

  3. Liunx 和 Win中的软链接详解

    用过Linux的朋友都知道linux中有软链接的概念,可以通过ln命令创建到目录或文件的软链接,软链接的好处就是可以让一个目录或文件有多个入口但保持单一物理位置,方便应用和管理.    1.命令格式: ...

  4. ER-18

    ER #18简要题解 就是推出循环矩阵乘积 然后一次操作后得到的c矩阵第一行第i列就是i的情况(b矩阵下标是a矩阵下标的转置) 两个循环矩阵乘积还是循环矩阵 以此推式子,发现c矩阵的第一行可以用a,b ...

  5. 网络编程----粘包以及粘包问题的解决、FTP上传

    一.粘包现象 让我们基于tcp先制作一个远程执行命令的程序(1:执行错误命令 2:执行ls 3:执行ifconfig) 注意注意: res=subprocess.Popen(cmd.decode('u ...

  6. uC/OS-II之入门与介绍20160525

    说一下刚学习uCOS的心得1)首先强调一下实时操作系统(RTOS)的特点,最明显的是提供及时响应和高可靠性2)基于实施操作系统的应用程序设计中,其中很重要的一个概念是"任务",任务 ...

  7. 《剑指offer》— JavaScript(7)斐波那契数列

    斐波那契数列 题目描述 大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项. n<=39 实现代码 function Fibonacci(n) { var arr = ...

  8. Codeforces 19.E Fairy

    E. Fairy time limit per test 1.5 seconds memory limit per test 256 megabytes input standard input ou ...

  9. git 撤销上一次 commit

    1.本地 commit,没有推到远程仓库 可以 git reset --soft <commit_id>,commit_id 是要回退到的某一版本 然后再进行修改,再commit, 如果需 ...

  10. Udp打洞原理和源代码。

    所谓udp打洞就是指客户端A通过udp协议向服务器发送数据包,服务器收到后,获取数据包,并且 可获取客户端A地址和端口号.同样在客户端B发送给服务器udp数据包后,服务器同样在收到B发送过来 的数据包 ...