Apache-2.4

在 .htaccess 中使用 Apache 重寫規則刪除 .html 導致 500 錯誤

  • November 23, 2021

我寫了一個小網站(4 頁,僅 HTML),我想通過在我的 .htaccess 文件中添加一些重寫規則來從 URL 中刪除 .html 副檔名,我搜尋了一下,發現了幾個類似的片段:

<IfModule mod_rewrite.c>
 RewriteEngine On
 RewriteCond %{REQUEST_FILENAME} !-d
 RewriteCond %{REQUEST_FILENAME}\.html -f
 RewriteRule ^(.*)$ $1.html
</IfModule>

以下兩個 URL 都提供相同的內容(我希望如此)

https://example.io/contact
https://example.io/contact.html

但是,以下給出了 500 錯誤:

https://example.io/contact/

該目錄不存在,如果我刪除上面提到的重寫程式碼,它將 404 而不是我所期望的。為什麼上​​面的程式碼會導致 500 錯誤?

更有趣的是,這將是 500:

https://example.io/contact/blah

但這將 404:

https://example.io/contact123/blah

contact/ 或contact123/ 都不作為目錄存在,但contact.html 確實存在而contact123.html 不存在。

任何幫助或解釋將不勝感激。


編輯:

MrWhite 已經給出了正確的答案,但是對於任何正在尋找未來的人來說,Apache 錯誤日誌看起來像這樣:

[Thu Oct 24 20:49:47.722210 2019] [core:error] [pid 13001:tid 139915446667008] [client 1.2.3.4:39006] AH00124: Request exceeded the limit of 10 internal redirects due to probable configuration error. Use 'LimitInternalRecursion' to increase the limit if necessary. Use 'LogLevel debug' to get a backtrace.

我檢查了日誌,不確定為什麼會這樣,但忘記在問題中包含這個。

tl;dr/contact/對(或)的請求/contact/blah會導致重寫循環(500 內部伺服器錯誤響應),因為REQUEST_FILENAME包含映射的文件系統路徑;不是您期望的 URL 路徑。


RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.html -f
RewriteRule ^(.*)$ $1.html

“問題”是REQUEST_FILENAME在第二種情況下的使用。REQUEST_FILENAME伺服器變數包含URL 映射到文件系統的絕對文件系統路徑。這不一定與 URL 路徑相同 - 但此條件假定它是相同的。當 URL 路徑包含不映射到文件系統的完整路徑段時(如在/contact/blahor中/contact123/blah),REQUEST_FILENAME則本質上“簡化”為映射到目錄的最後一個路徑段,加上“文件名”(即.../contact.../contact123分別 -文件根目錄,即 . /,是本例中最後匹配的目錄)。

要求/contact

當您請求時/contact,URL 路徑是/contact並且REQUEST_FILENAME/path/to/document-root/contact- 所以REQUEST_FILENAME直接映射到 URL 路徑。測試條件/path/to/document-root/contact.html成功,請求被重寫為contact.html. 一切都很好。

請求/contact//contact/blah

但是,當您請求時/contact/,URL 路徑是/contact/,但又REQUEST_FILENAME/path/to/document-root/contact(沒有斜杠後綴)。測試條件再次成功(如上),但請求被重寫contact/.html(因為.html附加到擷取的URL 路徑,即。$1.html)。處理循環,REQUEST_FILENAME評估結果與之前相同(條件再次成功)並且請求第二次重寫為contact/.html.html. 等等,導致重寫循環,當它“中斷”並且伺服器響應 500 Internal Server Error 時最終達到內部限制(預設 10)。

要求/contact123/blah

/contact123/blah另一方面,由於REQUEST_FILENAME伺服器變數變為/path/to/document-root/contact123並且/path/to/document-root/contact123.html不存在,因此導致 404,因此首先不會發生重寫。

解決方案

要“修復”這種行為,我們需要確保我們正在測試我們最終要重寫的相同文件/URL 路徑。

我們可以通過連接DOCUMENT_ROOTREQUEST_URI伺服器變數(或$1反向引用)來構造絕對文件名(用於測試),其中包含相對於根的 URL 路徑。(注意這REQUEST_URI包括斜杠前綴,而$1反向引用不包括。)

例如:

# Rewrite request to append ".html" extension to URL
RewriteCond %{DOCUMENT_ROOT}/$1.html -f
RewriteRule (.+) $1.html [L]

現在,測試條件正在測試請求將被重寫到的相同文件系統路徑(如果成功)。

無需檢查請求是否映射到目錄以及是否映射到文件(附加.html副檔名時),除非您還有與文件基名同名的目錄(例如basename.htmlbasename/)。但如果是這種情況,那麼無論如何都不會無法訪問一個或另一個,所以最好避免這種情況。

對或all/contact/的請求現在會按預期產生 404。/contact/blah``/contact123/blah

請注意,不需要反斜杠轉義RewriteCond TestString中的文字點,因為這不是正則表達式。

次要點… (and ) 上的^and$錨點是不必要的,因為(and ) 量詞在預設情況下是貪婪的(儘管有些使用者似乎仍然喜歡它們以提高可讀性?)。您還應該在. 如果這是文件中唯一(或最後一個)規則,則這不是必需的,但如果您稍後應該添加更多規則,那麼它可能是(並且必須記住以這種方式修改現有規則容易出錯)。^(.*)$``^(.+)$``*``+``L``last``RewriteRule``.htaccess

通過$1在指令中使用反向引用RewriteCond,這確實假設.htaccess文件位於文件根目錄中,否則,寫入的文件系統檢查將不正確。如果.htaccess文件位於子目錄中,則將RewriteCond指令更改為使用REQUEST_URI伺服器變數。例如:

RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule (.+) $1.html [L]

優化

您可以通過將正則表達式限制為不包含看起來像文件副檔名的 URL 來避免不必要地檢查所有已經包含文件副檔名的請求(即所有靜態資源)。例如:

RewriteCond %{DOCUMENT_ROOT}%{REQUEST_URI}.html -f
RewriteRule !\.\w{2,4}$ %{REQUEST_URI}.html [L]

引用自:https://serverfault.com/questions/989333