在上傳php程序文件的時候可能會把apache的.htaccess文件上傳上去 , 這樣會影響nginx的配置 , 建議刪除.htaccess文件在測試。php
ThinkPHP5發佈了,最近也要基於ThinkPHP5作一個小項目,藉着這個機會完全梳理下Nginx下有關pathinfo的配置。本文的宗旨是:遠離咬文嚼字的理論,儘可能的通俗。因此不可避免的會出現遺漏和疏忽,敬請指教~html
通用網關接口(Common Gateway Interface)是一個Web服務器主機提供信息服務的標準接口。經過CGI接口,Web服務器就可以獲取客戶端提交的信息,轉交給服務器端的CGI程序進行處理,最後返回結果給客戶端。nginx
神煩理論瞎,拿nginx、php這種模式來簡單理解cgi更爲直觀:web
-------------正則表達式
nginx:「哎呀,收到客戶端的一個http請求,該幹活了......咦,有php-fpm這小子的活兒!」apache
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的Fast已經代表含義了,是一種快速的CGI,也是現代動態網頁語言與web server之間廣泛所採用的。FastCGI像是一個常駐型的CGI,它能夠一直執行着,只要激活後,不會每次都要花費時間去fork一次(這是CGI最爲人詬病的fork-and-execute 模式)。它還支持分佈式的運算,即FastCGI程序能夠在網站服務器之外的主機上執行而且接受來自其它網站服務器來的請求。
nginx與php-fpm就是採用的FastCGI模式。
經常會見到這種格式的Urlhttp://blog.jjonline.cn/index.php/Article/Post/index.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是對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去解析,以下:
一、http://blog.jjonline.cn/index.php
匹配
二、http://blog.jjonline.cn/admin/index.php?m=Index&a=index
匹配,注意這裏Url中有Get變量,nginx中location匹配的路徑是uri,也就是虛擬路徑部分,本例也就是:/admin/index.php
三、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的開發者爲不一樣操做系統平臺提供的,無需深究。
依據上述第1條的墨跡得出兩個結論:
一、nginx須要正確將請求交給php-fpm來執行php腳本,nginx先得正確分析出URI中是否要去請求某個PHP腳本;
二、當php-fpm正確執行某個PHP腳本後,PHP中pathinfo模式實現單一入口須要PHP中$_SERVER['PATH_INFO']
包含了正確的pathinfo值;而PHP中的$_SERVER變量由nginx的fastcgi_param
指令來決定;
因此讓nginx支持pathinfo的配置中要修改內容也圍繞這個兩個點來展開。
第1、nginx的location能匹配到pathinfo格式的URI,去掉URI必須是.php
結尾的限定,修改以下:
location ~ \.php { }
第2、須要將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; }
In your PHP-fpm www.conf set security.limit_extensions
to .php
or .php5
or whatever suits your environment. For some users, completely removing all values or setting it to FALSE
was the only way to get it working.
In your nginx config file set fastcgi_pass
to your socket address (e.g. unix:/var/run/php-fpm/php-fpm.sock;
) instead of your server address and port.
Check your SCRIPT_FILENAME
fastcgi param and set it according to the location of your files.
In your nginx config file include fastcgi_split_path_info ^(.+\.php)(/.+)$;
in the location block where all the other fastcgi params are defined.
* In your php.ini set cgi.fix_pathinfo
to 1