CGI
通用網關接口(Common Gateway Interface)是一個Web服務器主機提供信息服務的標準接口。通過CGI接口,Web服務器就能夠獲取客戶端提交的信息,轉交給服務器端的CGI程序進行處理,最后返回結果給客戶端。
拿nginx、php這種模式來簡單理解cgi更為直觀:
nginx:“哎呀,收到客戶端的一個http請求,該干活了......咦,有php-fpm這小子的活兒!”
nginx:“別睡了,別睡了,php-fpm你該起來干活兒了...”
php-fpm:“好滴,把客戶端的http請求消息體給我一份啊......”
php-fpm:“nginx,我的活兒干完了,接收我要發給客戶端的數據,麻溜的...”
nginx:“好滴,合作愉快”
-------------
Nginx接收到php-fpm處理的結果后,就可以響應客戶端的http請求給予一個回應了,客戶端的這一次http請求就結束了,一張由php產生的華麗麗的網頁就呈現在網民的面前。在這段對話中,nginx與php-fpm并沒有相互推諉扯皮,交流的很順暢;沒有推諉扯皮的原因就是nginx與php-fpm之間的數據和消息傳遞使用了統一的標準格式,這個標準格式就是CGI,所以倘若nginx和php-fpm中有任何一方不按CGI標準來玩,你推諉扯皮也沒用。
發展到現在,對CGI的理解可以是一種標準接口(協議規范),也可以理解成處理動態網頁的某種語言,比如:php、asp都可以寬泛的看做是一種cgi,這個時候cgi就被泛化了但依然包含了不推諉扯皮的交流標準的這一層含義。
FastCGI
FastCGI的Fast已經表明含義了,是一種快速的CGI,也是現代動態網頁語言與web server之間普遍所采用的。FastCGI像是一個常駐型的CGI,它可以一直執行著,只要激活后,不會每次都要花費時間去fork一次(這是CGI最為人詬病的fork-and-execute 模式)。它還支持分布式的運算,即FastCGI程序可以在網站服務器以外的主機上執行并且接受來自其它網站服務器來的請求。
nginx與php-fpm就是采用的FastCGI模式。
PATHINFO
常常會見到這種格式的Urlhttp://blog.jjonline.cn/index.php/Article/Post/index.html ,這種Url理解有兩種方式:
1. index.php當做一個目錄看待:訪問blog.jjonline.cn服務器根目錄下的index.php目錄下的Article目錄下的Post目錄下的index.html靜態html文本文件;
2. index.php當做一個PHP腳本看待:訪問blog.jjonline.cn服務器根目錄下的index.php腳本,由該腳本產生html頁面,Url中/Article/Post/index.html這一部分作為index.php腳本中使用的某種類型的參數。
絕大部分情況下,這種格式的Url理解方式是第二種,而/Article/Post/index.html這一部分理解成PATHINFO就好了。其實PATHINFO是一個CGI 1.1的一個標準,經常用來做為傳參載體,只不過咱們沒必要深入。
由于Apache的默認配置文件開啟了PATHINFO的支持,Apache+PHP的環境下PATHINFO格式的Url可以不出任何錯誤的執行正確路徑的PHP腳本并在腳本中使用PATHINFO中的參數。而Nginx默認提供的有關執行php-fpm運行PHP腳本的默認配置文件中并沒有啟用PATHINFO,從而導致了一個長久以來的誤解:nginx不支持pathinfo。
早期版本的nginx確實不能直接支持pathinfo,但有變相的解決方法,網絡上的一些配置nginx支持pathinfo的文章大多就是這種變相解決方法。nginx其實早已可以很簡單的通過fastcgi_split_path_info指令支持pathinfo模式了,嚴格來說是nginx的0.7.31以上版本就可以使用這個指令了。
Nginx的PATHINFO配置
1、關于nginx配置指令的一些墨跡內容
默認的nginx是對http請求的uri進行正則匹配來決定這個請求是否要交給php-fpm來執行;nginx中有關是否要交給php-fpm這個cgi來解析執行某個php腳本的默認配置(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$這段是一個正則匹配,被匹配的內容是http請求的uri,正則表達式就是\.php$,而~則是nginx的location指令中的一個標記符,表示這個location匹配uri采用正則表達式來匹配;在這里URI和URL還是有區別,請厘清。正則表達式中$表示必須以某個字符或字符串結尾,這樣上述默認配置中僅能匹配到以.php為結尾的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中location匹配的路徑是uri,也就是虛擬路徑部分,本例也就是:/admin/index.php
3、http://blog.jjonline.cn/admin/index.php/Index/index 不匹配,pathinfo模式,nginx將index.php理解成一個目錄了,這種情況下的uri為:/admin/index.php/Index/index ,結尾并沒有.php這種條件
正確配置Nginx對php的pathinfo支持,先要理解清楚nginx配置文件中是如何將某個請求交給php-fpm來執行的,以上述配置段為例來分析一下:
root:這個指令配置了php腳本的根目錄,可以使用相對路徑也可以使用絕對路徑,上述示例中是html,表示php的根目錄在nginx安裝目錄下的html目錄;這里的目錄一般與nginx配置文件server段下的root目錄一致,也就是web服務器的根目錄;且大多數的時候建議使用絕對地址。假設這里的root設置為:/var/www/www.jjonline.cn/wwwRoot,這樣網站根目錄的絕對地址就是/var/www/www.jjonline.cn/wwwRoot,配合各種ftp服務器端配置,將ftp登錄的家目錄設定為/var/www/www.jjonline.cn。拿ThinkPHP來舉例:框架和核心模塊文件可以放置在/var/www/www.jjonline.cn目錄下,而入口文件放置在/var/www/www.jjonline.cn/wwwRoot下;這樣框架和核心模塊文件就不會被Url直接訪問到。
fastcgi_pass:這個指令配置了fastcgi監聽的端口,可以是TCP也可以是unix socket,這里一般推薦走TCP,這個TCP是由php-fpm配置文件決定的,不再詳細介紹。
fastcgi_index:這個指令配置了fastcgi的默認索引文件,與server端下index指令類似。
fastcgi_param:這個指令配置了fastcgi的一些參數,傳遞給php-fpm,這個指令是3段式,第一段fastcgi_param指令名稱,第二段傳遞給php-fpm的參數的名稱,第三段傳遞給php-fpm參數的值,也就是說fastcgi_param配置了一系列的key-value類型的值;對PHP來說fastcgi_param指令產生的key-value鍵值對最后都(未確認,暫時這么理解吧~)轉換成了超全局數組變量$_SERVER的鍵值對,上述示例中fastcgi_param? SCRIPT_FILENAME? /scripts$fastcgi_script_name就配置了一個SCRIPT_FILENAME的fastcgi參數,轉換成PHP中的變量就是$_SERVER['SCRIPT_FILENAME'] ,PHP參考手冊中對$_SERVER['SCRIPT_FILENAME']的說明是:“當前執行腳本的絕對路徑”。對nginx來說,將請求正確的交給php-fpm來執行正確的php腳本就是由fastcgi_param指令配置的SCRIPT_FILENAME來決定的,所以nginx能默契的與php-fpm協作,fastcgi_param指令正確的配置了SCRIPT_FILENAME值是關鍵。
include:這個指令將指定的文本文件的內容作為配置項包含進來,與php中的include差不多意思,這個指令的參數就是一個配置文件的路徑,可以是相對路徑也可以是絕對路徑,路徑中可以使用通配符*;nginx的虛擬主機實現就使用到了這個指令,以及指令參數中使用到通配符。include fastcgi_params; 則表示將主配置文件目錄下的fastcgi_params文本文件中的配置內容包含進來。讀取fastcgi_params文本文件,可以發現這個文件中的文本內容如下:
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;
可以發現包含進來的fastcgi_params文件依然使用了fastcgi_param指令,配置了一大堆鍵值對,拿fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;來簡單分析下:SERVER_SOFTWARE與$_SERVER['SERVER_SOFTWARE']對應,做后臺管理系統常常會用到這個變量來顯示服務器使用的軟件,在php代碼中讀取出來的值就是nginx中這個地方配置的,這個時候PHP中$_SERVER['SERVER_SOFTWARE']讀取出來的內容就是諸如nginx/1.8.0這樣的字符串,這段nginx的配置中$nginx_version是nginx提供的一個變量,變量內容就是nginx版本號。
另外fastcgi_params文件與fastcgi.conf的內容是一摸一樣的,任意包含一個即可,為什么會有兩個一摸一樣的呢?這是nginx的開發者為不同操作系統平臺提供的,無需深究。
2、nginx支持pathinfo的本質和配置實現
依據上述第1條的墨跡得出兩個結論:
1、nginx需要正確將請求交給php-fpm來執行php腳本,nginx先得正確分析出URI中是否要去請求某個PHP腳本;
2、當php-fpm正確執行某個PHP腳本后,PHP中pathinfo模式實現單一入口需要PHP中$_SERVER['PATH_INFO']包含了正確的pathinfo值;而PHP中的$_SERVER變量由nginx的fastcgi_param指令來決定;
所以讓nginx支持pathinfo的配置中要修改內容也圍繞這個兩個點來展開。
第一、nginx的location能匹配到pathinfo格式的URI,去掉URI必須是.php結尾的限定,修改如下:
location? ~? \.php? {
}
第二、需要將URI進行正則切割,產生正確的PHP腳本文件路徑和pathinfo值;
nginx的0.7.31以上版本以后就可以使用fastcgi_split_path_info指令了,這個指令的參數為一個正則表達式,這個正則表示必須有兩個捕獲子組,從左往右捕獲的第一子組自動賦值給nginx的$fastcgi_script_name變量,第二個捕獲的子組自動賦值給nginx的$fastcgi_path_info變量。
通常情況下,也就是在沒有使用fastcgi_split_path_info指令時nginx的$fastcgi_script_name變量保存著相對PHP腳本的URI,這個URI相對于web根目錄就是實際PHP腳本的路徑,所以下方的關于SCRIPT_FILENAME的配置很常見。
fastcgi_param? SCRIPT_FILENAME $document_root$fastcgi_script_name;
這樣在高版本的nginx支持php的pathinfo配置就出來了,這種方式是正規且推薦的:其原理就是nginx正則分析好需要執行的PHP腳本路徑和PATH_INFO變量。
##匹配nginx需要交給php-fpm執行的URI,先要允許pathinfo格式的URL能夠被匹配到
##所以要去掉$
##nginx文檔中的匹配規則為:^(.+\.php)(.*)$
##還有~ \.php這種寫法 和 ~ \.php($|/)這種寫法
##都是差不多意思沒啥嚴格區別
##唯一區別就是有多個匹配php的location的話需要留意權重差異
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兩個變量
? ? ##即$fastcgi_script_name 和$fastcgi_path_info
? ? fastcgi_split_path_info? ^(.+\.php)(.*)$;
? ? ##PHP中要能讀取到pathinfo這個變量
? ? ##就要通過fastcgi_param指令將fastcgi_split_path_info指令匹配到的pathinfo部分賦值給PATH_INFO
? ? ##這樣PHP中$_SERVER['PATH_INFO']才會存在值
? ? fastcgi_param PATH_INFO $fastcgi_path_info;
? ? ##在將這個請求的URI匹配完畢后,檢查這個絕對地址的PHP腳本文件是否存在
? ? ##如果這個PHP腳本文件不存在就不用交給php-fpm來執行了
? ? ##否者頁面將出現由php-fpm返回的:`File not found.`的提示
? ? if (!-e $document_root$fastcgi_script_name) {
? ? ? ? ##此處直接返回404錯誤
? ? ? ? ##你也可以rewrite 到新地址去,然后break;
? ? ? ? return 404;
? ? }
? ? fastcgi_param? SCRIPT_FILENAME? $document_root$fastcgi_script_name;
? ? include? ? ? ? fastcgi_params;
}
還有一種讓nignx支持pathinfo的方式,這種方式需要PHP配置文件php.ini中開啟cgi.fix_pathinfo配置項,賦值為1(php.ini中這個配置項的默認值就是1),早前這個配置項導致一個php任意文件解析的漏洞,見此:http://www.laruence.com/2010/05/20/1495.html,不過現在這個漏洞早已堵上,在我本機上測試,php-fpm將會直接返回403狀態碼和Access denied.的文字。
location ~ .php {
? ? ? root? ? ? ? ? /var/www/www.jjonline.cn/wwwRoot;
? ? ? fastcgi_pass? 127.0.0.1:9000;
? ? ? fastcgi_index? index.php;
? ? ? ##先加載默認的fastcgi配置項
? ? ? include fastcgi_params;
? ? ? ##直接將網站根目錄和完整的URI拼接起來后賦值給SCRIPT_FILENAME
? ? ? ##實際上此處賦值給SCRIPT_FILENAME的PHP腳本文件可能并不存在
? ? ? ##此處賦的值可能是/var/www/www.jjonline.cn/index.php/Index/index形式
? ? ? fastcgi_param? SCRIPT_FILENAME $document_root$fastcgi_script_name;
? ? ? ##同時將完整的URI賦值給PATH_INFO,此處賦的值可能是/index.php/Index/index形式
? ? ? fastcgi_param PATH_INFO $fastcgi_script_name;
}
由于php配置文件php.ini中的cgi.fix_pathinfo配置項處于開啟狀態,php-fpm接收到這些有問題的SCRIPT_FILENAME和PATH_INFO后會內部自動修正,所以這種情況在PHP代碼中$_SERVER['SCRIPT_FILENAME']和$_SERVER['PATH_INFO']是可以正確的修正解析的,這樣配置nginx相當于把URI匹配出正確的SCRIPT_FILENAME和PATH_INFO值交給了php-fpm來執行,這種情況下你會發現PHP中存在$_SERVER['ORIG_SCRIPT_FILENAME']和$_SERVER['ORIG_PATH_INFO']這兩個變量,或許還存在$_SERVER['ORIG_SCRIPT_NAME']。
最后將不再推薦的配置方式貼出來,貼出來的目的是分析下配置原理,加深nginx的配置指令理解
##因為nginx中$fastcgi_script_name內建變量無法賦值
##所有通過設置$real_script_name這個自定義nginx變量來做中間值
location ~ \.php {
root? ? ? ? ? /var/www/www.jjonline.cn/wwwRoot;
? ? ? ? fastcgi_pass? 127.0.0.1:9000;
? ? ? ? fastcgi_index? index.php;
##先加載默認的fastcgi配置項
include? ? ? ? fastcgi_params;
##正則解析路徑,先使用set指令產生兩個nginx變量并賦值
##此處先將$path_info值賦值為空
set $path_info "";
set $real_script_name $fastcgi_script_name;
##正則匹配URI,若能匹配將產生兩個子組
if ($fastcgi_script_name ~ "^(.+?\.php)(/.+)$") {
? ? ##將兩個子組賦值給剛生成的兩個nginx變量
? ? set $real_script_name $1;
? ? set $path_info $2;
}
##將可能匹配到的$path_info值通過fastcgi_param指令設置進去
fastcgi_param PATH_INFO? ? ? $path_info;
fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
##覆蓋fastcgi_params文件中默認的SCRIPT_NAME配置項
fastcgi_param SCRIPT_NAME? ? $real_script_name;
}