提问人:deceze 提问时间:12/13/2013 最后编辑:Peter Mortensendeceze 更新时间:3/11/2022 访问量:47961
参考资料:mod_rewrite、URL 重写和“漂亮链接”解释
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。需要强调的是,它不请求文件,它只是请求一些任意的 URL。该请求也可能如下所示:/foo/bar.html
GET /foo/bar?baz=42 HTTP/1.1
这与对 URL 的请求一样有效,而且它显然与文件无关。
Web 服务器是一个侦听端口的应用程序,接受进入该端口的 HTTP 请求并返回响应。Web 服务器可以完全自由地以它认为合适的任何方式/以您配置它响应的任何方式响应任何请求。此响应不是文件,而是 HTTP 响应,它可能与任何磁盘上的物理文件有关,也可能与此无关。Web 服务器不一定是 Apache,还有许多其他 Web 服务器,它们都只是持久运行的程序,并连接到响应 HTTP 请求的端口。你可以自己写一个。本段旨在让您摆脱 URL 直接等于文件的任何概念,这对于理解非常重要。:)
大多数 Web 服务器的默认配置是查找与硬盘上的 URL 匹配的文件。如果服务器的文档根目录设置为,例如,它可能会查看文件是否存在,如果存在,则提供该文件。如果文件以“.php”结尾,它将调用PHP解释器,然后返回结果。所有这些关联都是完全可配置的;文件不必以“.php”结尾,Web 服务器就可以通过 PHP 解释器运行它,并且 URL 不必与磁盘上的任何特定文件匹配即可发生某些事情。/var/www
/var/www/foo/bar.html
mod_rewrite 是一种重写内部请求处理的方法。当 Web 服务器收到对 URL 的请求时,您可以在 Web 服务器在磁盘上查找与之匹配的文件之前将该 URL 重写为其他内容。简单示例:/foo/bar
RewriteEngine On
RewriteRule /foo/bar /foo/baz
这条规则说,每当请求匹配“/foo/bar”时,就将其重写为“/foo/baz”。然后,该请求将像请求一样进行处理。这可用于各种效果,例如:/foo/baz
RewriteRule (.*) $1.html
此规则匹配任何内容 () 并捕获它 (),然后重写它以附加“.html”。换言之,如果是请求的 URL,则将像请求一样处理它。有关正则表达式匹配、捕获和替换的详细信息,请参阅 http://regular-expressions.info。.*
(..)
/foo/bar
/foo/bar.html
另一个经常遇到的规则是这样的:
RewriteRule (.*) index.php?url=$1
这再次匹配任何内容,并将其重写到文件索引 .php,并在查询参数中附加最初请求的 URL。即,对于传入的任何和所有请求,都会执行文件 index.php,并且该文件将可以访问 中的原始请求,因此它可以对它执行任何操作。url
$_GET['url']
首先,您将这些重写规则放入 Web 服务器配置文件中。Apache 还允许*将它们放入文档根目录中调用的文件中(即在 .php 文件旁边)。.htaccess
* 如果主 Apache 配置文件允许;它是可选的,但通常启用。
mod_rewrite不做什么
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功能的解释。
以下所有示例都假定您已经包含在文件中。RewriteEngine On
.htaccess
重写示例
让我们举个例子:
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/...
- 每组括号代表一个正则表达式,我们可以将其捕获为规则右侧的变量。在此示例中:
(…)
- 第一组括号 - - 匹配长度至少为 1 个字符且仅包含数值(即 0-9)的字符串。这可以在规则的右侧引用
([0-9]+)
$1
- 第二组括号匹配长度至少为 1 个字符的字符串,仅包含字母数字字符(A-Z、a-z 或 0-9)或 or(注意使用反斜杠转义,因为如果不转义,这将作为正则表达式重复字符执行)。这可以在规则的右侧引用
-
+
+
$2
- 第一组括号 - - 匹配长度至少为 1 个字符且仅包含数值(即 0-9)的字符串。这可以在规则的右侧引用
?
表示前面的字符是可选的,因此在这种情况下,两者都会重写到同一位置/blog/1/foo/
/blog/1/foo
$
表示这是我们要匹配的字符串的末尾
标志
这些选项在重写规则末尾的方括号中添加,用于指定某些条件。同样,您可以在文档中阅读许多不同的标志,但我将介绍一些更常见的标志:
NC
no case 标志表示重写规则不区分大小写,因此对于上面的示例规则,这意味着 and(或其任何变体)将匹配。/blog/1/foo/
/BLOG/1/foo/
L
最后一个标志指示这是应处理的最后一个规则。这意味着,当且仅当此规则匹配时,在当前的重写处理运行中将不会评估其他规则。如果规则不匹配,将照常按顺序尝试所有其他规则。如果未设置该标志,则以下所有规则都将应用于重写的 URL。L
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 重定向,并可选择包含状态代码(例如)。例如,如果你想在 /myblog/ 到 /blog/ 上执行 301 重定向,你只需编写一条规则,如下所示:R=301
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]
这是一种非常常见的做法,它会在您的域前面加上(如果还没有)并执行 301 重定向。例如,加载它会将您重定向到www.
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
将检查当前服务器上是否存在该文件,并且仅在不存在时执行重写- 重写将尝试在另一个域上加载相同的文件
mod_rewrite的替代品
许多基本的虚拟 URL 方案可以在不使用 RewriteRules 的情况下实现。Apache 允许在没有扩展的情况下调用 PHP 脚本,并使用虚拟参数。.php
PATH_INFO
使用PATH_INFO路加
如今,默认情况下通常启用
AcceptPathInfo On
。这基本上允许和其他资源 URL 携带虚拟参数:.php
http://example.com/script.php/virtual/path
现在,这在 PHP 中显示为
$_SERVER[“PATH_INFO”],
您可以在其中随心所欲地处理任何额外的参数。/virtual/path
这不如让 Apache 将输入路径段分成 , 并将它们作为不同的变量传递给 PHP 那么方便。它只是以更少的配置工作模拟“漂亮的 URL”。
$1
$2
$3
$_GET
启用 MultiView 以隐藏扩展
.php
避免在 URL 中使用“文件扩展名”的最简单选项是启用:
.php
Options +MultiViews
由于 base name 匹配,这会使 Apache 选择 HTTP 请求。这与上述PATH_INFO功能配合得很好。因此,您可以只使用像 .如果您有一个具有多个 PHP 调用点/脚本的传统 Web 应用程序,这是有道理的。
article.php
/article
http://example.com/article/virtual/title
请注意,MultiView 具有不同/更广泛的用途。它会产生非常小的性能损失,因为 Apache 总是会查找具有匹配基名的其他文件。它实际上是用于内容协商的,因此浏览器会在可用资源(例如、、)中获得最佳选择。
article.en.php
article.fr.php
article.jp.mp4
SetType 或 SetHandler 用于无扩展脚本
.php
避免在 URL 中携带后缀的更直接的方法是为其他文件方案配置 PHP 处理程序。最简单的选项是通过以下方式覆盖默认的 MIME/处理程序类型:
.php
.htaccess
DefaultType application/x-httpd-php
这样,您可以将脚本重命名为 just(不带扩展名),但仍将其作为 PHP 脚本处理。
article.php
article
现在,这可能会对安全性和性能产生一些影响,因为所有无扩展名的文件现在都将通过 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 重写方案
在众多选项中,Apache 提供了一些功能,这些功能有时与 RewriteRules 一样有效。请注意,其中大多数必须在一个部分中设置,而不是在每个目录的配置文件中设置。
mod_alias
mod_rewrite
<VirtualHost>
.htaccess
ScriptAliasMatch
主要用于 CGI 脚本,但也应该适用于 PHP。它允许正则表达式,就像任何 .事实上,它可能是配置包罗万象的前置控制器的最强大的选择。RewriteRule
普通
的别名
也有助于一些简单的重写方案。即使是普通的
ErrorDocument
指令也可以用来让 PHP 脚本处理虚拟路径。请注意,这是一个笨拙的解决方法,但是,除了 GET 请求之外,禁止任何内容,并且根据定义会淹没 error.log。
有关更多提示,请参阅 http://httpd.apache.org/docs/2.2/urlmapping.html。
引用
Stack Overflow 还有许多其他很棒的入门资源:
- Serverfault:你想知道的关于mod_rewrite
的一切(请记住,删除^/
模式前缀中的斜杠,以便使用.htaccess
。 - mod_rewrite隐藏功能中的注意事项。
- 浏览我们最受欢迎的 mod-rewrite 问题和答案。
- Apache 重定向和重新映射指南。
- AskApache 终极 .htaccess 指南
- 以及 mod-rewrite 标签 wiki 引用。
对新手友好的正则表达式甚至概述:
- 我们的正则表达式标签 wiki 用于语法纲要。
- 以及简短的 Apache 正则表达式摘要。
- 其他 regexp.info 易于理解的基础知识。
经常使用的占位符
.*
匹配任何内容,即使是空字符串。您不希望在任何地方都使用此模式,但通常在最后一个回退规则中。[^/]+
更常用于路径段。它匹配除正斜杠以外的任何内容。\d+
仅匹配数字字符串。\w+
匹配字母数字字符。它基本上是 的简写。[A-Za-z0-9_]
[\w\-]+
对于“slug”样式的路径段,使用字母、数字、破折号和-
_
[\w\-.,]+
添加句点和逗号。在字符类中首选转义破折号。\-
[…]
\.
表示文字句点。否则,超出 是任何符号的占位符。.
[…]
这些占位符中的每一个通常都括在括号中作为捕获组。而整个模式通常在开始+结束标记中。引用“模式”是可选的。(…)
^………$
重写规则
以下示例以 PHP 为中心,并且更具增量性,更容易适应类似情况。 它们只是摘要,通常链接到更多变化或详细的问答。
静态映射
,/contact
/about
将几个页面名称缩短为内部文件方案是最简单的:
RewriteRule ^contact$ templ/contact.html RewriteRule ^about$ about.php
数字标识符
/object/123
引入像现有PHP脚本这样的快捷方式也很容易。数字占位符可以重新映射到参数:
http://example.com/article/531
$_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 # └────────────────────────────────┘
请注意,您的脚本必须能够(或经过调整)才能将这些标题映射回 database-ids。仅靠 RewriteRules 无法凭空创建或猜测信息。
带有数字前缀的蛞蝓
/readable/123-plus-title
因此,在实践中经常会看到混合路径:
/article/529-title-slug
RewriteRule ^article/(\d+)-([\w-]+)$ article.php?id=$1&title=$2 # └───────────────────────────────┘
现在,您可以跳过传递 无论如何,因为您的脚本通常将依赖于 database-id。已成为任意 URL 修饰。
title=$2
-title-slug
与备选列表的统一性
/foo/…
/bar/…
/baz/…
如果对多个虚拟页面路径有类似的规则,则可以使用备用列表进行匹配和压缩。同样,只需将它们重新分配给内部 GET 参数:
|
# ┌─────────────────────────┐ RewriteRule ^(blog|post|user)/(\w+)$ disp.php?type=$1&id=$2 # └───────────────────────────────────┘
如果这变得太复杂,您可以将它们拆分为单独的。
RewriteRule
将相关 URL 分派到不同的后端
/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 # ┗┛
现在,这同时处理 和 。并且该方法很容易附加到任何其他 RewriteRule 上。
http://example.com/blog/123
/blog/123/
/?$
虚拟路径的灵活分段
.*/.*/.*/.*
您将遇到的大多数规则将一组受约束的资源路径段映射到单个 GET 参数。但是,某些脚本会处理可变数量的选项。 Apache 正则表达式引擎不允许选择任意数量的正则表达式。但是您可以轻松地将其扩展为规则块:
/…/
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 # └─────┴─────┴───────────────────┴────┴────┘
如果最多需要 5 个路径段,请将此方案复制到 5 个规则中。当然,您可以分别使用更具体的占位符。 在这里,排序并不那么重要,因为两者都没有重叠。因此,首先使用最常用的路径是可以的。
[^/]+
或者,您可以在此处通过查询字符串使用 PHP 数组参数 - 如果您的脚本只是喜欢预拆分它们。 (尽管更常见的做法是只使用一个包罗万象的规则,并让脚本本身将句段从REQUEST_URI中扩展出来。
?p[]=$1&p[]=$2&p[]=3
Смотритетакже: 如何将我的 URL 路径段转换为查询字符串键值对?
可选段
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
这将可选化任何尾随路径段。当然,这需要处理脚本将它们拆分,并对提取的参数进行 variabl-ify 本身(这就是 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 所概述的那样。 同时,您仍然会收到对旧路径的请求,有时甚至来自书签。作为解决方法,您可以使用乒乓球浏览器来显示/建立 新 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]
请注意,此示例仅使用而不是安全地交替。对于较旧的 Apache 2.2 版本,除了重新映射之外,您还可以使用其他解决方法 查询字符串参数,例如:将丑陋的重定向到漂亮的URL,重新映射回丑陋的路径,没有无限循环
[END]
[L]
模式中的空格␣
/this+that+
它在浏览器地址栏中不是那么漂亮,但您可以在 URL 中使用空格。对于重写模式,请使用反斜杠转义空格。 否则只需引用整个模式或替换:
\␣
"
RewriteRule "^this [\w ]+/(.*)$" "index.php?id=$1" [L]
客户端序列化带有空格或空格的 URL。然而,在 RewriteRules 中,它们使用所有相对路径段的文字字符进行解释。
+
%20
频繁重复:
中央调度器/前端控制器脚本的包罗万象
RewriteCond %{REQUEST_URI} !-f RewriteCond %{REQUEST_URI} !-d RewriteRule ^.*$ index.php [L]
这通常被PHP框架或WebCMS /门户脚本使用。然后,在 PHP 中使用 .因此,从概念上讲,它与“按mod_rewrite”的 URL 处理几乎相反。(只需改用
FallBackResource
。$_SERVER["REQUEST_URI"]
从主机名中删除
www.
请注意,这不会复制查询字符串等。
# ┌──────────┐ RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] │ RewriteRule ^(.*)$ http://%1/$1 [R=301,L] │ # ↓ └───┼────────────┘ # └───────────────┘
另请参阅:
·在 .htaccess
中为不同协议重写 ·通用 htaccess 重定向 www 到非 www
·.htaccess - 如何以通用方式强制使用“www.”?请注意,RewriteCond/RewriteRule 组合可能更复杂,匹配项 ( 和 ) 甚至在两个方向上交互:
%1
$1
Apache 手册 - mod_rewrite介绍,版权所有 2015 Apache 软件基金会,AL-2.0重定向至
HTTPS://
RewriteCond %{SERVER_PORT} 80 RewriteRule ^(.*)$ https://example.com/$1 [R,L]
Смотритетакже: https://wiki.apache.org/httpd/RewriteHTTPToHTTPS
“删除”PHP 扩展
RewriteCond %{REQUEST_FILENAME}.php -f RewriteRule ^(.+)$ $1.php [L] # or [END]
Смотритетакже: 使用 mod_rewrite 删除 .php 扩展
将旧的 .html 路径别名为 .php 脚本
请参见:http://httpd.apache.org/docs/2.4/rewrite/remapping.html#backward-compatibility
从“/page”等 URL 重写为“/index.php/page”等脚本
将子域重定向到文件夹
普遍存在的陷阱.htaccess
现在对此持保留态度。并非每个建议都可以推广到所有情况。 这只是对众所周知的和一些不明显的绊脚石的简单总结:
Enable 和
mod_rewrite
.htaccess
若要在每个目录配置文件中实际使用 RewriteRules,必须:
检查服务器是否启用
了 AllowOverride All
。否则,每个目录的指令将被忽略,并且 RewriteRules 将不起作用。.htaccess
显然,在您的模块部分启用
了mod_rewrite
。httpd.conf
在每个规则列表前面加上 still。虽然 mod_rewrite 在 和 部分中隐式活跃, 每个目录的文件需要单独调用它。
RewriteEngine On
<VirtualHost>
<Directory>
.htaccess
前导斜杠不匹配
^/
通常,不应以以下方式开始 RewriteRule 模式:
.htaccess
^/
RewriteRule ^/article/\d+$ … ↑
这在旧教程中很常见。它曾经适用于古老的 Apache 1.x 版本。如今,请求路径在 RewriteRules 中非常方便地完全相对于目录。只需将引线排除在外即可。
.htaccess
/
·请注意,前导斜杠在各节中仍然是正确的。这就是为什么您经常看到它为规则奇偶校验提供可选功能的原因。
·或者当使用 u 仍然匹配前导 .
·另请参见 Webmaster.SE:mod_rewrite模式中何时需要前导斜杠 (/)?<VirtualHost>
^/?
RewriteCond %{REQUEST_URI}
/
<IfModule *>
包装纸消失了!您可能已经在许多示例中看到了这一点:
<IfModule mod_rewrite.c> Rewrite… </IfModule>
- 它在部分中确实有意义 - 如果它与另一个回退选项(例如 ScriptAliasMatch)结合使用。(但从来没有人这样做过)。
<VirtualHost>
- 它通常针对许多开源项目的默认规则集进行分发。在那里,它只是作为后备,并将“丑陋”的 URL 保持为默认工作。
.htaccess
但是,您不希望它通常出现在您自己的文件中。
.htaccess
- 首先,mod_rewrite不会随机脱离。(如果是这样,你会遇到更大的问题)。
- 如果它真的被禁用,你的 RewriteRules 仍然无法正常工作。
- 它旨在防止 HTTP 错误。它通常完成的是用 HTTP 错误来装饰您的用户。(如果你仔细想想,并没有那么人性化。
500
404
- 实际上,它只是抑制更有用的日志条目或服务器通知邮件。你不会更聪明地解释为什么你的 RewriteRules 永远不会起作用。
看似诱人的普遍保障措施在实践中往往被证明是一个障碍。
- 它在部分中确实有意义 - 如果它与另一个回退选项(例如 ScriptAliasMatch)结合使用。(但从来没有人这样做过)。
除非需要,否则不要使用
RewriteBase
许多复制+粘贴示例都包含指令。无论如何,这恰好是隐式默认值。所以你实际上并不需要这个。这是花哨的 VirtualHost 重写方案的解决方法,并且错误地猜测了一些共享主机商的DOCUMENT_ROOT路径。
RewriteBase /
在更深的子目录中与单个 Web 应用程序一起使用是有意义的。在这种情况下,它可以缩短 RewriteRule 模式。通常,最好在每个目录规则集中首选相对路径说明符。
当虚拟路径重叠时禁用
MultiViews
URL 重写主要用于支持虚拟传入路径。通常,您只有一个调度程序脚本 () 或几个单独的处理程序 (, , , ...)。后者可能与类似的虚拟 RewriteRule 路径发生冲突。
index.php
articles.php
blog.php
wiki.php
例如,请求可以隐式映射到PATH_INFO。你要么必须用普通的 + 来保护你的规则,和/或禁用PATH_INFO支持,或者只是禁用 .
/article/123
article.php
/123
RewriteCond
!-f
!-d
Options -MultiViews
这并不是说你总是必须这样做。内容协商只是虚拟资源的自动化。
订购很重要
如果您还没有,请参阅您想知道的有关mod_rewrite的所有信息。组合多个 RewriteRules 通常会导致交互。这不是每个标志习惯性地阻止的事情,而是一旦精通就会接受的方案。 您可以重新重新写入从一个规则到另一个规则的虚拟路径,直到它到达实际的目标处理程序。
[L]
尽管如此,您仍然希望在早期规则中拥有最具体的规则(固定字符串模式或更严格的占位符)。 通用的 slurp-all 规则 () 最好留给后面的规则。(例外情况是将守卫作为主块。
/forum/…
[^/.]+
.*
RewriteCond -f/-d
样式表和图像停止工作
引入虚拟目录结构时,这会影响 HTML 中的相对资源引用(如 )。 这可以通过以下方式解决:
/blog/article/123
<img src=mouse.png>
- 仅使用服务器绝对引用或
href="/old.html"
src="/logo.png"
- 通常只需添加到您的 HTML 部分即可。
这会隐式地将相对引用重新绑定到它们以前的引用。
<base href="/index">
<head>
您也可以制作进一步的 RewriteRules 以重新绑定或路径到其原始位置。 但这既是不必要的,也会导致额外的重定向并阻碍缓存。
.css
.png
Смотритетакже: CSS、JS 和图像不显示漂亮的 url
- 仅使用服务器绝对引用或
RewriteConds 只屏蔽一个 RewriteRule
一个常见的错误是 RewriteCond 阻塞了多个 RewriteRules(因为它们在视觉上排列在一起):
RewriteCond %{SERVER_NAME} localhost RewriteRule ^secret admin/tools.php RewriteRule ^hidden sqladmin.cgi
默认情况下没有。您可以使用标志链接它们。否则,您将不得不重复它们。虽然有时你可以制定一个“倒置”的主要规则来[结束]重写处理。
[S=2]
QUERY_STRING免于 RewriteRules
您无法匹配 ,因为默认情况下mod_rewrite仅与相对路径进行比较。但是,您可以通过以下方式单独匹配它们:
RewriteRule index.php\?x=y
RewriteCond %{QUERY_STRING} \b(?:param)=([^&]+)(?:&|$) RewriteRule ^add/(.+)$ add/%1/$1 # ←──﹪₁──┘
.htaccess
与。<VirtualHost>
如果您在每个目录的配置文件中使用 RewriteRules,那么担心正则表达式性能是没有意义的。Apache 保留 编译的 PCRE 模式比具有通用路由框架的 PHP 进程更长。但是,对于高流量网站,您应该考虑 在经过实战测试后,将规则集移动到虚拟主机服务器配置中。
在这种情况下,首选可选的目录分隔符前缀。这允许在 PerDir 和服务器之间自由移动 RewriteRules 配置文件。
^/?
每当某些东西不起作用时
不用担心。
比较
access.log
和error.log
通常,您只需查看 和 即可弄清楚 RewriteRule 的不当行为。 关联访问时间以查看最初进入的请求路径,以及 Apache 无法解析到的路径/文件(错误 404/500)。
error.log
access.log
这并不能告诉您哪个 RewriteRule 是罪魁祸首。但是,无法进入的最终路径可能会泄露进一步检查的位置。 否则,请禁用规则,直到获得一些可预测的路径。
/docroot/21-.itle?index.php
启用 RewriteLog
请参阅 Apache RewriteLog 文档。对于调试,您可以在 vhost 部分中启用它:
# 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'
这有助于缩小过于通用的规则和正则表达式事故的范围。
在问自己的问题之前
您可能知道,Stack Overflow 非常适合在mod_rewrite上提问。通过包括先前的研究和尝试(避免多余的答案),展示基本的正则表达式理解,以及:
- 包括输入 URL 的完整示例、虚假重写的目标路径、您的真实目录结构。
- 完整的 RewriteRule 集,但也挑出假定有缺陷的集。
- Apache 和 PHP 版本、操作系统类型、文件系统、DOCUMENT_ROOT 和 PHP 环境(如果涉及参数不匹配)。
$_SERVER
- 摘自 your 和 以验证现有规则解析的内容。更好的是,总结一下。
access.log
error.log
rewrite.log
这样可以更快、更准确地找到答案,并使它们对其他人更有用。
评论你的
.htaccess
如果从某处复制示例,请注意包含 .虽然省略归因只是不礼貌, 它通常真的会伤害以后的维护。记录任何代码或教程源。特别是,虽然不熟悉,但你应该 更感兴趣的是不要把它们当作魔法黑匣子。
# comment and origin link
它不是“SEO”-URL
免责声明:只是一个宠物的烦恼。你经常听到漂亮的URL重写方案,称为“SEO”链接或其他东西。虽然这对于谷歌搜索示例很有用,但它是过时的用词不当。
没有一个现代搜索引擎真正受到路径段或查询字符串的干扰。旧的搜索引擎,如AltaVista,确实避免了抓取具有潜在模糊访问路径的网站。现代爬虫通常甚至渴望获得深网资源。
.html
.php
?id=123
从概念上讲,“漂亮”的 URL 应该用于使网站对用户友好。
- 具有可读且明显的资源方案。
- 确保 URL 的生存期较长(又名永久链接)。
- 通过提供可发现性。
/common/tree/nesting
但是,不要牺牲墨守成规的独特要求。
工具
有各种在线工具可以为大多数 GET 参数化的 URL 生成 RewriteRules:
- http://www.generateit.net/mod-rewrite/index.php
- http://www.ipdistance.com/mod_rewrite.php
- http://webtools.live2support.com/misc_rewrite.php
大多数情况下只是输出通用占位符,但对于琐碎的网站来说可能就足够了。[^/]+
评论
关于 URL 重写的一个常见问题是这样的:
我目前有如下所示的 URL:
我把它们做得很漂亮:
通过在我的.htaccess文件中使用它:
RewriteRule my-blog/(\d+)--i-found-the-answer my-blog/entry.php?id=$1
但我希望它们看起来像这样:
如何更改我的 .htaccess 文件以使其工作?
简单的答案是你不能。
重写规则不会让丑陋的 URL 变得漂亮,而是会让漂亮的 URL 变得丑陋
每当您在 Web 浏览器中键入 URL、点击链接或显示引用图像的页面等时,浏览器都会发出对特定 URL 的请求。该请求最终到达 Web 服务器,Web 服务器给出响应。
重写规则只是一个规则,它说“当浏览器请求一个看起来像 X 的 URL 时,给他们与请求 Y 相同的响应”。
当我们制定规则来处理“漂亮的 URL”时,请求是漂亮的 URL,而响应是基于内部丑陋的 URL。它不能反过来,因为我们是在服务器上编写规则,而服务器看到的只是浏览器发送的请求。
您不能使用您没有的信息
给定重写规则的基本模型,想象一下您正在向人类发出指令。你可以说:
- 如果您在请求中看到数字,例如“http://example.com/my-blog/42--i-found-the-answer”中的“42”,请将该数字放在“my-blog/entry.php?id=”的末尾
但是,如果请求中没有信息,则说明将没有任何意义:
- 如果请求中包含“my-blog”,例如“http://example.com/my-blog/i-found-the-answer”,请在“my-blog/entry.php?id=”的末尾输入正确的数字
阅读这些说明的人会说:“对不起,我怎么知道正确的数字是什么?
重定向:“此 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]
重要的区别在于标志,这意味着此规则实际上是一个重定向 - 而不是“提供来自此 URL 的响应”,而是“告诉浏览器加载此 URL”。[R]
你可以把这想象成那些自动回复的电子邮件之一,说“对不起,Joe Bloggs 目前正在度假;请将您的信息发送给简·史密斯。同样,上面的重定向告诉浏览器“对不起,没有内容;请改为请求。http://example.com/my-blog/entry.php?id=42
http://example.com/my-blog/42--i-found-the-answer
这个类比的重要一点是,如果实际上没有叫简·史密斯的人在那里工作,或者如果他们不知道如何回答乔·博格斯通常处理的问题,那么上述信息就没有多大用处。同样,如果您告诉浏览器请求的 URL 实际上没有做任何有用的事情,则重定向是没有用的。一旦浏览器遵循重定向,它将发出新的请求,当服务器收到新请求时,它仍然不知道ID号是什么。
但是有些网站会这样做,所以它一定是可能的!
Web 服务器仅包含请求中存在的信息,但它如何使用该信息取决于您。
例如,您可以直接将其 URL 存储在数据库中,然后编写一些代码直接在 PHP、Python、node.js 等中进行匹配,而不是按 ID 查找博客文章。或者,您可以让相同的 URL 根据用户在浏览器中设置的语言或基于 cookie 等显示不同的内容。
您可以做的另一件事是使用带有 POST 方法而不是 GET 方法的表单(或 API 请求)。这意味着附加信息将在请求的“正文”中发送,与 URL 分开。它仍然必须发送,但它在浏览器中不那么明显,不会包含在书签中等。
但是你不能在 .htaccess 文件中写一行来创造奇迹。
评论
reference-mod-rewrite-url-rewriting-explained
/questions/20563772/reference-mod-rewrite-url-rewriting-explained
.htaccess
mod-rewrite