[英]Reference: mod_rewrite, URL rewriting and "pretty links" explained
“漂亮的鏈接”是一個經常被要求的話題,但很少被完全解釋。 mod_rewrite是制作“漂亮鏈接”的一種方法,但它很復雜,而且它的語法非常簡潔,難以理解,並且文檔假定對 HTTP 有一定的熟練程度。 有人可以簡單地解釋“漂亮鏈接”如何工作以及如何使用 mod_rewrite 來創建它們嗎?
其他通用名稱、別名、干凈 URL 的術語: RESTful URL、用戶友好 URL、 SEO友好 URL、 slugging和 MVC URL(可能用詞不當)
要了解 mod_rewrite 是什么,您首先需要了解 Web 服務器的工作原理。 Web 服務器響應HTTP 請求。 最基本的 HTTP 請求如下所示:
GET /foo/bar.html HTTP/1.1
這是瀏覽器向 Web 服務器請求URL /foo/bar.html
的簡單請求。 重要的是要強調它不請求文件,它只請求一些任意 URL。 請求也可能如下所示:
GET /foo/bar?baz=42 HTTP/1.1
這與對 URL 的請求一樣有效,而且更明顯地與文件無關。
Web 服務器是一個偵聽端口的應用程序,接受來自該端口的 HTTP 請求並返回響應。 Web 服務器可以完全自由地以它認為合適的任何方式/以您配置的任何方式響應任何請求。 這個響應不是一個文件,它是一個HTTP 響應,它可能與任何磁盤上的物理文件有任何關系,也可能沒有任何關系。 Web 服務器不一定是 Apache,還有許多其他的 Web 服務器,它們都是持續運行的程序,並連接到響應 HTTP 請求的端口。 你可以自己寫一個。 本段旨在讓您擺脫 URL 直接等於文件的任何概念,理解這一點非常重要。 :)
大多數 Web 服務器的默認配置是在硬盤上查找與 URL 匹配的文件。 如果服務器的文檔根目錄設置為/var/www
,它可能會查看文件/var/www/foo/bar.html
是否存在,如果存在則提供它。 如果文件以“.php”結尾,它將調用 PHP 解釋器,然后返回結果。 所有這些關聯都是完全可配置的; 一個文件不必以“.php”結尾,Web 服務器就可以通過 PHP 解釋器運行它,並且 URL 不必匹配磁盤上的任何特定文件以發生某些事情。
mod_rewrite 是一種重寫內部請求處理的方法。 當 Web 服務器收到對 URL /foo/bar
的請求時,您可以將該 URL重寫為其他內容,然后 Web 服務器將在磁盤上查找與之匹配的文件。 簡單的例子:
RewriteEngine On
RewriteRule /foo/bar /foo/baz
這條規則說,只要請求匹配“/foo/bar”,就將其重寫為“/foo/baz”。 然后將處理該請求,就好像/foo/baz
已被請求一樣。 這可用於各種效果,例如:
RewriteRule (.*) $1.html
此規則匹配任何內容( .*
)並捕獲它( (..)
),然后將其重寫為附加“.html”。 換句話說,如果/foo/bar
是請求的 URL,它將被處理為好像/foo/bar.html
已被請求。 有關正則表達式匹配、捕獲和替換的更多信息,請參閱http://regular-expressions.info 。
另一個經常遇到的規則是:
RewriteRule (.*) index.php?url=$1
這再次匹配任何內容並將其重寫為文件 index.php,並將最初請求的 URL 附加在url
查詢參數中。 即,對於傳入的任何和所有請求,都會執行文件 index.php 並且該文件將可以訪問$_GET['url']
中的原始請求,因此它可以用它做任何事情。
首先,您將這些重寫規則放入您的Web 服務器配置文件中。 Apache 還允許*您將它們放入文檔根目錄中名為.htaccess
的文件中(即在 .php 文件旁邊)。
*如果主 Apache 配置文件允許; 它是可選的,但經常啟用。
mod_rewrite 不會神奇地使您的所有 URL 都“漂亮”。 這是一個常見的誤解。 如果您的網站中有此鏈接:
<a href="/my/ugly/link.php?is=not&very=pretty">
沒有什么 mod_rewrite 可以做的漂亮。 為了使它成為一個漂亮的鏈接,您必須:
將鏈接更改為漂亮的鏈接:
<a href="/my/pretty/link">
在服務器上使用 mod_rewrite 以使用上述任何一種方法處理對 URL /my/pretty/link
的請求。
(可以結合使用mod_substitute
來轉換傳出的 HTML 頁面及其包含的鏈接。盡管這通常比更新 HTML 資源更費力。)
mod_rewrite 可以做很多事情,您可以創建非常復雜的匹配規則,包括鏈接多個重寫、將請求代理到完全不同的服務或機器、返回特定的 HTTP 狀態代碼作為響應、重定向請求等。它非常強大,可用於如果您了解基本的 HTTP 請求-響應機制,那就太好了。 它不會自動使您的鏈接漂亮。
有關所有可能的標志和選項,請參閱官方文檔。
為了擴展deceze 的答案,我想提供一些示例並解釋其他一些 mod_rewrite 功能。
以下所有示例都假定您已經在.htaccess
文件中包含了RewriteEngine On
。
讓我們舉這個例子:
RewriteRule ^blog/([0-9]+)/([A-Za-z0-9-\+]+)/?$ /blog/index.php?id=$1&title=$2 [NC,L,QSA]
該規則分為 4 個部分:
RewriteRule
- 啟動重寫規則^blog/([0-9]+)/([A-Za-z0-9-\+]+)/?$
- 這稱為模式,但我將其稱為左側規則的 - 你想要重寫的內容blog/index.php?id=$1&title=$2
- 稱為替換,或重寫規則的右側 - 您要重寫的內容[NC,L,QSA]
是重寫規則的標志,用逗號分隔,我稍后會詳細解釋上面的重寫將允許您鏈接到/blog/1/foo/
之類的東西,它實際上會加載/blog/index.php?id=1&title=foo
。
^
表示頁面名稱的開頭 - 因此它將重寫example.com/blog/...
但不會重寫 example.com/foo/blog example.com/foo/blog/...
(…)
括號代表一個正則表達式,我們可以將其捕獲為規則右側的變量。 在這個例子中:
([0-9]+)
- 匹配長度至少為 1 個字符且僅具有數值(即 0-9)的字符串。 這可以用規則右側的$1
來引用-
或+
(注意+
用反斜杠轉義,因為沒有轉義它會執行作為正則表達式重復字符)。 這可以用規則右側的$2
來引用?
表示前面的字符是可選的,所以在這種情況下/blog/1/foo/
和/blog/1/foo
都會重寫到同一個地方$
表示這是我們要匹配的字符串的結尾這些選項添加在重寫規則末尾的方括號中以指定某些條件。 同樣,您可以在文檔中閱讀許多不同的標志,但我將介紹一些更常見的標志:
NC
no case 標志意味着重寫規則不區分大小寫,因此對於上面的示例規則,這意味着/blog/1/foo/
和/BLOG/1/foo/
(或任何變體)都將匹配。
L
最后一個標志表明這是應處理的最后一條規則。 這意味着當且僅當此規則匹配時,不會在當前重寫處理運行中評估進一步的規則。 如果規則不匹配,將照常嘗試所有其他規則。 如果您不設置L
標志,則以下所有規則將應用於之后重寫的 URL。
END
從 Apache 2.4 開始,您還可以使用[END]
標志。 與之匹配的規則將完全終止進一步的別名/重寫處理。 (而[L]
標志通常會觸發第二輪,例如在重寫子目錄或從子目錄中重寫時。)
QSA
查詢字符串附加標志允許我們將額外的變量傳遞給指定的 URL,這些變量將被添加到原始 get 參數中。 對於我們的示例,這意味着/blog/1/foo/?comments=15
之類的內容將加載/blog/index.php?id=1&title=foo&comments=15
R
這個標志不是我在上面的示例中使用的標志,但我認為值得一提。 這允許您指定 http 重定向,並可選擇包含狀態代碼(例如R=301
)。 例如,如果您想在 /myblog/ 上執行 301 重定向到 /blog/,您只需編寫如下規則:
RewriteRule ^/myblog/(*.)$ /blog/$1 [R=301,QSA,L]
重寫條件使重寫更加強大,允許您為更具體的情況指定重寫。 您可以在文檔中閱讀很多條件,但我將介紹一些常見示例並進行解釋:
# if the host doesn't start with www. then add it and redirect
RewriteCond %{HTTP_HOST} !^www\.
RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
這是一種非常常見的做法,它會在您的域前面加上www.
(如果它不存在)並執行 301 重定向。 例如,加載http://example.com/blog/
它會將您重定向到http://www.example.com/blog/
# if it cant find the image, try find the image on another domain
RewriteCond %{REQUEST_URI} \.(jpg|jpeg|gif|png)$ [NC]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*)$ http://www.example.com/$1 [L]
這稍微不太常見,但是如果文件名是服務器上存在的目錄或文件,則不執行規則的一個很好的示例。
%{REQUEST_URI} \.(jpg|jpeg|gif|png)$ [NC]
只會對文件擴展名為 jpg、jpeg、gif 或 png(不區分大小寫)的文件執行重寫。%{REQUEST_FILENAME} !-f
將檢查文件是否存在於當前服務器上,如果不存在則只執行重寫%{REQUEST_FILENAME} !-d
將檢查文件是否存在於當前服務器上,如果不存在則僅執行重寫Stack Overflow 還有許多其他很棒的入門資源:
^/
模式前綴中的斜線以用於.htaccess
使用。)甚至對新手友好的正則表達式概述:
.*
匹配任何內容,甚至是空字符串。 您不想在任何地方都使用此模式,但通常在最后一個后備規則中使用。[^/]+
更常用於路徑段。 它匹配除正斜杠之外的任何內容。\d+
只匹配數字字符串。\w+
匹配字母數字字符。 它基本上是[A-Za-z0-9_]
的簡寫。[\w\-]+
用於“slug”風格的路徑段,使用字母、數字、破折號-
和_
[\w\-.,]+
添加句點和逗號。 更喜歡在[…]
字符類中使用轉義的\-
破折號。\.
表示字面時間。 否則.
[…]
之外是任何符號的占位符。 這些占位符中的每一個通常都包含在(…)
括號中作為捕獲組。 而整個模式通常在^………$
開始 + 結束標記。 引用“模式”是可選的。
以下示例以 PHP 為中心,並且更加增量,更容易適應類似情況。 它們只是摘要,通常鏈接到更多變體或詳細問答。
/contact
, /about
將一些頁面名稱縮短為內部文件方案是最簡單的:
RewriteRule ^contact$ templ/contact.html RewriteRule ^about$ about.php
/object/123
將http://example.com/article/531
類的快捷方式引入現有 PHP 腳本也很容易。 數字占位符可以重新映射到$_GET
參數:
RewriteRule ^article/(\d+)$ article-show.php?id=$1 # └───────────────────────────┘
/article/with-some-title-slug
您可以輕松地擴展該規則以允許/article/title-string
占位符:
RewriteRule ^article/([\w-]+)$ article-show.php?title=$1 # └────────────────────────────────┘
請注意,您的腳本必須能夠(或適應)將這些標題映射回數據庫 ID。 單獨的 RewriteRules 不能憑空創建或猜測信息。
/readable/123-plus-title
因此,您會經常看到在實踐中使用的混合/article/529-title-slug
路徑:
RewriteRule ^article/(\d+)-([\w-]+)$ article.php?id=$1&title=$2 # └───────────────────────────────┘
現在您無論如何都可以跳過傳遞title=$2
,因為您的腳本通常會依賴於 database-id 。 -title-slug
已成為任意 URL 裝飾。
/foo/…
/bar/…
/baz/…
如果您對多個虛擬頁面路徑有類似的規則,那么您可以使用|
匹配和壓縮它們。 替代清單。 再次將它們重新分配給內部 GET 參數:
# ┌─────────────────────────┐ RewriteRule ^(blog|post|user)/(\w+)$ disp.php?type=$1&id=$2 # └───────────────────────────────────┘
如果這變得太復雜,您可以將它們拆分為單獨的RewriteRule
。
/date/SWITCH/backend
替代列表的更實際用途是將請求路徑映射到不同的腳本。 例如,根據日期為較舊和較新的 Web 應用程序提供統一的 URL:
# ┌─────────────────────────────┐ # │ ┌───────────┼───────────────┐ RewriteRule ^blog/(2009|2010|2011)/([\d-]+)/?$ old/blog.php?date=$2 RewriteRule ^blog/(\d+)/([\d-]+)/?$ modern/blog/index.php?start=$2 # └──────────────────────────────────────┘
這只是將 2009-2011 年的帖子重新映射到一個腳本,並將所有其他年份隱式地重新映射到另一個處理程序。 請注意更具體的規則首先出現。 每個腳本可能使用不同的 GET 參數。
/
路徑斜杠之外的其他分隔符/user-123-name
您最常看到 RewriteRules 來模擬虛擬目錄結構。 但你不會被迫缺乏創造力。 您也可以使用-
連字符進行分段或結構。
RewriteRule ^user-(\d+)$ show.php?what=user&id=$1 # └──────────────────────────────┘ # This could use `(\w+)` alternatively for user names instead of ids.
對於同樣常見的/wiki:section:Page_Name
方案:
RewriteRule ^wiki:(\w+):(\w+)$ wiki.php?sect=$1&page=$2 # └─────┼────────────────────┘ │ # └────────────────────────────┘
有時它適合在/
-delimiters 和:
或 之間交替使用.
甚至在同一規則中。 或者再次使用兩個 RewriteRules 將變體映射到不同的腳本。
/
斜杠/dir
= /dir/
選擇目錄式的路徑時,您可以在有或沒有最終的情況下達到它
RewriteRule ^blog/([\w-]+)/?$ blog/show.php?id=$1 # ┗┛
現在這處理http://example.com/blog/123
和/blog/123/
。 並且/?$
方法很容易附加到任何其他 RewriteRule 上。
.*/.*/.*/.*
您將遇到的大多數規則將一組受限的/…/
資源路徑段映射到單個 GET 參數。 然而,一些腳本處理可變數量的選項。 Apache regexp 引擎不允許選擇任意數量的它們。 但是您可以自己輕松地將其擴展為規則塊:
Rewriterule ^(\w+)/?$ in.php?a=$1 Rewriterule ^(\w+)/(\w+)/?$ in.php?a=$1&b=$2 Rewriterule ^(\w+)/(\w+)/(\w+)/?$ in.php?a=$1&b=$2&c=$3 # └─────┴─────┴───────────────────┴────┴────┘
如果您需要最多五個路徑段,則將此方案復制到五個規則中。 您當然可以使用更具體的[^/]+
占位符。 在這里,排序並不重要,因為兩者都不重疊。 因此,首先擁有最常用的路徑是可以的。
或者,您可以在此處通過?p[]=$1&p[]=$2&p[]=3
查詢字符串使用 PHP 數組參數 - 如果您的腳本只是更喜歡預先拆分它們。 (雖然更常見的是只使用一個包羅萬象的規則,並讓腳本本身將這些段從 REQUEST_URI 中擴展出來。)
prefix/opt?/.*
一個常見的變體是在規則中具有可選前綴。 如果您有靜態字符串或更受限制的占位符,這通常是有意義的:
RewriteRule ^(\w+)(?:/([^/]+))?/(\w+)$ ?main=$1&opt=$2&suffix=$3
現在更復雜的模式(?:/([^/])+)?
那里簡單地包裝了一個非捕獲(?:…)
組,並使其成為可選的)?
. 包含的占位符([^/]+)
將是替換模式$2
,但如果沒有中間/…/
路徑,則為空。
/prefix/123-capture/…/*/…whatever…
如前所述,您通常不需要過於通用的重寫模式。 但是,有時將靜態和特定比較與.*
結合起來確實有意義。
RewriteRule ^(specific)/prefix/(\d+)(/.*)?$ speci.php?id=$2&otherparams=$2
這可選擇任何/…/…/…
尾隨路徑段。 然后當然需要處理腳本將它們拆分,並對提取的參數本身進行可變化(這就是Web- “MVC”框架所做的)。
/old/path.HTML
URL 並沒有真正的文件擴展名。 這就是整個參考的內容(= URL 是虛擬定位器,不一定是直接文件系統映像)。 但是,如果您之前有 1:1 文件映射,則可以制定更簡單的規則:
RewriteRule ^styles/([\w\.\-]+)\.css$ sass-cache.php?old_fn_base=$1 RewriteRule ^images/([\w\.\-]+)\.gif$ png-converter.php?load_from=$2
其他常見用途是將過時的.html
路徑重新映射到更新的.php
處理程序,或者僅為單個(實際/真實)文件的目錄名稱別名。
/ugly.html
←→ /pretty
因此,在某些時候,您正在重寫 HTML 頁面以僅攜帶漂亮的鏈接,如deceze 所述。 同時,您仍然會收到對舊路徑的請求,有時甚至是來自書簽的請求。 作為解決方法,您可以通過 ping-pong 瀏覽器顯示/建立新的 URL。
這個常見的技巧涉及每當傳入的 URL 遵循過時/丑陋的命名方案時發送 30x/Location重定向。 然后瀏覽器將重新請求新的/漂亮的 URL,然后將其重寫(僅在內部)到原始或新位置。
# redirect browser for old/ugly incoming paths RewriteRule ^old/teams\.html$ /teams [R=301,QSA,END] # internally remap already-pretty incoming request RewriteRule ^teams$ teams.php [QSA,END]
請注意此示例如何僅使用[END]
而不是[L]
來安全地交替。 對於較舊的 Apache 2.2 版本,您可以使用其他解決方法,除了重新映射查詢字符串參數之外,例如: 重定向丑陋到漂亮的 URL,重新映射回丑陋的路徑,沒有無限循環
/this+that+
它在瀏覽器地址欄中不是那么漂亮,但您可以在 URL 中使用空格。 對於重寫模式,使用反斜杠轉義的\␣
空格。 否則只需"
- 引用整個模式或替換:
RewriteRule "^this [\w ]+/(.*)$" "index.php?id=$1" [L]
客戶端使用+
或%20
對空格進行序列化。 然而在 RewriteRules 中,它們被解釋為所有相對路徑段的文字字符。
頻繁重復:
RewriteCond %{REQUEST_URI} !-f RewriteCond %{REQUEST_URI} !-d RewriteRule ^.*$ index.php [L]
PHP 框架或 WebCMS / 門戶腳本經常使用它。 然后在 PHP 中使用$_SERVER["REQUEST_URI"]
處理實際的路徑拆分。 所以從概念上講,它與“per mod_rewrite”的 URL 處理幾乎相反。 (只需使用FallBackResource
代替。)
www.
從主機名請注意,這不會復制查詢字符串等。
# ┌──────────┐ RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] │ RewriteRule ^(.*)$ http://%1/$1 [R=301,L] │ # ↓ └───┼────────────┘ # └───────────────┘
也可以看看:
· .htaccess 中不同協議的 URL 重寫
· 通用 htaccess 將 www 重定向到非 www
· .htaccess - 如何強制“www”。 以一般的方式?
請注意,RewriteCond/RewriteRule 組合可能更復雜,匹配項( %1
和$1
)在兩個方向上交互,甚至:
Apache 手冊 - mod_rewrite 介紹,版權所有 2015 The Apache Software Foundation, AL-2.0
HTTPS://
RewriteCond %{SERVER_PORT} 80 RewriteRule ^(.*)$ https://example.com/$1 [R,L]
另見: https ://wiki.apache.org/httpd/RewriteHTTPToHTTPS
RewriteCond %{REQUEST_FILENAME}.php -f RewriteRule ^(.+)$ $1.php [L] # or [END]
請參閱: http ://httpd.apache.org/docs/2.4/rewrite/remapping.html#backward-compatibility
.htaccess
陷阱現在把這個和一粒鹽一起吃。 並非每個建議都可以推廣到所有情況。 這只是對眾所周知的和一些不明顯的絆腳石的簡單總結:
mod_rewrite
和.htaccess
要在每個目錄的配置文件中實際使用 RewriteRules,您必須:
檢查您的服務器是否啟用了AllowOverride All
。 否則,您的每個目錄的.htaccess
指令將被忽略,並且 RewriteRules 將不起作用。
顯然在您的httpd.conf
模塊部分中啟用了mod_rewrite
。
仍然在每個規則列表前面加上RewriteEngine On
。 雖然 mod_rewrite 在<VirtualHost>
和<Directory>
部分中隱式處於活動狀態,但每個目錄的.htaccess
文件需要單獨調用它。
^/
不匹配您通常不應該使用^/
開始您的.htaccess
RewriteRule 模式:
RewriteRule ^/article/\d+$ … ↑
這在舊教程中很常見。 它曾經對古老的 Apache 1.x 版本是正確的。 如今,請求路徑在.htaccess
RewriteRules 中很方便地完全與目錄相關。 只留下領先/
出局。
· 請注意,雖然<VirtualHost>
部分中的前導斜杠仍然是正確的。 這就是為什么你經常看到它^/?
可選的規則奇偶校驗。
· 或者當使用RewriteCond %{REQUEST_URI}
時,您仍然會匹配前導/
。
· 另請參閱Webmaster.SE:mod_rewrite 模式中何時需要前導斜杠 (/)?
<IfModule *>
包裝器消失了!您可能已經在許多示例中看到了這一點:
<IfModule mod_rewrite.c> Rewrite… </IfModule>
<VirtualHost>
部分中確實有意義 - 如果它與另一個后備選項結合使用,例如 ScriptAliasMatch。 (但從來沒有人這樣做過)。.htaccess
規則集。 在那里它只是作為后備,並保持“丑陋”的 URL 作為默認工作。 但是,您通常不希望在您自己的.htaccess
文件中使用它。
500
錯誤。 它通常完成的是用 HTTP 404
錯誤代替您的用戶。 (如果您考慮一下,它不會對用戶更加友好。)看似誘人的普遍保障措施,往往成為實踐中的障礙。
RewriteBase
許多復制+粘貼示例包含RewriteBase /
指令。 無論如何,這恰好是隱含的默認值。 所以你實際上並不需要這個。 這是花哨的 VirtualHost 重寫方案的解決方法,並且錯誤地猜測了某些共享主機的 DOCUMENT_ROOT 路徑。
在更深的子目錄中與單個 Web 應用程序一起使用是有意義的。 在這種情況下,它可以縮短 RewriteRule 模式。 通常,最好在每個目錄規則集中使用相對路徑說明符。
MultiViews
URL 重寫主要用於支持虛擬傳入路徑。 通常,您只有一個調度程序腳本( index.php
)或幾個單獨的處理程序( articles.php
、 blog.php
、 wiki.php
,...)。 后者可能與類似的虛擬 RewriteRule 路徑發生沖突。
例如,對/article/123
的請求可以使用/123
PATH_INFO 隱式映射到article.php
。 您要么必須使用普通的RewriteCond
!-f
+ !-d
來保護您的規則,和/或禁用 PATH_INFO 支持,或者只是禁用Options -MultiViews
。
這並不是說您總是必須這樣做。 內容協商只是虛擬資源的一種自動化。
如果您還沒有,請查看您想知道的關於 mod_rewrite 的所有信息。 組合多個 RewriteRules 通常會導致交互。 這不是每個[L]
標志習慣性地阻止的事情,而是您一旦精通就會接受的方案。 您可以重新重新編寫從一條規則到另一條規則的虛擬路徑,直到它到達實際的目標處理程序。
盡管如此,您仍然經常希望在早期規則中擁有最具體的規則(固定字符串/forum/…
模式,或更嚴格的占位符[^/.]+
)。 通用的 slurp-all 規則 ( .*
) 最好留給后面的規則。 (一個例外是RewriteCond -f/-d
保護作為主要塊。)
當您引入虛擬目錄結構/blog/article/123
時,這會影響 HTML 中的相對資源引用(例如<img src=mouse.png>
)。 可以通過以下方式解決:
href="/old.html"
或src="/logo.png"
<base href="/index">
添加到您的 HTML <head>
部分。 這隱含地將相對引用重新綁定到它們之前的內容。 您也可以制作進一步的 RewriteRules 以將.css
或.png
路徑重新綁定到其原始位置。 但這都是不必要的,或者會導致額外的重定向並妨礙緩存。
另請參閱: CSS、JS 和圖像不顯示漂亮的 url
一個常見的誤解是 RewriteCond 會阻止多個 RewriteRules(因為它們在視覺上排列在一起):
RewriteCond %{SERVER_NAME} localhost RewriteRule ^secret admin/tools.php RewriteRule ^hidden sqladmin.cgi
默認情況下它不會。 您可以使用[S=2]
標志鏈接它們。 否則,您將不得不重復它們。 雖然有時您可以制定一個“倒置”的主要規則來提早 [END] 重寫處理。
您無法匹配RewriteRule index.php\?x=y
,因為 mod_rewrite 僅與默認情況下的相對路徑進行比較。 您可以通過以下方式單獨匹配它們:
RewriteCond %{QUERY_STRING} \b(?:param)=([^&]+)(?:&|$) RewriteRule ^add/(.+)$ add/%1/$1 # ←──﹪₁──┘
.htaccess
與<VirtualHost>
如果您在每個目錄的配置文件中使用 RewriteRules,那么擔心正則表達式的性能是沒有意義的。 Apache 保留編譯后的 PCRE 模式的時間比使用通用路由框架的 PHP 進程長。 但是,對於高流量站點,一旦經過實戰測試,您應該考慮將規則集移動到虛擬主機服務器配置中。
在這種情況下,更喜歡可選的^/?
目錄分隔符前綴。 這允許在 PerDir 和服務器配置文件之間自由移動 RewriteRules。
不要擔心。
比較access.log
和error.log
通常你可以通過查看你的error.log
和access.log
來弄清楚 RewriteRule 的行為異常。 關聯訪問時間以查看最初進入的請求路徑,以及 Apache 無法解析到的路徑/文件(錯誤 404/500)。
這並沒有告訴您哪個 RewriteRule 是罪魁禍首。 但是像/docroot/21-.itle?index.php
這樣不可訪問的最終路徑可能會泄露進一步檢查的位置。 否則禁用規則,直到你得到一些可預測的路徑。
啟用重寫日志
請參閱Apache RewriteLog文檔。 對於調試,您可以在虛擬主機部分啟用它:
# Apache 2.2 RewriteLogLevel 5 RewriteLog /tmp/rewrite.log # Apache 2.4 LogLevel alert rewrite:trace5 #ErrorLog /tmp/rewrite.log
這產生了每個規則如何修改傳入請求路徑的詳細摘要:
[..] applying pattern '^test_.*$' to uri 'index.php' [..] strip per-dir prefix: /srv/www/vhosts/hc-profi/index.php -> index.php [..] applying pattern '^index\.php$' to uri 'index.php'
這有助於縮小過於通用的規則和正則表達式的失誤。
也可以看看:
· .htaccess 不工作 (mod_rewrite)
· 調試.htaccess 重寫規則的技巧
在問自己的問題之前
您可能知道,Stack Overflow 非常適合在 mod_rewrite 上提問。 通過包括先前的研究和嘗試(避免多余的答案)使它們成為主題,展示基本的正則表達式理解,並且:
$_SERVER
環境(如果它與參數不匹配有關)。access.log
和error.log
的摘錄,用於驗證現有規則解析的內容。 更好的是,一個rewrite.log
總結。這可以更快,更准確地獲得答案,並使它們對其他人更有用。
.htaccess
如果您從某處復制示例,請注意包含# comment and origin link
。 雖然忽略歸因只是一種不禮貌的做法,但它通常確實會損害以后的維護。 記錄任何代碼或教程源。 特別是在不熟悉的情況下,您應該更感興趣的是不要將它們視為魔法黑匣子。
免責聲明:只是一個小問題。 您經常聽到漂亮的 URL 重寫方案被稱為“SEO”鏈接或其他東西。 雖然這對於搜索示例很有用,但它是一個過時的誤稱。
現代搜索引擎都不會真正受到路徑段中的.html
和.php
或?id=123
查詢字符串的干擾。 舊的搜索引擎,例如 AltaVista,確實避免了抓取具有潛在不明確訪問路徑的網站。 現代爬蟲通常甚至渴望深度網絡資源。
從概念上講,“漂亮”的 URL 應該用於使網站對用戶友好。
/common/tree/nesting
提供可發現性。但是,不要為了順從而犧牲獨特的要求。
有各種在線工具可以為大多數 GET 參數 URL 生成 RewriteRules:
大多數情況下只輸出[^/]+
通用占位符,但對於瑣碎的站點可能就足夠了。
許多基本的虛擬 URL 方案可以在不使用 RewriteRules 的情況下實現。 Apache 允許在沒有.php
擴展名的情況下調用 PHP 腳本,並使用虛擬PATH_INFO
參數。
現在AcceptPathInfo On
通常默認啟用。 這基本上允許.php
和其他資源 URL 攜帶一個虛擬參數:
http://example.com/script.php/virtual/path
現在這個/virtual/path
在 PHP 中顯示為$_SERVER["PATH_INFO"]
,您可以在其中處理任何您喜歡的額外參數。
這不像讓 Apache 將輸入路徑段分成$1
、 $2
、 $3
並將它們作為不同的$_GET
變量傳遞給 PHP 那樣方便。 它只是用更少的配置工作來模擬“漂亮的 URL”。
.php
擴展名在 URL 中避免使用.php
“文件擴展名”的最簡單選項是啟用:
Options +MultiViews
由於匹配的基本名稱,這使 Apache 為/article
上的 HTTP 請求選擇article.php
。 這與前面提到的 PATH_INFO 功能配合得很好。 因此,您可以只使用http://example.com/article/virtual/title
之類的 URL。 如果您有一個具有多個 PHP 調用點/腳本的傳統 Web 應用程序,這很有意義。
請注意,MultiViews 具有不同/更廣泛的用途。 它會導致非常小的性能損失,因為 Apache 總是尋找具有匹配基本名稱的其他文件。 它實際上是用於Content-Negotiation ,因此瀏覽器會在可用資源(例如article.en.php
、 article.fr.php
、 article.jp.mp4
)中獲得最佳選擇。
.php
腳本的 SetType 或 SetHandler 避免在 URL 中攜帶.php
后綴的更直接的方法是為其他文件方案配置 PHP 處理程序。 最簡單的選項是通過.htaccess
覆蓋默認的 MIME/處理程序類型:
DefaultType application/x-httpd-php
這樣,您可以將您的article.php
腳本重命名為僅article
(不帶擴展名),但仍將其作為 PHP 腳本處理。
現在這可能會對安全性和性能產生一些影響,因為現在所有無擴展名文件都將通過 PHP 進行管道傳輸。 因此,您也可以只為單個文件設置此行為:
<Files article> SetHandler application/x-httpd-php # or SetType </Files>
這在某種程度上取決於您的服務器設置和使用的 PHP SAPI。 常見的替代方案包括ForceType application/x-httpd-php
或AddHandler php5-script
。
再次注意,此類設置會從一個
.htaccess
傳播到子文件夾。 您始終應該禁用靜態資源和上傳/目錄等的腳本執行(SetHandler None
和Options -Exec
或php_flag engine off
等)。
在其眾多選項中,Apache 提供了mod_alias
功能——有時它的工作原理與mod_rewrite
的 RewriteRules 一樣好。 請注意,其中大部分必須在<VirtualHost>
部分中設置,而不是在每個目錄的.htaccess
配置文件中。
ScriptAliasMatch
主要用於 CGI 腳本,但也應該適用於 PHP。 它允許正則表達式,就像任何RewriteRule
一樣。 事實上,它可能是配置一個包羅萬象的前端控制器的最強大的選擇。
一個簡單的Alias
也有助於一些簡單的重寫方案。
即使是簡單的ErrorDocument
指令也可以用來讓 PHP 腳本處理虛擬路徑。 請注意,這是一個笨拙的解決方法,但是禁止 GET 請求以外的任何內容,並根據定義淹沒 error.log。
有關更多提示,請參閱http://httpd.apache.org/docs/2.2/urlmapping.html 。
一個關於 URL 重寫的常見問題是這樣的:
我目前有如下所示的 URL:
我把它們做成了這樣:
通過在我的 .htaccess 文件中使用它:
RewriteRule my-blog/(\d+)--i-found-the-answer my-blog/entry.php?id=$1
但我希望它們看起來像這樣:
如何更改我的 .htaccess 文件以使其正常工作?
簡單的答案是你不能。
每當您在 Web 瀏覽器中輸入 URL、點擊鏈接或顯示引用圖像的頁面等時,瀏覽器都會請求特定 URL。 該請求最終到達 Web 服務器,Web 服務器給出響應。
重寫規則只是一條規則,它說“當瀏覽器請求一個看起來像 X 的 URL 時,給他們與他們請求 Y 相同的響應”。
當我們制定規則來處理“漂亮的 URL”時,請求是漂亮的 URL ,響應是基於內部丑陋的 URL 。 它不能反過來,因為我們正在服務器上編寫規則,而服務器看到的只是瀏覽器發送的請求。
給定重寫規則的基本模型,假設您正在向人類發出指令。 你可以說:
但是,如果請求中沒有信息,您的指示將沒有任何意義:
閱讀這些說明的人會說“對不起,我怎么知道正確的數字是多少?”
有時,您會看到相反的規則,如下所示:
RewriteRule my-blog/entry.php?id=(\d+) my-blog/$1--i-found-the-answer [R]
此規則匹配左側的丑陋 URL,並在右側生成漂亮的 URL。 所以我們肯定可以在漂亮部分的開頭不使用 ID 來編寫它嗎?
RewriteRule my-blog/entry.php?id=(\d+) my-blog/i-found-the-answer [R]
重要的區別是[R]
標志,這意味着這個規則實際上是一個重定向——而不是“提供來自這個 URL 的響應”,它的意思是“告訴瀏覽器加載這個 URL”。
您可以將其想象為其中一封自動回復的電子郵件,內容為“抱歉,Joe Bloggs 目前正在度假;請將您的信息發送給 Jane Smith。” 同樣,上面的重定向告訴瀏覽器“對不起,沒有http://example.com/my-blog/entry.php?id=42
的內容;請請求http://example.com/my-blog/42--i-found-the-answer
代替。
這個類比的重點是,如果實際上沒有任何叫 Jane Smith 的人在那里工作,或者如果他們不知道如何回答 Joe Bloggs 通常處理的問題,那么上面的信息就沒有多大用處。 同樣,如果您告訴瀏覽器請求的 URL 實際上沒有做任何有用的事情,那么重定向也沒有用。 一旦瀏覽器跟隨重定向,它就會發出一個新的請求,當服務器收到新的請求時,它仍然不知道 ID 號是什么。
Web 服務器只有請求中存在的信息,但它如何使用這些信息取決於您。
例如,您可以直接將其 URL 存儲在數據庫中,然后編寫一些代碼直接在 PHP、Python、node.js 等中進行匹配,而不是通過 ID 查找博客文章。或者您可以使用相同的 URL根據用戶在瀏覽器中設置的語言或基於 cookie 等顯示不同的內容。
您可以做的另一件事是使用帶有 POST 而不是 GET 方法的表單(或 API 請求)。 這意味着附加信息在請求的“正文”中發送,與 URL 分開。 它仍然必須發送,但在瀏覽器中不那么明顯,不會包含在書簽等中。
但是你不能在 .htaccess 文件中寫一行來創造奇跡。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.