接着上篇《Nginx安装与使用》,本篇介绍CGI/FASTCGI的原理、及如何使用C/C++编写简单的CGI/FastCGI,最后将CGI/FASTCGI部署到nginx。内容大纲如下:
1. CGI 1.1. 环境变量 1.2. 标准输入 2. FastCGI 3. nginx cgi/fastcgi 3.1. nginx + fastcgi 3.1.1. spawn-fcgi 3.1.2. 编写fastcgi应用程序 3.1.3. nginx fastcgi配置 3.2. nginx + cgi 3.2.1 fastcgi-wrapper 3.2.2. nginx fcgiwrap配置 3.2.3. 编写cgi应用程序 参考链接 1.CGI最初,CGI 是在 1993 年由美国国家超级电脑应用中心(NCSA)为 NCSA HTTPd Web 服务器开发的。这个 Web 服务器使用了UNIX shell 环境变量 来保存从 Web 服务器传递出去的参数,然后生成一个运行 CGI 的独立的进程。cgi的处理流程如下图所示: l step1. web 服务器收到客户端(浏览器)的请求Http Request,启动CGI程序,并通过环境变量、标准输入传递数据 l step2. cgi进程启动解析器、加载配置(如业务相关配置)、连接其它服务器(如数据库服务器)、逻辑处理等 l step3. cgi程将处理结果通过标准输出、标准错误,传递给web 服务器 l step4. web 服务器收到cgi返回的结果,构建Http Response返回给客户端,并杀死cgi进程 web服务器与cgi通过环境变量、标准输入、标准输出、标准错误互相传递数据。 1.1.环境变量GET请求,它将数据打包放置在环境变量QUERY_STRING中,CGI从环境变量QUERY_STRING中获取数据。常见的环境变量如下表所示:
1.2.标准输入环境变量的大小是有一定的限制的,当需要传送的数据量大时,储存环境变量的空间可能会不足,造成数据接收不完全,甚至无法执行CGI程序。因此后来又发展出另外一种方法:POST,也就是利用I/O重新导向的技巧,让CGI程序可以由STDIN和STDOUT直接跟浏览器沟通。 当我们指定用这种方法传递请求的数据时,web 服务器收到数据后会先放在一块输入缓冲区中,并且将数据的大小记录在CONTENT_LENGTH这个环境变数,然后调用CGI程式并将CGI程序的STDIN指向这块缓冲区,于是我们就可以很顺利的通过STDIN和环境变数CONTENT_LENGTH得到所有的资料,再没有资料大小的限制了。 总结:CGI使外部程序与Web服务器之间交互成为可能。CGI程式运行在独立的进程中,并对每个Web请求建立一个进程,这种方法非常容易实现,但效率很差,难以扩展。面对大量请求,进程的大量建立和消亡使操作系统性能大大下降。此外,由于地址空间无法共享,也限制了资源重用。 2.FastCGI当进来一个请求时,Web 服务器把环境变量和这个页面请求通过一个unix domain socket(都位于同一物理服务器)或者一个IP Socket(FastCGI部署在其它物理服务器)传递给FastCGI进程。 l step1. Web 服务器启动时载入初始化FastCGI执行环境 。 例如IIS ISAPI、apache mod_fastcgi、nginx ngx_http_fastcgi_module、lighttpd mod_fastcgi l step2. FastCGI进程管理器自身初始化,启动多个CGI解释器进程并等待来自Web 服务器的连接。启动FastCGI进程时,可以配置以ip和UNIX 域socket两种方式启动。 l step3. 当客户端请求到达Web 服务器时, Web 服务器将请求采用socket方式转发到 FastCGI主进程,FastCGI主进程选择并连接到一个CGI解释器。Web 服务器将CGI环境变量和标准输入发送到FastCGI子进程。 l step4. FastCGI子进程完成处理后将标准输出和错误信息从同一socket连接返回Web 服务器。当FastCGI子进程关闭连接时,请求便处理完成。 l step5. FastCGI子进程接着等待并处理来自Web 服务器的下一个连接。 由于 FastCGI 程序并不需要不断的产生新进程,可以大大降低服务器的压力并且产生较高的应用效率。它的速度效率最少要比CGI技术提高 5 倍以上。它还支持分布式的部署, 即 FastCGI 程序可以在web 服务器以外的主机上执行。 总结:CGI 就是所谓的短生存期应用程序,FastCGI 就是所谓的长生存期应用程序。FastCGI像是一个常驻(long-live)型的CGI,它可以一直执行着,不会每次都要花费时间去fork一次(这是CGI最为人诟病的fork-and-execute 模式)。 3.nginx cgi/fastcgi3.1. nginx + fastcgi通过前面的介绍知道,fastcgi进程由FastCGI进程管理器管理,而不是nginx。这样就需要一个FastCGI管理,管理我们编写fastcgi程序。本文使用spawn-fcgi作为FastCGI进程管理器。3.1.1. spawn-fcgispawn-fcgi是一个通用的FastCGI进程管理器,简单小巧,原先是属于lighttpd的一部分,后来由于使用比较广泛,所以就迁移出来作为独立项目了。spawn-fcgi使用pre-fork 模型,功能主要是打开监听端口,绑定地址,然后fork-and-exec创建我们编写的fastcgi应用程序进程,退出完成工作。fastcgi应用程序初始化,然后进入死循环侦听socket的连接请求。安装spawn-fcgi: l 获取spawn-fcgi编译安装包,在http://redmine.lighttpd.net/projects/spawn-fcgi/wiki上可以获取当前最新的版本。 l 解压缩spawn-fcgi-x.x.x.tar.gz包。 l 进入解压缩目录,执行./configure。 l make & make install 如果遇到以下错误:“ ./autogen.sh: x: autoreconf: not found”,因为没有安装automake 工具,ubuntu用下面的命令安装好就可以了:sudo apt-get install autoconf automake libtool 。 spawn-fcgi的帮助信息可以通过man spawn-fcgi或spawn-fcgi –h获得,下面是部分常用spawn-fcgi参数信息:
3.1.2. 编写fastcgi应用程序使用C/C++编写fastcgi应用程序,可以使用FastCGI软件开发套件或者其它开发框架,如fastcgi++。本文使用FastCGI软件开发套件——fcgi(http://www.fastcgi.com/drupal/node/6?q=node/21),通过此套件可以轻松编写fastcgi应用程序,安装fcgi: l 获取fcgi编译安装包,在http://www.fastcgi.com/drupal/node/5上可以获取当前最新的版本。 l 解压缩fcgi-x.x.x.tar.gz包。 l 进入解压缩目录,执行./configure。 l make & make install 如果编译提示一下错误: fcgio.cpp: In destructor 'virtual fcgi_streambuf::~fcgi_streambuf()': fcgio.cpp:50: error: 'EOF' was not declared in this scope fcgio.cpp: In member function 'virtual int fcgi_streambuf::overflow(int)': fcgio.cpp:70: error: 'EOF' was not declared in this scope fcgio.cpp:75: error: 'EOF' was not declared in this scope fcgio.cpp: In member function 'virtual int fcgi_streambuf::sync()': fcgio.cpp:86: error: 'EOF' was not declared in this scope fcgio.cpp:87: error: 'EOF' was not declared in this scope fcgio.cpp: In member function 'virtual int fcgi_streambuf::underflow()': fcgio.cpp:113: error: 'EOF' was not declared in this scope make[2]: *** [fcgio.lo] Error 1 make[2]: Leaving directory `/root/downloads/fcgi-2.4.1-SNAP-0910052249/libfcgi' make[1]: *** [all-recursive] Error 1 make[1]: Leaving directory `/root/downloads/fcgi-2.4.1-SNAP-0910052249'make: *** [all] Error 2 解决办法:在/include/fcgio.h文件中加上 #include <cstdio>,然后再编译安装就通过了。 如果提示找不到动态库,请在LD_LIBRARY_PATH或/etc/ld.so.conf中添加fcgi的安装路径,如/usr/local/lib,并执行ldconfig更新一下。 #include "fcgi_stdio.h" #include <stdlib.h> int main(void) { int count = 0; while (FCGI_Accept() >= 0) printf("Content-type: text/html\r\n" "\r\n" "<title>FastCGI Hello!</title>" "<h1>FastCGI Hello!</h1>" "Request number %d running on host <i>%s</i>\n", ++count, getenv("SERVER_NAME")); return 0; } 编译g++ main.cpp -o demo –lfcgi,并将demo部署到/opt/nginx-1.7.7/cgi-bin/目录 通过spawn-fcgi启动c/c++编写好的fastcgi程序:/opt/nginx-1.7.7/sbin/spawn-fcgi -a 127.0.0.1 -p 8081 -f /opt/nginx-1.7.7/cgi-bin/demo 3.1.3. nginx fastcgi配置关于nginx的几个配置文件解析,可以参阅《Nginx安装与使用》http://www.cnblogs.com/skynet/p/4146083.html,在上篇的nginx.conf基础上增加下面的fastcgi配置。这样nginx收到http://localhost/demo.cgi请求时,会匹配到location = /demo.cgi块,将请求传到后端的fastcgi应用程序处理。如下如所示:(注意其中number为80,是因为我请求了80次) 3.2. nginx + cginginx 不能直接执行外部可执行程序,并且cgi是接收到请求时才会启动cgi进程,不像fastcgi会在一开就启动好,这样nginx天生是不支持 cgi 的。nginx 虽然不支持cgi,但它支持 fastCGI。所以,我们可以考虑使用fastcgi包装来支持 cgi。原理大致如下图所示:pre-fork几个通用的代理fastcgi程序——fastcgi-wrapper,fastcgi-wrapper启动执行cgi然后将cgi的执行结果返回给nginx(fork-and-exec)。明白原理之后,编写一个fastcgi-warpper也比较简单。网上流传比较多的一个解决方案是,来自nginx wiki(http://wiki.nginx.org/SimpleCGI)上的使用perl的fastcgi包装脚本cgiwrap-fcgi.pl。但我对perl不是很感冒,下面给出一个C/C++写的fastcgi-wrapper。 3.2.1. fastcgi-wrapper其实编写C/C++的fastcgi-wrapper,就是写一个C/C++的fastcgi,步骤和原理跟前面的小节(nginx+fastcgi)一样。github上已经有人开源了,C写的fastcgi-wrapper:https://github.com/gnosek/fcgiwrap。安装fcgiwrap: l 下载(https://github.com/gnosek/fcgiwrap.git) l 解压缩fcgiwrap,进入解压目录 l autoreconf -i l ./configure l make && make install 启动fastcgi-wrapper:/opt/nginx-1.7.7/sbin/spawn-fcgi -f /usr/local/sbin/fcgiwrap -p 8081 3.2.2. nginx fcgiwrap配置在nginx.conf中增加下面的loaction配置块,这样所有的xxx.cgi请求都会走到fcgiwrap,然后fcgiwrap会执行cgi-bin目录下的cgi程序。3.2.3. 编写cgi应用程序#include <stdio.h> #include <stdlib.h> int main(void) { int count = 0; printf("Content-type: text/html\r\n" "\r\n" "<title>CGI Hello!</title>" "<h1>CGI Hello!</h1>" "Request number %d running on host <i>%s</i>\n", ++count, getenv("SERVER_NAME")); return 0; } tyler@ubuntu:~/ClionProjects/HelloFastCGI$ g++ cgi.cpp -o cgidemo -lfcgi tyler@ubuntu:~/ClionProjects/HelloFastCGI$ sudo cp cgidemo /opt/nginx-1.7.7/cgi-bin/ 注意图中的请求次数一直都是1,因为cgi的模式是fork-and-exec,每次都是一个新的进程。 参考链接l fastcgi, http://www.dwz.cn/yFMap l spawn-fcgi, http://redmine.lighttpd.net/projects/spawn-fcgi/wiki l fcgi, http://www.fastcgi.com/drupal/node/6?q=node/21 l fcgiwrap, https://github.com/gnosek/fcgiwrap.git (责任编辑:IT) |