最近升級PHP到PHP7版本,並重新部署了新的Nginx,啓動的時候發現了一個問題,全局變量$_SERVER['PHP_SELF']的值發生了改變,從而影響到代碼的功能。因此我們來了解下$_SERVER全局變量中的PHP_SELF/PATH_INFO/SCRIPT_NAME等參數以及其關係。
CGI 1.1規範
之前的文章 [ php-fpm進程數管理 ] 已經簡單説過CGI的內容,這裏我們再詳細講一下。
CGI是Common Gateway Interface(通用網管協議),用於讓交互程序和Web服務器通信的協議。它負責處理URL的請求,啓動一個進程,將客户端發送的數據作為輸入,由Web服務器收集程序的輸出並加上合適的頭部,再發送回客户端。
FastCGI是基於CGI的增強版本的協議,不同於創建新的進程來服務請求,使用持續的進程和創建的子進程來處理一連串的進程,這些進程由FastCGI服務器管理,開銷更小,效率更高。
CGI誕生於1993年美國國家計算機中心,目的是為不同的動態頁面處理語言(php/python/java)在不同的服務器下(apache/nginx)提供一致的接口規範,提供會話環境變量、會話客户端等信息。
在RFC-CGI1.1文檔中包含了協議的全部內容,我們現在只關注它的 4.1節:Request Meta-Variables 。
標準中定義了處理請求應該實現的17個屬性和如何自定義新屬性,比如:
SERVER_PROTOCOL:信息協議的名字和修訂版。格式為protocol/reVision。SERVER_PORT:發送請求的端口號。REQUEST_METHOD:請求的方法。對於HTTP,有"GET"、 "HEAD"、 "POST"等等。PATH_INFO:額外的路徑信息,由客户端給出的。換句話説,腳本可以由他們的虛擬路徑名來訪問,在這個路徑的末尾附帶額外的信息。這個額外信息被作為PATH_INFO發送。這個信息如果在傳遞給CGI腳本之前來自URL就可以由服務器來解碼。PATH_TRANSLATED:服務器提供了一個PATH_INFO的轉換版本,它需要路徑並且為它做虛擬到物理的映射。SCRIPT_NAME:將要執行的腳本的一個虛擬路徑。QUERY_STRING:在引用腳本的URL中緊跟在?之後的信息。這是一個查詢信息。它不能以任何方式來解碼。這個變量總是可以在有查詢信息的時候被設置,而不管命令行解碼。REMOTE_HOST:產生請求的主機名。如果服務器沒有這個信息,它應該設置REMOTE_ADDR並且讓這個為未設置狀態。REMOTE_ADDR:產生請求的遠程主機的IP地址。AUTH_TYPE:如果服務器支持用户驗證,腳本就受保護。這是一個協議規範授權方法,用於驗證用户。REMOTE_USER:如果服務器支持用户驗證,腳本就受保護。這是他們授權的用户名。REMOTE_IDENT:如果HTTP服務器支持RFC931認證,這個變量將被設置為從服務器取出的遠程用户名。這個變量的用法應該只限制在登陸的時候。CONTENT_TYPE:對於哪些已經附上信息的請求,比如HTTP POST和PUT,這是數據的內容類型。CONTENT_LENGTH:客户端給的數據內容的長度。
這些變量需要各個語言和服務器進行自己的實現,同時他們也會有自己定義的一些變量。如我們今天要説的PHP語言中的$_SERVER['PHP_SELF']變量。
PHP的超全局變量$_SERVER
$_SERVER是一個包含了諸如頭信息(header)、路徑(path)、以及腳本位置(script locations)等等信息的數組。這個數組中的項目由Web服務器創建。不能保證每個服務器都提供全部項目;服務器可能會忽略一些,或者提供一些沒有在這裏列舉出來的項目。這也就意味着大量的此類變量都會在» CGI 1.1規範中説明,所以應該仔細研究一下。
__FILE__ 常量包含當前(例如包含)文件的完整路徑和文件名。
與此相關的,我們這裏主要關注的幾個變量是:
PHP_SELF: 當前執行腳本的文件名,與document root有關。例如,在地址為http://example.com/foo/bar.php的腳本中值為/foo/bar.php。SCRIPT_NAME: 包含當前腳本的路徑。這在頁面需要指向自己時非常有用。PATH_INFO: 包含由客户端提供的、跟在真實腳本名稱之後並且在查詢語句(query string)之前的路徑信息,如果存在的話。例如,如果當前腳本是通過URL http://www.example.com/php/path_info.php/some/stuff?foo=bar被訪問,那麼值為/some/stuff。
文檔裏表述的Web服務器,在我的環境裏指代的是Nginx。在Apache中,當不加配置的時候對於PHP腳本, AcceptPathInfo是默認接受的。而對於Nginx下, 是不支持PATH INFO的, 也就是它不會默認設置PATH_INFO.
因此,對於一個Nginx架構的常規請求來説,這幾個字段的值分別是:
# http://www.baidu.com:8080/odp/index.php?r=update
PHP_SELF: /odp/index.php
SCRIPT_NAME: /odp/index.php
PATH_INFO: null
問題:PHP_SELF中出現重複路徑
在我部署完成新的Nginx服務後,得到的上面三個字段的值為:
# http://www.baidu.com:8080/odp/index.php?r=update
PHP_SELF: /odp/index.php/odp/index.php
SCRIPT_NAME: /odp/index.php
PATH_INFO: /odp/index.php
注意這裏的PHP_SELF字段存在重複的路徑,而PATH_INFO也存在了值,此時的nginx.conf配置為:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
# 注意這一行,我們配置了PATH_INFO字段
fastcgi_param PATH_INFO $fastcgi_script_name;
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;
那麼我們為什麼配置了PATH_INFO就會影響PHP_SELF的值了呢?這一點,我們首先會想到PHP_SELF這個自定義屬性的來源是什麼,然而,我並沒有找到任何的文檔説明。但我們可以通過重命名的方式,來探究一下它的定義:
fastcgi_param PATH_INFO PATH_INFO;
# fastcgi_param PATH_INFO $fastcgi_script_name;
fastcgi_param SCRIPT_NAME SCRIPT_NAME;
# fastcgi_param SCRIPT_NAME $fastcgi_script_name;
變更這兩行,我們將其重命名為指定字符串,而不是請求傳入的變量,nginx reload後,此時的結果是:
# http://www.baidu.com:8080/odp/index.php?r=update
PHP_SELF: SCRIPT_NAMEPATH_INFO
SCRIPT_NAME: SCRIPT_NAME
PATH_INFO: PATH_INFO
而其他變量均正常,因此我們可以進一步理解:
PHP_SELF = SCRIPT_NAME + PATH_INFO
自定義變量:PHP_SELF
那麼PHP為什麼要自定義這個屬性呢?在官方文檔裏有這麼一個url請求,此時:
# http://www.example.com/php/path_info.php/some/stuff?foo=bar
PHP_SELF: /php/path_info.php/some/stuff
SCRIPT_NAME: /php/path_info.php
PATH_INFO: /some/stuff
所以,在這種場景下,只有PHP_SELF才能拿到完整的當前執行腳本的文件或路徑。
總結
為了不同服務器、不同語言之間的請求通信,於是有了CGI協議規範,這個規範在不同的服務器和語言中有自己的實現,在Web Server: Nginx的配置文件中,可以設置不同變量的值,解析後傳遞給PHP-FPM(PHP-FastCGI Process Manager),再進一步傳遞給負責響應請求的PHP子進程,而PHP中也定義了關於請求通信的全局變量$_SERVER,用於解析請求和處理邏輯。這就是整個關於解析請求信息的流程。
由於PHP中$_SERVER中的這幾個變量的定義有一定混淆,也依賴於不同的實現和Server環境,如PATH_INFO在Nginx/Apache中的不同默認狀態,因此,如果需要頁面指向自己時,除非如上面示例中的那種url,建議使用SCRIPT_NAME變量即可。
參考資料
- segmentfault-php-fpm進程數管理: https://segmentfault.com/a/11...
- RFC-CGI1.1: https://tools.ietf.org/html/r...
- CGI規範及其歷史:http://www.voidcn.com/article...
- php關於$_SERVER中一些和環境有關的參數詳解: https://www.jianshu.com/p/fea...
- PHP文檔-$_SERVER:http://php.net/manual/zh/rese...