Nginx + CGI/FastCGI + C/Cpp

http://www.cnblogs.com/skynet/p/4173450.html

Nginx + CGI/FastCGI + C/Cpp

2014-12-19 11:05 by 吳秦, 19794 閱讀, 6 評論, 收藏編輯html

接着上篇《Nginx安裝與使用》,本篇介紹CGI/FASTCGI的原理、及如何使用C/C++編寫簡單的CGI/FastCGI,最後將CGI/FASTCGI部署到nginx。內容大綱以下:node

1.     CGInginx

1.1.     環境變量c++

1.2.     標準輸入git

2.     FastCGIgithub

3. nginx cgi/fastcgiweb

3.1. nginx + fastcgishell

3.1.1. spawn-fcgi數據庫

3.1.2. 編寫fastcgi應用程序apache

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

通用網關接口Common Gateway Interface/CGI描述了客戶端和服務器程序之間傳輸數據的一種標準,可讓一個客戶端,從網頁瀏覽器向執行在網絡服務器上的程序請求數據。CGI 獨立於任何語言的,CGI 程序能夠用任何腳本語言或者是徹底獨立編程語言實現,只要這個語言能夠在這個系統上運行。Unix shell script, PythonRubyPHP, perl, TclC/C++,和 Visual Basic 均可以用來編寫 CGI 程序。(http://www.dwz.cn/yFFgQ)

最初,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中獲取數據。常見的環境變量以下表所示:

環境變數

內容

AUTH_TYPE

存取認證類型。

CONTENT_LENGTH

由標準輸入傳遞給CGI程序的數據長度,以bytes或字元數來計算。

CONTENT_TYPE

請求的MIME類型。

GATEWAY_INTERFACE

服務器的CGI版本編號。

HTTP_ACCEPT

瀏覽器能直接接收的Content-types, 能夠有HTTP Accept header定義.

HTTP_USER_AGENT

遞交表單的瀏覽器的名稱、版本 和其餘平臺性的附加信息。

HTTP_REFERER

遞交表單的文本的 URL,不是全部的瀏覽器都發出這個信息,不要依賴它

PATH_INFO

傳遞給cgi程式的路徑信息。

QUERY_STRING

傳遞給CGI程式的請求參數,也就是用"?"隔開,添加在URL後面的字串。

REMOTE_ADDR

client端的host名稱。

REMOTE_HOST

client端的IP位址。

REMOTE_USER

client端送出來的使用者名稱。

REMOTE_METHOD

client端發出請求的方法(如get、post)。

SCRIPT_NAME

CGI程式所在的虛擬路徑,如/cgi-bin/echo。

SERVER_NAME

server的host名稱或IP地址。

SERVER_PORT

收到request的server端口。

SERVER_PROTOCOL

所使用的通信協定和版本編號。

SERVER_SOFTWARE

server程序的名稱和版本。

1.2.標準輸入

環境變量的大小是有必定的限制的,當須要傳送的數據量大時,儲存環境變量的空間可能會不足,形成數據接收不徹底,甚至沒法執行CGI程序。所以後來又發展出另一種方法:POST,也就是利用I/O從新導向的技巧,讓CGI程序能夠由STDIN和STDOUT直接跟瀏覽器溝通。
當咱們指定用這種方法傳遞請求的數據時,web 服務器收到數據後會先放在一塊輸入緩衝區中,而且將數據的大小記錄在CONTENT_LENGTH這個環境變數,而後調用CGI程式並將CGI程序的STDIN指向這塊緩衝區,因而咱們就能夠很順利的經過STDIN和環境變數CONTENT_LENGTH獲得全部的資料,再沒有資料大小的限制了。
 

總結:CGI使外部程序與Web服務器之間交互成爲可能。CGI程式運行在獨立的進程中,並對每一個Web請求創建一個進程,這種方法很是容易實現,但效率不好,難以擴展。面對大量請求,進程的大量創建和消亡使操做系統性能大大降低。此外,因爲地址空間沒法共享,也限制了資源重用。

2.FastCGI

快速通用網關接口(Fast Common Gateway Interface/FastCGI)是通用網關接口(CGI)的改進,描述了客戶端和服務器程序之間傳輸數據的一種標準。FastCGI致力於減小Web服務器CGI程式之間互動的開銷,從而使服務器能夠同時處理更多的Web請求。與爲每一個請求建立一個新的進程不一樣,FastCGI使用持續的進程來處理一連串的請求。這些進程由FastCGI進程管理器管理,而不是web服務器。(http://www.dwz.cn/yFMap)

 

當進來一個請求時,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/fastcgi

nginx 不能像apache那樣直接執行外部可執行程序,但nginx能夠做爲代理服務器,將請求轉發給後端服務器,這也是nginx的主要做用之一。其中nginx就支持FastCGI代理,接收客戶端的請求,而後將請求轉發給後端fastcgi進程。下面介紹如何使用C/C++編寫cgi/fastcgi,並部署到nginx中。

3.1. nginx + fastcgi

經過前面的介紹知道,fastcgi進程由FastCGI進程管理器管理,而不是nginx。這樣就須要一個FastCGI管理,管理咱們編寫fastcgi程序。本文使用spawn-fcgi做爲FastCGI進程管理器。

3.1.1. spawn-fcgi

spawn-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參數信息:

-f <fcgiapp> 指定調用FastCGI的進程的執行程序位置

-a <addr> 綁定到地址addr。

-p <port> 綁定到端口port。

-s <path> 綁定到unix domain socket

-C <childs> 指定產生的FastCGI的進程數,默認爲5。(僅用於PHP)

-P <path> 指定產生的進程的PID文件路徑。

-F <childs> 指定產生的FastCGI的進程數(C的CGI用這個)

-u和-g FastCGI使用什麼身份(-u 用戶 -g 用戶組)運行,CentOS下可使用apache用戶,其餘的根據狀況配置,如nobody、www-data等。

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 + cgi

nginx 不能直接執行外部可執行程序,而且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,每次都是一個新的進程。

相關文章
相關標籤/搜索