CGI
通用網(wǎng)關(guān)接口(Common Gateway Interface)是一個(gè)Web服務(wù)器主機(jī)提供信息服務(wù)的標(biāo)準(zhǔn)接口宴凉。通過CGI接口晰房,Web服務(wù)器就能夠獲取客戶端提交的信息,轉(zhuǎn)交給服務(wù)器端的CGI程序進(jìn)行處理荧恍,最后返回結(jié)果給客戶端伸刃。
拿nginx、php這種模式來簡單理解cgi更為直觀:
nginx:“哎呀樟插,收到客戶端的一個(gè)http請(qǐng)求结澄,該干活了......咦,有php-fpm這小子的活兒岸夯!”
nginx:“別睡了麻献,別睡了,php-fpm你該起來干活兒了...”
php-fpm:“好滴猜扮,把客戶端的http請(qǐng)求消息體給我一份啊......”
php-fpm:“nginx勉吻,我的活兒干完了,接收我要發(fā)給客戶端的數(shù)據(jù)旅赢,麻溜的...”
nginx:“好滴齿桃,合作愉快”
-------------
Nginx接收到php-fpm處理的結(jié)果后,就可以響應(yīng)客戶端的http請(qǐng)求給予一個(gè)回應(yīng)了煮盼,客戶端的這一次http請(qǐng)求就結(jié)束了短纵,一張由php產(chǎn)生的華麗麗的網(wǎng)頁就呈現(xiàn)在網(wǎng)民的面前。在這段對(duì)話中僵控,nginx與php-fpm并沒有相互推諉扯皮香到,交流的很順暢;沒有推諉扯皮的原因就是nginx與php-fpm之間的數(shù)據(jù)和消息傳遞使用了統(tǒng)一的標(biāo)準(zhǔn)格式报破,這個(gè)標(biāo)準(zhǔn)格式就是CGI悠就,所以倘若nginx和php-fpm中有任何一方不按CGI標(biāo)準(zhǔn)來玩,你推諉扯皮也沒用充易。
發(fā)展到現(xiàn)在梗脾,對(duì)CGI的理解可以是一種標(biāo)準(zhǔn)接口(協(xié)議規(guī)范),也可以理解成處理動(dòng)態(tài)網(wǎng)頁的某種語言盹靴,比如:php炸茧、asp都可以寬泛的看做是一種cgi,這個(gè)時(shí)候cgi就被泛化了但依然包含了不推諉扯皮的交流標(biāo)準(zhǔn)的這一層含義稿静。
FastCGI
FastCGI的Fast已經(jīng)表明含義了梭冠,是一種快速的CGI,也是現(xiàn)代動(dòng)態(tài)網(wǎng)頁語言與web server之間普遍所采用的自赔。FastCGI像是一個(gè)常駐型的CGI妈嘹,它可以一直執(zhí)行著,只要激活后绍妨,不會(huì)每次都要花費(fèi)時(shí)間去fork一次(這是CGI最為人詬病的fork-and-execute 模式)润脸。它還支持分布式的運(yùn)算柬脸,即FastCGI程序可以在網(wǎng)站服務(wù)器以外的主機(jī)上執(zhí)行并且接受來自其它網(wǎng)站服務(wù)器來的請(qǐng)求。
nginx與php-fpm就是采用的FastCGI模式毙驯。
PATHINFO
常常會(huì)見到這種格式的Urlhttp://blog.jjonline.cn/index.php/Article/Post/index.html 倒堕,這種Url理解有兩種方式:
1. index.php當(dāng)做一個(gè)目錄看待:訪問blog.jjonline.cn服務(wù)器根目錄下的index.php目錄下的Article目錄下的Post目錄下的index.html靜態(tài)html文本文件;
2. index.php當(dāng)做一個(gè)PHP腳本看待:訪問blog.jjonline.cn服務(wù)器根目錄下的index.php腳本爆价,由該腳本產(chǎn)生html頁面垦巴,Url中/Article/Post/index.html這一部分作為index.php腳本中使用的某種類型的參數(shù)。
絕大部分情況下铭段,這種格式的Url理解方式是第二種骤宣,而/Article/Post/index.html這一部分理解成PATHINFO就好了。其實(shí)PATHINFO是一個(gè)CGI 1.1的一個(gè)標(biāo)準(zhǔn)序愚,經(jīng)常用來做為傳參載體憔披,只不過咱們沒必要深入。
由于Apache的默認(rèn)配置文件開啟了PATHINFO的支持爸吮,Apache+PHP的環(huán)境下PATHINFO格式的Url可以不出任何錯(cuò)誤的執(zhí)行正確路徑的PHP腳本并在腳本中使用PATHINFO中的參數(shù)芬膝。而Nginx默認(rèn)提供的有關(guān)執(zhí)行php-fpm運(yùn)行PHP腳本的默認(rèn)配置文件中并沒有啟用PATHINFO,從而導(dǎo)致了一個(gè)長久以來的誤解:nginx不支持pathinfo形娇。
早期版本的nginx確實(shí)不能直接支持pathinfo锰霜,但有變相的解決方法,網(wǎng)絡(luò)上的一些配置nginx支持pathinfo的文章大多就是這種變相解決方法桐早。nginx其實(shí)早已可以很簡單的通過fastcgi_split_path_info指令支持pathinfo模式了癣缅,嚴(yán)格來說是nginx的0.7.31以上版本就可以使用這個(gè)指令了。
Nginx的PATHINFO配置
1勘畔、關(guān)于nginx配置指令的一些墨跡內(nèi)容
默認(rèn)的nginx是對(duì)http請(qǐng)求的uri進(jìn)行正則匹配來決定這個(gè)請(qǐng)求是否要交給php-fpm來執(zhí)行所灸;nginx中有關(guān)是否要交給php-fpm這個(gè)cgi來解析執(zhí)行某個(gè)php腳本的默認(rèn)配置(nginx1.8.0)如下:
location ~ \.php$ {
? ? ? root? ? ? ? ? html;
? ? ? fastcgi_pass? 127.0.0.1:9000;
? ? ? fastcgi_index? index.php;
? ? ? fastcgi_param? SCRIPT_FILENAME? /scripts$fastcgi_script_name;
? ? ? include? ? ? ? fastcgi_params;
}
上述location ~ \.php$這段是一個(gè)正則匹配,被匹配的內(nèi)容是http請(qǐng)求的uri炫七,正則表達(dá)式就是\.php$,而~則是nginx的location指令中的一個(gè)標(biāo)記符钾唬,表示這個(gè)location匹配uri采用正則表達(dá)式來匹配万哪;在這里URI和URL還是有區(qū)別,請(qǐng)厘清抡秆。正則表達(dá)式中$表示必須以某個(gè)字符或字符串結(jié)尾奕巍,這樣上述默認(rèn)配置中僅能匹配到以.php為結(jié)尾的uri交給php-fpm去解析,如下:
1儒士、http://blog.jjonline.cn/index.php 匹配
2的止、http://blog.jjonline.cn/admin/index.php?m=Index&a=index 匹配,注意這里Url中有Get變量着撩,nginx中l(wèi)ocation匹配的路徑是uri诅福,也就是虛擬路徑部分匾委,本例也就是:/admin/index.php
3、http://blog.jjonline.cn/admin/index.php/Index/index 不匹配氓润,pathinfo模式赂乐,nginx將index.php理解成一個(gè)目錄了,這種情況下的uri為:/admin/index.php/Index/index 咖气,結(jié)尾并沒有.php這種條件
正確配置Nginx對(duì)php的pathinfo支持挨措,先要理解清楚nginx配置文件中是如何將某個(gè)請(qǐng)求交給php-fpm來執(zhí)行的,以上述配置段為例來分析一下:
root:這個(gè)指令配置了php腳本的根目錄崩溪,可以使用相對(duì)路徑也可以使用絕對(duì)路徑浅役,上述示例中是html,表示php的根目錄在nginx安裝目錄下的html目錄伶唯;這里的目錄一般與nginx配置文件server段下的root目錄一致觉既,也就是web服務(wù)器的根目錄;且大多數(shù)的時(shí)候建議使用絕對(duì)地址抵怎。假設(shè)這里的root設(shè)置為:/var/www/www.jjonline.cn/wwwRoot奋救,這樣網(wǎng)站根目錄的絕對(duì)地址就是/var/www/www.jjonline.cn/wwwRoot,配合各種ftp服務(wù)器端配置反惕,將ftp登錄的家目錄設(shè)定為/var/www/www.jjonline.cn尝艘。拿ThinkPHP來舉例:框架和核心模塊文件可以放置在/var/www/www.jjonline.cn目錄下,而入口文件放置在/var/www/www.jjonline.cn/wwwRoot下姿染;這樣框架和核心模塊文件就不會(huì)被Url直接訪問到背亥。
fastcgi_pass:這個(gè)指令配置了fastcgi監(jiān)聽的端口,可以是TCP也可以是unix socket悬赏,這里一般推薦走TCP狡汉,這個(gè)TCP是由php-fpm配置文件決定的,不再詳細(xì)介紹闽颇。
fastcgi_index:這個(gè)指令配置了fastcgi的默認(rèn)索引文件盾戴,與server端下index指令類似。
fastcgi_param:這個(gè)指令配置了fastcgi的一些參數(shù)兵多,傳遞給php-fpm尖啡,這個(gè)指令是3段式,第一段fastcgi_param指令名稱剩膘,第二段傳遞給php-fpm的參數(shù)的名稱衅斩,第三段傳遞給php-fpm參數(shù)的值,也就是說fastcgi_param配置了一系列的key-value類型的值怠褐;對(duì)PHP來說fastcgi_param指令產(chǎn)生的key-value鍵值對(duì)最后都(未確認(rèn)畏梆,暫時(shí)這么理解吧~)轉(zhuǎn)換成了超全局?jǐn)?shù)組變量$_SERVER的鍵值對(duì),上述示例中fastcgi_param? SCRIPT_FILENAME? /scripts$fastcgi_script_name就配置了一個(gè)SCRIPT_FILENAME的fastcgi參數(shù),轉(zhuǎn)換成PHP中的變量就是$_SERVER['SCRIPT_FILENAME'] 奠涌,PHP參考手冊(cè)中對(duì)$_SERVER['SCRIPT_FILENAME']的說明是:“當(dāng)前執(zhí)行腳本的絕對(duì)路徑”宪巨。對(duì)nginx來說,將請(qǐng)求正確的交給php-fpm來執(zhí)行正確的php腳本就是由fastcgi_param指令配置的SCRIPT_FILENAME來決定的铣猩,所以nginx能默契的與php-fpm協(xié)作揖铜,fastcgi_param指令正確的配置了SCRIPT_FILENAME值是關(guān)鍵。
include:這個(gè)指令將指定的文本文件的內(nèi)容作為配置項(xiàng)包含進(jìn)來达皿,與php中的include差不多意思天吓,這個(gè)指令的參數(shù)就是一個(gè)配置文件的路徑,可以是相對(duì)路徑也可以是絕對(duì)路徑峦椰,路徑中可以使用通配符*龄寞;nginx的虛擬主機(jī)實(shí)現(xiàn)就使用到了這個(gè)指令,以及指令參數(shù)中使用到通配符汤功。include fastcgi_params; 則表示將主配置文件目錄下的fastcgi_params文本文件中的配置內(nèi)容包含進(jìn)來物邑。讀取fastcgi_params文本文件,可以發(fā)現(xiàn)這個(gè)文件中的文本內(nèi)容如下:
fastcgi_param? QUERY_STRING? ? ? $query_string;
fastcgi_param? REQUEST_METHOD? ? $request_method;
fastcgi_param? CONTENT_TYPE? ? ? $content_type;
fastcgi_param? CONTENT_LENGTH? ? $content_length;
fastcgi_param? SCRIPT_NAME? ? ? ? $fastcgi_script_name;
fastcgi_param? REQUEST_URI? ? ? ? $request_uri;
fastcgi_param? DOCUMENT_URI? ? ? $document_uri;
fastcgi_param? DOCUMENT_ROOT? ? ? $document_root;
fastcgi_param? SERVER_PROTOCOL? ? $server_protocol;
fastcgi_param? HTTPS? ? ? ? ? ? ? $https if_not_empty;
fastcgi_param? GATEWAY_INTERFACE? CGI/1.1;
fastcgi_param? SERVER_SOFTWARE? ? nginx/$nginx_version;
fastcgi_param? REMOTE_ADDR? ? ? ? $remote_addr;
fastcgi_param? REMOTE_PORT? ? ? ? $remote_port;
fastcgi_param? SERVER_ADDR? ? ? ? $server_addr;
fastcgi_param? SERVER_PORT? ? ? ? $server_port;
fastcgi_param? SERVER_NAME? ? ? ? $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param? REDIRECT_STATUS? ? 200;
可以發(fā)現(xiàn)包含進(jìn)來的fastcgi_params文件依然使用了fastcgi_param指令滔金,配置了一大堆鍵值對(duì)色解,拿fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;來簡單分析下:SERVER_SOFTWARE與$_SERVER['SERVER_SOFTWARE']對(duì)應(yīng),做后臺(tái)管理系統(tǒng)常常會(huì)用到這個(gè)變量來顯示服務(wù)器使用的軟件餐茵,在php代碼中讀取出來的值就是nginx中這個(gè)地方配置的科阎,這個(gè)時(shí)候PHP中$_SERVER['SERVER_SOFTWARE']讀取出來的內(nèi)容就是諸如nginx/1.8.0這樣的字符串,這段nginx的配置中$nginx_version是nginx提供的一個(gè)變量忿族,變量內(nèi)容就是nginx版本號(hào)锣笨。
另外fastcgi_params文件與fastcgi.conf的內(nèi)容是一摸一樣的,任意包含一個(gè)即可道批,為什么會(huì)有兩個(gè)一摸一樣的呢错英?這是nginx的開發(fā)者為不同操作系統(tǒng)平臺(tái)提供的,無需深究隆豹。
2椭岩、nginx支持pathinfo的本質(zhì)和配置實(shí)現(xiàn)
依據(jù)上述第1條的墨跡得出兩個(gè)結(jié)論:
1、nginx需要正確將請(qǐng)求交給php-fpm來執(zhí)行php腳本璃赡,nginx先得正確分析出URI中是否要去請(qǐng)求某個(gè)PHP腳本簿煌;
2、當(dāng)php-fpm正確執(zhí)行某個(gè)PHP腳本后鉴吹,PHP中pathinfo模式實(shí)現(xiàn)單一入口需要PHP中$_SERVER['PATH_INFO']包含了正確的pathinfo值;而PHP中的$_SERVER變量由nginx的fastcgi_param指令來決定惩琉;
所以讓nginx支持pathinfo的配置中要修改內(nèi)容也圍繞這個(gè)兩個(gè)點(diǎn)來展開豆励。
第一、nginx的location能匹配到pathinfo格式的URI,去掉URI必須是.php結(jié)尾的限定良蒸,修改如下:
location? ~? \.php? {
}
第二技扼、需要將URI進(jìn)行正則切割,產(chǎn)生正確的PHP腳本文件路徑和pathinfo值嫩痰;
nginx的0.7.31以上版本以后就可以使用fastcgi_split_path_info指令了剿吻,這個(gè)指令的參數(shù)為一個(gè)正則表達(dá)式,這個(gè)正則表示必須有兩個(gè)捕獲子組串纺,從左往右捕獲的第一子組自動(dòng)賦值給nginx的$fastcgi_script_name變量丽旅,第二個(gè)捕獲的子組自動(dòng)賦值給nginx的$fastcgi_path_info變量。
通常情況下纺棺,也就是在沒有使用fastcgi_split_path_info指令時(shí)nginx的$fastcgi_script_name變量保存著相對(duì)PHP腳本的URI榄笙,這個(gè)URI相對(duì)于web根目錄就是實(shí)際PHP腳本的路徑,所以下方的關(guān)于SCRIPT_FILENAME的配置很常見祷蝌。
fastcgi_param? SCRIPT_FILENAME $document_root$fastcgi_script_name;
這樣在高版本的nginx支持php的pathinfo配置就出來了茅撞,這種方式是正規(guī)且推薦的:其原理就是nginx正則分析好需要執(zhí)行的PHP腳本路徑和PATH_INFO變量。
##匹配nginx需要交給php-fpm執(zhí)行的URI巨朦,先要允許pathinfo格式的URL能夠被匹配到
##所以要去掉$
##nginx文檔中的匹配規(guī)則為:^(.+\.php)(.*)$
##還有~ \.php這種寫法 和 ~ \.php($|/)這種寫法
##都是差不多意思沒啥嚴(yán)格區(qū)別
##唯一區(qū)別就是有多個(gè)匹配php的location的話需要留意權(quán)重差異
location ~ ^(.+\.php)(.*)$ {
? ? ?root? ? ? ? ? ? ? /var/www/www.jjonline.cn/wwwRoot;
? ? fastcgi_pass? 127.0.0.1:9000;
? ? fastcgi_index? index.php;
? ? ##增加 fastcgi_split_path_info指令米丘,將URI匹配成PHP腳本的URI和pathinfo兩個(gè)變量
? ? ##即$fastcgi_script_name 和$fastcgi_path_info
? ? fastcgi_split_path_info? ^(.+\.php)(.*)$;
? ? ##PHP中要能讀取到pathinfo這個(gè)變量
? ? ##就要通過fastcgi_param指令將fastcgi_split_path_info指令匹配到的pathinfo部分賦值給PATH_INFO
? ? ##這樣PHP中$_SERVER['PATH_INFO']才會(huì)存在值
? ? fastcgi_param PATH_INFO $fastcgi_path_info;
? ? ##在將這個(gè)請(qǐng)求的URI匹配完畢后,檢查這個(gè)絕對(duì)地址的PHP腳本文件是否存在
? ? ##如果這個(gè)PHP腳本文件不存在就不用交給php-fpm來執(zhí)行了
? ? ##否者頁面將出現(xiàn)由php-fpm返回的:`File not found.`的提示
? ? if (!-e $document_root$fastcgi_script_name) {
? ? ? ? ##此處直接返回404錯(cuò)誤
? ? ? ? ##你也可以rewrite 到新地址去糊啡,然后break;
? ? ? ? return 404;
? ? }
? ? fastcgi_param? SCRIPT_FILENAME? $document_root$fastcgi_script_name;
? ? include? ? ? ? fastcgi_params;
}
還有一種讓nignx支持pathinfo的方式拄查,這種方式需要PHP配置文件php.ini中開啟cgi.fix_pathinfo配置項(xiàng),賦值為1(php.ini中這個(gè)配置項(xiàng)的默認(rèn)值就是1)悔橄,早前這個(gè)配置項(xiàng)導(dǎo)致一個(gè)php任意文件解析的漏洞靶累,見此:http://www.laruence.com/2010/05/20/1495.html,不過現(xiàn)在這個(gè)漏洞早已堵上癣疟,在我本機(jī)上測試挣柬,php-fpm將會(huì)直接返回403狀態(tài)碼和Access denied.的文字。
location ~ .php {
? ? ? root? ? ? ? ? /var/www/www.jjonline.cn/wwwRoot;
? ? ? fastcgi_pass? 127.0.0.1:9000;
? ? ? fastcgi_index? index.php;
? ? ? ##先加載默認(rèn)的fastcgi配置項(xiàng)
? ? ? include fastcgi_params;
? ? ? ##直接將網(wǎng)站根目錄和完整的URI拼接起來后賦值給SCRIPT_FILENAME
? ? ? ##實(shí)際上此處賦值給SCRIPT_FILENAME的PHP腳本文件可能并不存在
? ? ? ##此處賦的值可能是/var/www/www.jjonline.cn/index.php/Index/index形式
? ? ? fastcgi_param? SCRIPT_FILENAME $document_root$fastcgi_script_name;
? ? ? ##同時(shí)將完整的URI賦值給PATH_INFO睛挚,此處賦的值可能是/index.php/Index/index形式
? ? ? fastcgi_param PATH_INFO $fastcgi_script_name;
}
由于php配置文件php.ini中的cgi.fix_pathinfo配置項(xiàng)處于開啟狀態(tài)邪蛔,php-fpm接收到這些有問題的SCRIPT_FILENAME和PATH_INFO后會(huì)內(nèi)部自動(dòng)修正,所以這種情況在PHP代碼中$_SERVER['SCRIPT_FILENAME']和$_SERVER['PATH_INFO']是可以正確的修正解析的扎狱,這樣配置nginx相當(dāng)于把URI匹配出正確的SCRIPT_FILENAME和PATH_INFO值交給了php-fpm來執(zhí)行侧到,這種情況下你會(huì)發(fā)現(xiàn)PHP中存在$_SERVER['ORIG_SCRIPT_FILENAME']和$_SERVER['ORIG_PATH_INFO']這兩個(gè)變量,或許還存在$_SERVER['ORIG_SCRIPT_NAME']淤击。
最后將不再推薦的配置方式貼出來匠抗,貼出來的目的是分析下配置原理,加深nginx的配置指令理解
##因?yàn)閚ginx中$fastcgi_script_name內(nèi)建變量無法賦值
##所有通過設(shè)置$real_script_name這個(gè)自定義nginx變量來做中間值
location ~ \.php {
root? ? ? ? ? /var/www/www.jjonline.cn/wwwRoot;
? ? ? ? fastcgi_pass? 127.0.0.1:9000;
? ? ? ? fastcgi_index? index.php;
##先加載默認(rèn)的fastcgi配置項(xiàng)
include? ? ? ? fastcgi_params;
##正則解析路徑污抬,先使用set指令產(chǎn)生兩個(gè)nginx變量并賦值
##此處先將$path_info值賦值為空
set $path_info "";
set $real_script_name $fastcgi_script_name;
##正則匹配URI汞贸,若能匹配將產(chǎn)生兩個(gè)子組
if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
? ? ##將兩個(gè)子組賦值給剛生成的兩個(gè)nginx變量
? ? set $real_script_name $1;
? ? set $path_info $2;
}
##將可能匹配到的$path_info值通過fastcgi_param指令設(shè)置進(jìn)去
fastcgi_param PATH_INFO? ? ? $path_info;
fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
##覆蓋fastcgi_params文件中默認(rèn)的SCRIPT_NAME配置項(xiàng)
fastcgi_param SCRIPT_NAME? ? $real_script_name;
}