由于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这个环境变量了。
至于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
web应用中动态部分的java和cgi实现原理的简单总结
一、初始web
最初知道网站是用html写的,静态的,会动的那个显示效果,内容完全没变,只是有时候移动了,有时候隐藏了。
这个时候,我认为:
浏览器是一个html的解释器和http的解释器的合体。
服务器是一个接收http请求,并可以根据请求中指定的文件(无论是地址栏写的,还是超链接发送的)找到服务器对应的文件,把文件中的内容以http的格式返回给浏览器。
不过听说表单是可以提交给后台处理的,也就是内容可以根据人工输入而改变的。但是这个过程一直不知道是什么样。
二、java的动态网站
后来学习了java,知道用tomcat和jsp/servlet就可以实现动态交互的网站,比如提交个表单,后台处理一下,然后返回一个结果。但是文件不是html了,而jsp了,或者直接后台的java类了,既然浏览器没有变,那么后台返回的内容一定还是html格式的内容,无论是jsp还是servlet都是需要tomcat(或者apache+tomcat等)翻译成html格式的静态文件。其中tomcat可以内部划分为web服务器和应用服务器(即j2ee的容器)。
那么流程大概是这样的:
1、浏览器请求一个地址,
2、web服务器通过地址的格式判断,如果后缀是html等静态文件就直接返回文件内容,
3、如果后缀是jsp就发送给应用服务器处理,容器会将相应的jsp文件翻译为servlet(即java类),
4、如果后缀是其他非常规静态文件和jsp,那么也会发送给容器,容器检查web.xml中的servlet定义是否包含了这个路径,如果有则调用相应的servlet,
5、调用servlet之前,容器会在启动时进行一系列j2ee规范中的session-config、context-param,welcom-file-list等预处理来准备提供服务,接收请求之前会运行listener,filter等对请求做预处理,这些都不是本文要关心的。
6、终于在第3或第4步等待这么久的servlet要处理了,web服务器传给容器的地址,就好像一个标准输入的文本,容器监听程序读取地址,然后解析对应的类,并尝试调用类的对象。容器管理所有servlet对象的生命周期,对象保存在容器中重复使用,容器根据注册在web.xml中的类寻找对象,如果没有,则会创建一个该类的对象并运行其init方法,当有请求时,调用对象的service方法。并将servlet的输出流(HttpServletResponse.getWriter())作为返回的结果,并且完全按照html格式输出。
7、容器将结果返回给浏览器,浏览器解析http报文,解释html文件,显示。
三、cgi是什么
除了java之外,还有很多语言都可以做动态网站,比如asp、.net,虽然只看过一眼,猜想大概和java的类似,有自己的容器负责处理。但是其他语言貌似都是用cgi实现,那么cgi是什么,虽然百度(鄙视我吧)了一下定义,完全没搞清楚,继续找吧。没有写过cgi,只能顺藤摸瓜的半猜半看了。
cgi其实是一套web服务器和各种语言通信的协议,Common Gateway Interface通用网关接口,其实web服务器需要通信的东西不多。
1、主要是告诉后台程序,传了什么参数过来(除了用户手工填写的参数,还有浏览器相关、http协议相关的一些参数),后台程序如果通过主程序参数传入非常不通用而且复杂,所以cgi规定了一套环境变量,让http请求的相关参数全部存在环境变量中,那么所有后台语言都可以引用环境变量的参数值。
2、解决了传参问题,那么就是如何运行了,在apache(web服务器)中,可以在httpd.conf中配置程序执行器,例如perl语言就用perl.exe(windows中),web服务器会用浏览器请求的地址生成本地文件的路径,作为perl.exe的运行参数。当然如果命令行需要其他的参数,也可以在web服务器中配置。
3、执行完后,后台程序输出到标准输出流的结果会被web服务器捕获(当然也是html格式的),封装成http包发送给浏览器。
4、浏览器解析显示。
可以看出,cgi每次请求都重新执行后台程序的解析工作,进程不像java一样在容器中可以重复利用。有个叫fastcgi的就是解决这个问题的,它是一个进程管理器,让进程能重复利用。
=========http://gaodi2002.blog.163.com/blog/static/232076820106215131828/
C语言cgi程序在apache上的实现
本文介绍使用apache实现C语言写的cgi程序。
必要条件,安装apache。
首先建立C程序,这里就不多介绍。参照前面的文章或者其它的参考书籍。
建立文件hello.c,内容如下:
#include <stdio.h>
int main()
{
printf("Content-type:text/html\n\n");
printf("<html>");
printf("<head><title>welcome to c cgi.</title></head><body>");
printf("你好:世界<br/>");
printf("</body></html>");
}
编绎hello.c,生成hello.exe。把hello.exe文件拷到 apache安装/cgi-bin/ 目录下。
然后配置 apache 配置文件 httpd.conf。在配制文件中找到
AddHandler cgi-script .cgi 在这一行后面加上 .exe,并且去掉前边的#
最后在浏览器中输入 http://localhost/cgi-bin/hello.exe,回车。
在浏览器中将显示:你好:世界。
通过index.html调用cgi
index.html 文件如下:
<HTML>
<HEAD>
<HEAD>
<BODY>
<FORM ACTION=http://localhost/cgi-bin/cgi.exe METHOD=POST>
<INPUT TYPE=submit VALUE=OK>
</FORM>
</BODY>
</HTML>
在浏览器中输入 http://localhost/index.html,回车。
点击ok按钮,调用cgi程序浏览器显示:你好:世界。