接著(zhù)上篇《
Nginx安裝與使用》,本篇介紹CGI/FASTCGI的原理、及如何使用C/C++編寫(xiě)簡(jiǎn)單的CGI/FastCGI,最后將CGI/FASTCGI部署到nginx。內容大綱如下:
1. CGI
1.1. 環(huán)境變量
1.2. 標準輸入
2. FastCGI
3. nginx cgi/fastcgi
3.1. nginx + fastcgi
3.1.1. spawn-fcgi
3.1.2. 編寫(xiě)fastcgi應用程序
3.1.3. nginx fastcgi配置
3.2. nginx + cgi
3.2.1 fastcgi-wrapper
3.2.2. nginx fcgiwrap配置
3.2.3. 編寫(xiě)cgi應用程序
參考鏈接
1.CGI
通用網(wǎng)關(guān)接口(Common Gateway Interface/CGI)描述了客戶(hù)端和服務(wù)器程序之間傳輸數據的一種標準,可以讓一個(gè)客戶(hù)端,從網(wǎng)頁(yè)瀏覽器向執行在網(wǎng)絡(luò )服務(wù)器上的程序請求數據。CGI 獨立于任何語(yǔ)言的,CGI 程序可以用任何
腳本語(yǔ)言或者是完全獨立
編程語(yǔ)言實(shí)現,只要這個(gè)語(yǔ)言可以在這個(gè)系統上運行。
Unix shell script,
Python,
Ruby,
PHP, perl,
Tcl,
C/
C++, 和
Visual Basic 都可以用來(lái)編寫(xiě) CGI 程序。(
http://www.dwz.cn/yFFgQ)
最初,CGI 是在 1993 年由美國
國家超級電腦應用中心(NCSA)為 NCSA HTTPd Web 服務(wù)器開(kāi)發(fā)的。這個(gè) Web 服務(wù)器使用了 UNIX
shell 環(huán)境變量 來(lái)保存從 Web 服務(wù)器傳遞出去的參數,然后生成一個(gè)運行 CGI 的獨立的
進(jìn)程。cgi的處理流程如下圖所示:
l step1. web 服務(wù)器收到客戶(hù)端(瀏覽器)的請求Http Request,啟動(dòng)CGI程序,并通過(guò)環(huán)境變量、標準輸入傳遞數據
l step2. cgi進(jìn)程啟動(dòng)解析器、加載配置(如業(yè)務(wù)相關(guān)配置)、連接其它服務(wù)器(如數據庫服務(wù)器)、邏輯處理等
l step3. cgi程將處理結果通過(guò)標準輸出、標準錯誤,傳遞給web 服務(wù)器
l step4. web 服務(wù)器收到cgi返回的結果,構建Http Response返回給客戶(hù)端,并殺死cgi進(jìn)程
web服務(wù)器與cgi通過(guò)環(huán)境變量、標準輸入、標準輸出、標準錯誤互相傳遞數據。
1.1.環(huán)境變量
GET請求,它將數據打包放置在環(huán)境變量QUERY_STRING中,CGI從環(huán)境變量QUERY_STRING中獲取數據。常見(jiàn)的環(huán)境變量如下表所示:
環(huán)境變數
內容
AUTH_TYPE
存取認證類(lèi)型。
CONTENT_LENGTH
由標準輸入傳遞給CGI程序的數據長(cháng)度,以bytes或字元數來(lái)計算。
CONTENT_TYPE
請求的MIME類(lèi)型。
GATEWAY_INTERFACE
服務(wù)器的CGI版本編號。
HTTP_ACCEPT
瀏覽器能直接接收的Content-types, 可以有HTTP Accept header定義.
HTTP_USER_AGENT
遞交表單的瀏覽器的名稱(chēng)、版本 和其他平臺性的附加信息。
HTTP_REFERER
遞交表單的文本的 URL,不是所有的瀏覽器都發(fā)出這個(gè)信息,不要依賴(lài)它
PATH_INFO
傳遞給cgi程式的路徑信息。
QUERY_STRING
傳遞給CGI程式的請求參數,也就是用"?"隔開(kāi),添加在URL后面的字串。
REMOTE_ADDR
client端的host名稱(chēng)。
REMOTE_HOST
client端的IP位址。
REMOTE_USER
client端送出來(lái)的使用者名稱(chēng)。
REMOTE_METHOD
client端發(fā)出請求的方法(如get、post)。
SCRIPT_NAME
CGI程式所在的虛擬路徑,如/cgi-bin/echo。
SERVER_NAME
server的host名稱(chēng)或IP地址。
SERVER_PORT
收到request的server端口。
SERVER_PROTOCOL
所使用的通訊協(xié)定和版本編號。
SERVER_SOFTWARE
server程序的名稱(chēng)和版本。
1.2.標準輸入
環(huán)境變量的大小是有一定的限制的,當需要傳送的數據量大時(shí),儲存環(huán)境變量的空間可能會(huì )不足,造成數據接收不完全,甚至無(wú)法執行CGI程序。因此后來(lái)又發(fā)展出另外一種方法:POST,也就是利用I/O重新導向的技巧,讓CGI程序可以由STDIN和STDOUT直接跟瀏覽器溝通。當我們指定用這種方法傳遞請求的數據時(shí),web 服務(wù)器收到數據后會(huì )先放在一塊輸入緩沖區中,并且將數據的大小記錄在CONTENT_LENGTH這個(gè)環(huán)境變數,然后調用CGI程式并將CGI程序的STDIN指向這塊緩沖區,于是我們就可以很順利的通過(guò)STDIN和環(huán)境變數CONTENT_LENGTH得到所有的資料,再沒(méi)有資料大小的限制了。
總結:CGI使外部程序與Web服務(wù)器之間交互成為可能。CGI程式運行在獨立的進(jìn)程中,并對每個(gè)Web請求建立一個(gè)進(jìn)程,這種方法非常容易實(shí)現,但效率很差,難以擴展。面對大量請求,進(jìn)程的大量建立和消亡使操作系統性能大大下降。此外,由于地址空間無(wú)法共享,也限制了資源重用。
2.FastCGI
快速通用網(wǎng)關(guān)接口(Fast Common Gateway Interface/FastCGI)是
通用網(wǎng)關(guān)接口(CGI)的改進(jìn),描述了客戶(hù)端和服務(wù)器程序之間傳輸數據的一種標準。FastCGI致力于減少
Web服務(wù)器與
CGI程式之間互動(dòng)的開(kāi)銷(xiāo),從而使
服務(wù)器可以同時(shí)處理更多的Web請求。與為每個(gè)請求創(chuàng )建一個(gè)新的進(jìn)程不同,FastCGI使用持續的進(jìn)程來(lái)處理一連串的請求。這些進(jìn)程由FastCGI進(jìn)程管理器管理,而不是web服務(wù)器。(
http://www.dwz.cn/yFMap)
當進(jìn)來(lái)一個(gè)請求時(shí),Web 服務(wù)器把環(huán)境變量和這個(gè)頁(yè)面請求通過(guò)一個(gè)unix domain socket(都位于同一物理服務(wù)器)或者一個(gè)IP Socket(FastCGI部署在其它物理服務(wù)器)傳遞給FastCGI進(jìn)程。
l step1. Web 服務(wù)器啟動(dòng)時(shí)載入初始化FastCGI執行環(huán)境 。 例如IIS ISAPI、apache mod_fastcgi、nginx ngx_http_fastcgi_module、lighttpd mod_fastcgi
l step2. FastCGI進(jìn)程管理器自身初始化,啟動(dòng)多個(gè)CGI解釋器進(jìn)程并等待來(lái)自Web 服務(wù)器的連接。啟動(dòng)FastCGI進(jìn)程時(shí),可以配置以ip和UNIX 域socket兩種方式啟動(dòng)。
l step3. 當客戶(hù)端請求到達Web 服務(wù)器時(shí), Web 服務(wù)器將請求采用socket方式轉發(fā)到 FastCGI主進(jìn)程,FastCGI主進(jìn)程選擇并連接到一個(gè)CGI解釋器。Web 服務(wù)器將CGI環(huán)境變量和標準輸入發(fā)送到FastCGI子進(jìn)程。
l step4. FastCGI子進(jìn)程完成處理后將標準輸出和錯誤信息從同一socket連接返回Web 服務(wù)器。當FastCGI子進(jìn)程關(guān)閉連接時(shí),請求便處理完成。
l step5. FastCGI子進(jìn)程接著(zhù)等待并處理來(lái)自Web 服務(wù)器的下一個(gè)連接。
由于 FastCGI 程序并不需要不斷的產(chǎn)生新進(jìn)程,可以大大降低服務(wù)器的壓力并且產(chǎn)生較高的應用效率。它的速度效率最少要比CGI 技術(shù)提高 5 倍以上。它還支持分布式的部署, 即 FastCGI 程序可以在web 服務(wù)器以外的主機上執行。
總結:CGI 就是所謂的短生存期應用程序,FastCGI 就是所謂的長(cháng)生存期應用程序。FastCGI像是一個(gè)常駐(long-live)型的CGI,它可以一直執行著(zhù),不會(huì )每次都要花費時(shí)間去fork一次(這是CGI最為人詬病的fork-and-execute 模式)。
3.nginx cgi/fastcgi
nginx 不能像apache那樣直接執行外部可執行程序,但nginx可以作為代理服務(wù)器,將請求轉發(fā)給后端服務(wù)器,這也是nginx的主要作用之一。其中nginx就支持FastCGI代理,接收客戶(hù)端的請求,然后將請求轉發(fā)給后端fastcgi進(jìn)程。下面介紹如何使用C/C++編寫(xiě)cgi/fastcgi,并部署到nginx中。
3.1. nginx + fastcgi
通過(guò)前面的介紹知道,fastcgi進(jìn)程由FastCGI進(jìn)程管理器管理,而不是nginx。這樣就需要一個(gè)FastCGI管理,管理我們編寫(xiě)fastcgi程序。本文使用spawn-fcgi作為FastCGI進(jìn)程管理器。
3.1.1. spawn-fcgi
spawn-fcgi是一個(gè)通用的FastCGI進(jìn)程管理器,簡(jiǎn)單小巧,原先是屬于lighttpd的一部分,后來(lái)由于使用比較廣泛,所以就遷移出來(lái)作為獨立項目了。spawn-fcgi使用pre-fork 模型,功能主要是打開(kāi)監聽(tīng)端口,綁定地址,然后fork-and-exec創(chuàng )建我們編寫(xiě)的fastcgi應用程序進(jìn)程,退出完成工作。fastcgi應用程序初始化,然后進(jìn)入死循環(huán)偵聽(tīng)socket的連接請求。
安裝spawn-fcgi:
l 獲取spawn-fcgi編譯安裝包,在
http://redmine.lighttpd.net/projects/spawn-fcgi/wiki上可以獲取當前最新的版本。
l 解壓縮spawn-fcgi-x.x.x.tar.gz包。
l 進(jìn)入解壓縮目錄,執行./configure。
l make & make install
如果遇到以下錯誤:“ ./autogen.sh: x: autoreconf: not found”,因為沒(méi)有安裝automake 工具,ubuntu用下面的命令安裝好就可以了:sudo apt-get install autoconf automake libtool 。
spawn-fcgi的幫助信息可以通過(guò)man spawn-fcgi或spawn-fcgi –h獲得,下面是部分常用spawn-fcgi參數信息:
-f <fcgiapp> 指定調用FastCGI的進(jìn)程的執行程序位置
-a <addr> 綁定到地址addr。
-p <port> 綁定到端口port。
-s <path> 綁定到unix domain socket
-C <childs> 指定產(chǎn)生的FastCGI的進(jìn)程數,默認為5。(僅用于PHP)
-P <path> 指定產(chǎn)生的進(jìn)程的PID文件路徑。
-F <childs> 指定產(chǎn)生的FastCGI的進(jìn)程數(C的CGI用這個(gè))
-u和-g FastCGI使用什么身份(-u 用戶(hù) -g 用戶(hù)組)運行,CentOS下可以使用apache用戶(hù),其他的根據情況配置,如nobody、www-data等。
3.1.2. 編寫(xiě)fastcgi應用程序
使用C/C++編寫(xiě)fastcgi應用程序,可以使用FastCGI軟件開(kāi)發(fā)套件或者其它開(kāi)發(fā)框架,如fastcgi++。
本文使用FastCGI軟件開(kāi)發(fā)套件——fcgi(
http://www.fastcgi.com/drupal/node/6?q=node/21),通過(guò)此套件可以輕松編寫(xiě)fastcgi應用程序,安裝fcgi:
l 獲取fcgi編譯安裝包,在
http://www.fastcgi.com/drupal/node/5上可以獲取當前最新的版本。
l 解壓縮fcgi-x.x.x.tar.gz包。
l 進(jìn)入解壓縮目錄,執行./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>,然后再編譯安裝就通過(guò)了。
如果提示找不到動(dòng)態(tài)庫,請在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/目錄
通過(guò)spawn-fcgi啟動(dòng)c/c++編寫(xiě)好的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配置
關(guān)于nginx的幾個(gè)配置文件解析,可以參閱《
Nginx安裝與使用》
http://www.cnblogs.com/skynet/p/4146083.html,在上篇的nginx.conf基礎上增加下面的fastcgi配置。
這樣nginx收到
http://localhost/demo.cgi請求時(shí),會(huì )匹配到location = /demo.cgi塊,將請求傳到后端的fastcgi應用程序處理。如下如所示:(注意其中number為80,是因為我請求了80次)
3.2. nginx + cgi
nginx 不能直接執行外部可執行程序,并且cgi是接收到請求時(shí)才會(huì )啟動(dòng)cgi進(jìn)程,不像fastcgi會(huì )在一開(kāi)就啟動(dòng)好,這樣nginx天生是不支持 cgi 的。nginx 雖然不支持cgi,但它支持 fastCGI。所以,我們可以考慮使用fastcgi包裝來(lái)支持 cgi。原理大致如下圖所示:pre-fork幾個(gè)通用的代理fastcgi程序——fastcgi-wrapper,fastcgi-wrapper啟動(dòng)執行cgi然后將cgi的執行結果返回給nginx(fork-and-exec)。
明白原理之后,編寫(xiě)一個(gè)fastcgi-warpper也比較簡(jiǎn)單。網(wǎng)上流傳比較多的一個(gè)解決方案是,來(lái)自nginx wiki(
http://wiki.nginx.org/SimpleCGI)上的使用perl的fastcgi包裝腳本cgiwrap-fcgi.pl。但我對perl不是很感冒,下面給出一個(gè)C/C++寫(xiě)的fastcgi-wrapper。
3.2.1. fastcgi-wrapper
其實(shí)編寫(xiě)C/C++的fastcgi-wrapper,就是寫(xiě)一個(gè)C/C++的fastcgi,步驟和原理跟前面的小節(nginx+fastcgi)一樣。github上已經(jīng)有人開(kāi)源了,C寫(xiě)的fastcgi-wrapper:
https://github.com/gnosek/fcgiwrap。
安裝fcgiwrap:
l 下載(
https://github.com/gnosek/fcgiwrap.git)
l 解壓縮fcgiwrap,進(jìn)入解壓目錄
l autoreconf -i
l ./configure
l make && make install
啟動(dòng)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請求都會(huì )走到fcgiwrap,然后fcgiwrap會(huì )執行cgi-bin目錄下的cgi程序。
3.2.3. 編寫(xiě)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,每次都是一個(gè)新的進(jìn)程。
參考鏈接
l CGI,
http://www.dwz.cn/yFFgQl fastcgi,
http://www.dwz.cn/yFMapl spawn-fcgi,
http://redmine.lighttpd.net/projects/spawn-fcgi/wikil fcgi,
http://www.fastcgi.com/drupal/node/6?q=node/21l fcgiwrap,
https://github.com/gnosek/fcgiwrap.git