由于Carl要用到我的程序,我们便合作工作。但是他写的程序是Python的,我写的程序是Java的,必须得找一种方式进行通信。尽管有Jython这些东西,但是Carl认为还是CGI最简便。于是,前阵子开始学学CGI怎么弄。刚开始,觉得好像也不是很难,但是后来进展没有预期的顺利。最后,由于学院的服务器有CGI模块但是不允许随便跑CGI,实验室服务器又装的是FastCGI,感觉麻烦,最后Carl说还是用socket吧... - - |||。
整体来说,用CGI进行通信这个计划算是破产了。虽然是很老旧、基本都不会再用的东西了,但是由于对我来说是新东西,还是很好奇,没有实现,自然是一种惋惜。虽然最后没能用上CGI,但是个人觉得如果学院服务器让用户自己跑CGI的话,应该还是没问题的。无论怎样,还是记录下一些基本知识,免得以后又忘了。CGI年代久远,不过却实现了动态网页。CGI,准确说,应该是种协议(或者说接口),它使得server中的程序(cgi script)能够通过标准I/O流(STDIN和STDOUT,比如在Java语言里就是System.in和System.out,在C里面就是printf等,只要是能够进行标准I/O流读写的程序,都可以用来实现CGI)读取到所需的信息和输出信息给浏览器,而这些所要从客户端读取的信息则是包含在某些已经被定义好的环境变量中,我们只需要读取这些环境变量,自然就能获取想要的值了(这也就是CGI这个借口为我们所做的事)。而通常所说的CGI,也有可能泛指CGI程序,即CGI script,是开发者自己所写的、处理用户请求的程序。
总体来说,过程是这样的:
1. 服务器接受到请求
2. 服务器发现这个请求是需要一个CGI程序来进行处理
3. 服务器建立一个环境,这个运行环境里有一些变量,也就是所谓的CGI程序所需要的环境变量
4. 服务器在这个环境下启动相应的CGI程序
5. CGI程序解析自己想要的环境变量来获取所提交的请求信息
6. CGI程序对这些请求信息做相应的处理
7. CGI程序将相应的结果输出到标准输出STDOUT,此结果将被输出到用户端,呈现给用户结果。
8. CGI程序执行完毕,退出程序。
比较详细来说,原理大概是这样的:
1. 客户端在浏览某个网页的时候,提交表单(form)。而表单的action则指定了要处理该表单的CGI程序,比如:
<html>
<body>
<form action="/cgi-bin/plus.cgi" method="GET">
<input name="m" size="5">
<input name="n" size="5"><br>
<input type="submit" values="提交">
</form>
</body>
</html>
上面的程序中,action="/cgi-bin/plus.cgi" 的意思就是说“这个表单里的数据,都交给plus.cgi这个程序处理了,然后它处理完这些数据后,再把结果返回给浏览器”。
需要说明的是,plus是一个可执行文件的文件名,比如可能是一个shell文件(例如 plus.sh),其后缀不一定是.cgi。那么为什么要取在这里取名.cgi呢?原因是这样能说明,这个表单处理,是通过一个类型为cgi的程序进行处理,或者说,通过cgi这种方式让这个可执行文件对提交的数据进行处理。一般来说,出于安全考虑,不是任何用户都有权限去写一个自己的cgi程序然后上传到服务器、让该服务器去运行的(比如我们学院的服务器就不行,哎...)。而只有放在服务器指定目录(一般为cgi-bin)下的可执行文件,才会被被视为是cgi程序,才能被执行。而一般人是无法访问这个目录的。
2. 然后,name为“m”和“n”的这两个变量以及他们的值,就通过某个环境变量输入到CGI程序中,由CGI程序去进行该CGI程序所指定的处理了。GET和POST获取“m”和“n”的方式有所不同。如果提交方法是GET的话,那么GET所提交的内容则被包含在名为QUERY_STRING的环境变量中,当得到这个环境变量的值后,将其内容解析出来就可以得到name/value这样的值对了,就可以进行相应的处理了。而POST的话,则需要从标准输入流STDIN里面读取数据,而这些数据到底有多少呢?这个则需要通过读取CONTENT_LENGTH这个环境变量的值来知道。
像在C语言里面,可以通过getenv函数来直接获取环境变量的值,比如:getenv("QUERY_STRING"), getenv("CONTENT_LENGTH")等等。Perl可能要算是写CGI的最佳语言,但是不会Perl,此处略过。另外,由于我的程序是用Java写的,就使得读取环境变量的方式有点特殊,因为Java是没有直接获取这些环境变量的函数的。Java为何不能直接访问环境变量?简单说,因为Java内部还有一个属于System这个类的属性。因此,我们一个System.getProperty获取的是Java中这个System类的属性的,而不是环境变量的。这些System的属性也是以name/value这样的形式来存储一些Java运行环境的信息的。因而,额外的环境变量(或者说属性信息)需要用指令java -D的形式,详见后面的wrapper程序。
常用的环境变量有:REQUEST_METHOD,QUERY_STRING,CONTENT_LENGTH,PATH_INFO。其他的有:SERVER_SOFTWARE,SERVER_NAME,GATEWAY_INTERFACE,SERVER_PROTOCOL,SERVER_PORT,PATH_TRANSLATED,SCRIPT_NAME,REMOTE_HOST,REMOTE_ADDR,AUTH_TYPE,REMOTE_USER,REMOTE_IDENT,CONTENT_TYPE。如果现在忘记这些是干嘛的了...额...google下吧...
现在,我们已经读取到了所想要的环境变量。但是,这些环境变量的值,是需要我们进行解析的,因为当初在传给我们的时候,就是进行了URL编码的(URL encode)。比如,如果我们提交的表单是GET类型的话,再提交完信息后,可以在地址栏看来类似这样的url地址:http://www.xxxxxxx.edu/cgi-bin/plus.cgi?m=5&n=6。其中,“m=5&n=6”就是被编码的字符串,也就是我们想要获取的值。而“http://www.xxxxxxx.edu/cgi-bin/plus.cgi”是处理该表单的cgi程序的地址。“?”说明后面的字符串(即“m=5&n=6”)是传入的参数。不难看出,在编码的时候,类似于这样的规则“name1=value1&name2=value2&name3=value3”。此外,非西文的字符串将被“%XX”所代替,空格将被“+”代替。具体地,去参考URL encode的相关资料。
如何解析这些麻烦的字符串,不需要自己再动手去写,网上有很多这样的函数库,各种语言。直接用网上现成的函数库来解析就行了。就Java来说,推荐cgi_lib.java这个库。很方便,应用也很广泛,文件也很小。其中函数的功能是跟cgi_lib.pl一样的。
3. 解析字符串后,我们可以知道变量m的值为5,变量n的值为6。然后就可以在CGI程序中进行相应的处理了。自己想怎么处理就怎么处理。比如,此处是想处理加法,则在自己的CGI程序里实现m和n的加法即可。
4. 处理完了数据,需要将结果呈现给浏览器端的用户。这个简单,直接用STDOUT就行了。比如,C语言用printf,Java语言用System.out.println之类的。但是需要注意的是,首先输出的必须是"Content-type: text/plain"(也就是MIME头信息,告诉server它随后的输出是以纯ASCII文本的形式),然后下面空一行(必须空一行),再直接打印出要呈现给用户的html语句。比如,在Java里面,就是:
System.out.println("Content-type: text/plain");
System.out.println("");
比如,要测验自己是否有权限可以写一个CGI放在服务器上运行,则可以写一个最简单的CGI程序,该程序暂时不处理任何来自用户的请求,只是输出"Hello, world"。
shell程序可以写成:
#!/bin/sh
echo "Content-type: text/html"
echo ""
echo "<html>"
echo "<body>"
echo "<h1>Hello, world</h1>"
echo "</body>"
echo "</html>"
C语言可以写成:
#include<stdio.h>
void main(void){
printf("Content-type: text/html\n");
printf("\n");
printf("<html>\n");
printf("<body>\n");
printf("<h1>Hello, world</h1>\n");
printf("</body>");
printf("/<html>");
}
这样,在客户端运行就会呈现出<h1>大小的“Hello, world”了。
Java的特别注意:
1.Java读取环境变量,只能通过-D的option来加以读取。
2.Java生成的.class文件(比如java_plus.class),这不是一个通常意义上的可执行文件,这个文件只是说通过JVM可以执行起来。而CGI程序是一个直接可以执行的文件。因此,我们需要写一个wrapper,将这个.class文件封装起来。当调用这个wrapper程序的时候,这个wrapper程序能通过Java指令运行该.class文件。
结合上诉两点,则我们可以写一个shell文件当做wrapper程序,如下:
#!/bin/sh
java \
-Dcgi.content_type=$CONTENT_TYPE \
-Dcgi.content_length=$CONTENT_LENGTH \
-Dcgi.request_method=$REQUEST_METHOD \
-Dcgi.query_string=$QUERY_STRING \
-Dcgi.server_name=$SERVER_NAME \
-Dcgi.server_port=$SERVER_PORT \
-Dcgi.script_name=$SCRIPT_NAME \
-Dcgi.path_info=$PATH_INFO \
java_plus
然后将上面的文件保存为plus.sh,并且放在指定的目录(比如cgi-bin)即可。这样,当表单提交新的时候,就会到这个目录中(因为后缀名为cgi)去寻找名为plus的文件,然后执行这个文件。而在java_plus这个文件中,我们用System.getProperty("cgi.query_string")就可以访问到QUERY_STRING这个环境变量了。
http://www.jmarshall.com/easy/cgi/
http://www.eli.sdsu.edu/courses/spring96/cs596/notes/andrew/cgi.html
http://www.javaworld.com/javaworld/jw-01-1997/jw-01-cgiscripts.html
http://apps.hi.baidu.com/share/detail/18814484
http://www.jaguarpc.com/forums/showthread.php?t=2553
http://httpd.apache.org/docs/current/howto/cgi.html
至于FastCGI,这个效率比传统CGI高很多。但是,比较麻烦,需要用循环,还要设置端口什么的,就没动手搞那个,只是看了下相关资料。还是放在这里吧。
http://www.phpchina.com/download/handbook/linux-html/1272.html
http://www.20cn.net/ns/wz/net/data/20030615005558.htm
http://www.fastcgi.com/devkit/doc/fcgi-java.htm
http://www.citycat.ru/doc/FastCGI/fcdk/index.html
http://hi.baidu.com/coffeefoam/blog/item/1446493be749f3e814cecbb8.html