Tommy 碎碎念

Tommy Wu's blog

« 上一篇 | 下一篇 »

Nginx 對於 PHP 的錯誤轉向處理, fastcgi_intercept_errors 修正
post by tommy @ 19 十二月, 2011 17:54

在一般靜態網頁的處理上頭, nginx 可以正常的顯示一些錯誤的訊息 (如 404 File not found 之類的訊息). 不過對於 PHP (其他用 fastcgi 方式跑的程式也一樣), 預設來說, 如果有什麼錯誤, 往往就看到空白的一個網頁. 而一般的 nginx 教學上頭, 對於這個問題的處理建議就是 fastcgi_intercept_errors on 的設定. 但... 這個方式並不是完美的.

首先在 fastcgi_intercept_errors off (預設值) 的情形下:

  • 如果 $uri 指到的那個 php 檔案不存在, nginx 會把這個檔案名轉給 php-fpm (或 cgi 程式) 去處理, 這時 PHP 會找不到該檔案, 會出現 No input file specified. 這個錯誤訊息. (http 的狀態碼是 404)
  • 如果該 php 檔案存在, 但裡頭程式有問題 (打錯字之類), 如果 PHP 的 display_errors 是 true, 會顯示 PHP 的錯誤訊息 (http 的狀態碼是 200, 對 nginx 來說, 就是不認為有發生錯誤). 如果 dispaly_errors 是 false 時, 則會出現完全空白的畫面 (http 的狀態碼是 500).
  • 如果 php 的檔案沒有錯誤, 程式本身是正常的, 但對於程式的處理上頭, 要把這情形回應給使用者, 告知是不正常的狀態, 例如很多的程式, 在某些情形下會在找不到資料時, 顯示自訂的 404 網頁, 但是 http 的狀態碼是 404 (就是先執行 header ('HTTP/1.1 404 File Not Found'); 然後再顯示網頁的內容. 這時, 使用者會看到該程式所顯示的畫面, 但是 http 的狀態碼是 404. (不光是 404 的處理, 其它的也一樣)

對正常的網頁管理來看, 第一種情形, 我們希望能看到的是系統給的 404 錯誤的網頁, 而不是 php 的訊息. 而第二種情形, 我們希望在 display_errors 關閉的情形下, 能顯示系統的 500 錯誤的網頁, 而不光只是一個空白畫面. 第三種情形就是我們正常所希望的處理方式了.

而一般在 ngnix 上, 為了解決前面兩個情形所碰到的問題,  建議是把 fastcgi_intercept_errors 設成 on (必須在有設定 error_page 的時候才有作用), 這時:

  • 一樣會把不存在的 php 檔名轉給 php-fpm 處理, 而 php-fpm 一樣會回 No input file specified. 這個錯誤訊息 (http 的狀態碼是 404), 但是 nginx 在取得 404 的狀態之後, 並不是顯示 No input file specified. 而是轉給 error_page 所指定的 404 處理網頁.
  • 在 display_errors 打開的情形下, 不認為有錯誤, 而直接顯示 php 的錯誤訊息. 但是在 display_errors 關閉的情形下, 同樣得到沒有任何訊息的 http 狀態碼 500, 這時, nginx 會把這個錯誤再轉給 error_page  所指定的 500 處理網頁去處理.
  • nginx 雖然收到要顯示的網頁內容, 但是因為 http 狀態碼是 404, 所以並不直接顯示該內容, 而是轉到 error_page 的 404 處理網頁去顯示. (我的理解應該是這樣, 可是實際上, 是看到 browser 的 file not found 網頁, 而不是 nginx 那邊提供的內容)

所以.... 為了解決前兩種情形的問題, 改用 fastcgi_intercept_errors on 之後, 前兩者是正常的, 但是第三種情形就反而不正常了, 與我們想要的內容不一樣.

對於第一種情形, 我們可以在 fastcgi_intercept_errors off 的情形下, 改用 try_filesif 來檢查該 php 檔案是否存在, 這時, 就可以直接回應 404 的錯誤, 而不會到 php-fpm 處理才發現沒有該檔案. 利用這樣的檢查, 在不管 fastcgi_intercept_errors 的設定的情形下, 都可以轉到正確的 404 錯誤網頁.

所以, 問題就剩下第二種與第三種情形了.

就目前的 nginx 功能來看, 看來只能兩種選擇一種來用, 一個正常, 另一個就會不正常. 無法讓 fastcgi_intercept_errors 只去處理 404 以外的錯誤. 我們希望的功能, 應該是 fastcgi_intercept_errors on 的修正, 也就是在 fastcgi_intercept_errors 應該只處理 404 之外的錯誤, 不要再針對 404 去處理.

因為找不到其它的解決方法 (有人有不改程式的方法嗎?), 所以, 只好動手自己加上這功能的修正:

diff -Nur nginx-1.1.11.orig/src/http/modules/ngx_http_fastcgi_module.c nginx-1.1.11/src/http/modules/ngx_http_fastcgi_module.c
--- nginx-1.1.11.orig/src/http/modules/ngx_http_fastcgi_module.c 2011-12-09 21:32:51.000000000 +0800
+++ nginx-1.1.11/src/http/modules/ngx_http_fastcgi_module.c 2011-12-19 13:52:43.431468931 +0800
@@ -287,6 +287,16 @@
offsetof(ngx_http_fastcgi_loc_conf_t, upstream.pass_request_body),
NULL },
 
+ // twu2 begin
+ // option to skip 404 error
+ { ngx_string("fastcgi_intercept_error_skip_404"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_fastcgi_loc_conf_t, upstream.intercept_error_skip_404),
+ NULL },
+ // twu2 edn
+
{ ngx_string("fastcgi_intercept_errors"),
NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
ngx_conf_set_flag_slot,
@@ -2092,6 +2102,10 @@
conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
 
conf->upstream.intercept_errors = NGX_CONF_UNSET;
+ // twu2 begin
+ // option to skip 404 error
+ conf->upstream.intercept_error_skip_404 = NGX_CONF_UNSET;
+ // twu2 end
 
/* "fastcgi_cyclic_temp_file" is disabled */
conf->upstream.cyclic_temp_file = 0;
@@ -2332,6 +2346,11 @@
 
ngx_conf_merge_value(conf->upstream.intercept_errors,
prev->upstream.intercept_errors, 0);
+ // twu2 begin
+ // option to skip 404 error
+ ngx_conf_merge_value(conf->upstream.intercept_errors,
+ prev->upstream.intercept_errors, 0);
+ // twu2 end
 
ngx_conf_merge_ptr_value(conf->catch_stderr, prev->catch_stderr, NULL);
 
diff -Nur nginx-1.1.11.orig/src/http/ngx_http_upstream.c nginx-1.1.11/src/http/ngx_http_upstream.c
--- nginx-1.1.11.orig/src/http/ngx_http_upstream.c 2011-12-09 21:19:57.000000000 +0800
+++ nginx-1.1.11/src/http/ngx_http_upstream.c 2011-12-19 13:49:21.476532705 +0800
@@ -1742,6 +1742,12 @@
return NGX_DECLINED;
}
 
+ // twu2 begin
+ // don't intercept 404 error (it will be processed by upstream)
+ if (status == NGX_HTTP_NOT_FOUND && u->conf->intercept_error_skip_404)
+ return NGX_DECLINED;
+ // twu2 end
+
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
 
if (clcf->error_pages == NULL) {
diff -Nur nginx-1.1.11.orig/src/http/ngx_http_upstream.h nginx-1.1.11/src/http/ngx_http_upstream.h
--- nginx-1.1.11.orig/src/http/ngx_http_upstream.h 2011-12-09 21:19:57.000000000 +0800
+++ nginx-1.1.11/src/http/ngx_http_upstream.h 2011-12-19 13:50:22.147014289 +0800
@@ -148,6 +148,10 @@
 
ngx_flag_t ignore_client_abort;
ngx_flag_t intercept_errors;
+ // twu2 begin
+ // add an option to skip intercept 404 error
+ ngx_flag_t intercept_error_skip_404;
+ // twu2 end
ngx_flag_t cyclic_temp_file;
 
ngx_path_t *temp_path;

經過這個修正之後, 我們只要在 nginx.conf 中設定

        fastcgi_intercept_errors on;
fastcgi_intercept_error_skip_404 on;

然後在 php 的處理之前加上檔案的存在檢查:

location ~ \.php$ {
try_files $uri =404;

用 try_files 去檢查是否存在, 不存在就回傳 404.

用 try_files 似乎會檢查不出來, 所以看來還是要用 if 來處理:

# without path_info
location ~ ^(?<SCRIPT_NAME>/.*\.(php|suphp))$ {
if (!-f $document_root$SCRIPT_NAME) {
return 404;
}

在 userdir 上頭, 由於目錄會改寫, 直接用 try_files 會有問題, 可以改成這樣:

location ~ ^/~(?<USER>.+?)(?<SCRIPT_NAME>/.+\.php)$ {
root /home/$USER/public_html;
if (!-f $document_root$SCRIPT_NAME) {
return 404;
}

這樣子就可以達到我們要的功能了.


補充一下, 上頭那個 php 的處理, 如果使用 try_files 在 PATH_INFO 的狀態時, 會造成 PATH_INFO 無法正常使用, 所以對於 PATH_FILE 的 php 處理, 要改成跟 userdir 一樣, 用 if 來處理才可以, 如:

# with path_info
location ~ ^(?<SCRIPT_NAME>/.*\.(php|suphp))(?<PATH_INFO>.*)$ {
# use try_files here will make PATH_INFO not work, so use if
#try_files $uri =404;
if (!-f $document_root$SCRIPT_NAME) {
return 404;
}

改成這樣, 有用到 PATH_INFO 的程式就正常了.

Del.icio.us Furl HEMiDEMi Technorati MyShare
commons icon [1] Re:Nginx 對於 PHP 的錯誤轉向處理, fastcgi_intercept_errors 修正 [ 回覆 ]

您好~~想請教一個問題~~小弟上次不小心在Wordpress的設定-->一般-->URL的地方把網址修改掉~~修改過後整個出現404錯誤(連Wordpress的登入也是)~~然後看了香腸炒魷魚的文章~~把網址改回原本的了~~Wordpress也可以登入了~~這個網址http://nutrition-gui.com/wp/~~然後現在第二個問題出現了~~就是網頁可以進去~~但只要點選文章就又會出現404錯誤~~我該怎麼做~~才能把404錯誤救回來,又不會砍到之前的文章??

commons icon [2] Re:Nginx 對於 PHP 的錯誤轉向處理, fastcgi_intercept_errors 修正 [ 回覆 ]

Wordpress 我沒用過.
以一般程式對 web server 的 rewrite 支援來看, 應該都是用 apache 的 .htaccess 吧. 不過 nginx 並不支援 .htaccess, 所以你必須要自行把 .htaccess 裡頭的 rewrite 語法轉成 nginx 的語法並寫入 nginx 的設定檔中.

迴響
暱稱:
標題:
個人網頁:
電子郵件:
authimage

迴響

  

Bad Behavior 已經阻擋了 56 個過去 7 天試圖闖關的垃圾迴響與引用。
Power by LifeType. Template design by JamesHuang. Valid XHTML and CSS