参考:mod_rewrite,URL重写和“漂亮链接”的解释

“漂亮的链接”是一个经常被要求的话题,但是它很less被充分解释。 mod_rewrite是制作“漂亮链接”的一种方式,但是它很复杂,它的语法非常简洁,难以理解,并且文档假定HTTP有一定程度的熟练度。 有人可以用简单的术语来解释“漂亮链接”是如何工作的,以及如何使用mod_rewrite来创build它们?

其他常见的名称,别名,干净的URL的术语:REST风格的URL,用户友好的URL,search引擎优化友好的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请求的端口。 你可以自己写一个。 这段文字的目的是为了让你脱离URLs直接等同于文件的任何概念,这是真正重要的理解。 🙂

大多数Web服务器的默认configuration是查找与硬盘上的URL匹配的文件。 如果服务器的文档根目录设置为/var/www ,则可能会查看/var/www/foo/bar.html文件是否存在并提供。 如果文件以“.php”结尾,则会调用PHP解释器, 然后返回结果。 所有这些关联都是完全可configuration的。 一个文件不必以“.php”结尾,以便Web服务器通过PHP解释器运行它,并且该URL不必与磁盘上的任何特定文件相匹配就可以发生。

mod_rewrite是重写内部请求处理的一种方法。 当Web服务器接收到URL /foo/bar的请求时,您可以将该URL 重写为其他内容,然后Web服务器将在磁盘上查找文件以匹配它。 简单的例子:

 RewriteEngine On RewriteRule /foo/bar /foo/baz 

这个规则说每当一个请求匹配“/ foo / bar”时,把它重写为“/ foo / baz”。 这个请求将被处理,就像if /foo/baz被请求一样。 这可以用于各种效果,例如:

 RewriteRule (.*) $1.html 

这条规则匹配任何东西( .* )并捕获它( (..) ),然后重写它以追加“.html”。 换句话说,如果/foo/bar是请求的URL,那么将会像处理/foo/bar.html一样被处理。 有关正则expression式匹配,捕获和replace的更多信息,请参阅http://regular-expressions.info

另一个经常遇到的规则是:

 RewriteRule (.*) index.php?url=$1 

这又一次匹配任何东西,并将其重写到index.php文件中,并在url查询参数中附加最初请求的URL。 也就是说,对于任何和所有的请求来说,文件index.php被执行,这个文件将有权访问$_GET['url']的原始请求,所以它可以做任何事情。

什么mod_rewrite不会做

mod_rewrite不会神奇地使所有的URL“漂亮”。 这是一个常见的误解。 如果你在你的网站上有这个链接:

 <a href="/my/ugly/link.php?is=not&amp;very=pretty"> 

没有什么mod_rewrite可以做到这一点。 为了使这一个漂亮的链接,你必须:

  1. 将链接更改为一个漂亮的链接:

     <a href="/my/pretty/link"> 
  2. 在服务器上使用mod_rewrite来使用上述任何一种方法处理对URL /my/pretty/link的请求。

(可以使用mod_substitute结合来转换传出的HTML页面及其包含的链接,虽然这比更新HTML资源更费力。)

有很多mod_rewrite可以做,可以创build非常复杂的匹配规则,包括链接几个重写,代理请求到一个完全不同的服务或机器,返回特定的HTTP状态代码作为响应,redirect请求等。它是非常强大的,可以用来如果你理解了基本的HTTP请求 – 响应机制,这是非常好的。 它不会自动让你的链接漂亮。

查看所有可能的标志和选项的官方文档 。

为了扩大deceze的答案 ,我想提供一些其他mod_rewritefunction的例子和解释。

下面的所有例子都假定你已经在你的.htaccess文件中包含了RewriteEngine On

重写示例

让我们看看这个例子:

 RewriteRule ^blog/([0-9]+)/([A-Za-z0-9-\+]+)/?$ /blog/index.php?id=$1&title=$2 [NC,L,QSA] 

规则分为4个部分:

  1. RewriteRule – 启动重写规则
  2. ^blog/([0-9]+)/([A-Za-z0-9-\+]+)/?$ – 这就是所谓的模式,不过我只是把它称为左手边的规则 – 你想重写什么
  3. blog/index.php?id=$1&title=$2 – 调用重写规则的替代或右侧 – 要重写的内容
  4. [NC,L,QSA]是重写规则的标志,用逗号分隔,稍后我会进一步解释

上面的重写将允许你链接到/blog/1/foo/ ,它实际上会加载/blog/index.php?id=1&title=foo

规则的左侧

  • ^表示页面名称的开始 – 所以它将重写example.com/blog/...但不是example.com/foo/blog/...
  • 每个(…)圆括号表示一个正则expression式,我们可以在规则的右边捕获一个variables。 在这个例子中:
    • 第一组括号 – ([0-9]+) – 匹配一个长度最less为1个字符且只有数字值(即0-9)的string。 这可以在规则的右侧用$1引用
    • 第二组括号匹配一个长度至less为1个字符的string,只包含字母数字字符(AZ,az或0-9)或-+ (注意+用反斜线转义而不逃脱它将执行作为正则expression式重复字符 )。 这可以在规则的右侧用$2引用
  • ? 意味着前面的字符是可选的,所以在这种情况下, /blog/1/foo//blog/1/foo都会重写到同一个地方
  • $表示这是我们想要匹配的string的末尾

这些选项是在重写规则末尾的方括号中添加以指定某些条件的选项。 再一次,有很多不同的标志,你可以在文档中阅读,但我会通过一些更常见的标志:

 NC 

no case标志意味着重写规则是不区分大小写的,所以对于上面的示例规则来说,这意味着/blog/1/foo//BLOG/1/foo/ (或者这个的任何变体)都将被匹配。

 L 

最后一个标志表示这是应该处理的最后一个规则。 这意味着当且仅当此规则匹配时,在当前的重写处理运行中不会再评估其他规则。 如果规则不匹配,所有其他规则将照常按顺序尝试。 如果您没有设置L标志,则以后所有以下规则将应用于重写的 URL。

 END 

从Apache 2.4开始,你也可以使用[END]标志。 与它匹配的规则将完全终止进一步的别名/重写处理。 (而[L]标志通常可以触发第二轮,例如当重写到子目录或从子目录中重写)。

 QSA 

查询stringappend标志允许我们将额外的variables传递给指定的URL,它将被添加到原始的get参数中。 对于我们的例子来说,这意味着/blog/1/foo/?comments=15会加载/blog/index.php?id=1&title=foo&comments=15

 R 

这个标志不是我在上面的例子中使用的,而是我认为值得一提的。 这允许你指定一个httpredirect,可以包含一个状态码(例如R=301 )。 例如,如果你想在/ myblog / to / blog /上做一个301redirect,你只需要编写一个如下的规则:

 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.前缀www. (如果它不在那里)并执行301redirect。 例如,加载http://example.com/blog/会将您redirect到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会检查当前服务器上是否存在该文件,如果不存在,则只执行重写
  • 重写将尝试在另一个域上加载相同的文件

参考

堆栈溢出有许多其他伟大的资源开始:

  • Serverfault: 你想知道的关于mod_rewrite的一切
    (请记住删除用于.htaccess使用的^/模式前缀中的斜杠。)
  • 做和不在mod_rewrite的隐藏function 。
  • 看看我们最stream行的改写问题和答案。
  • Apache redirect和重映射指南。
  • AskApache 最终的.htaccess指南
  • 和mod重写 标签维基引用 。

甚至对新人友好的正则expression式概述:

  • 我们的正则expression式 标签wiki的语法纲要。
  • 和短的Apache正则expression式总结 。
  • 否则regexp.info为易于理解的基础。

Oft使用的占位符

  • .*匹配任何东西,甚至是一个空string。 你不想在任何地方使用这种模式,但往往在最后的后备规则。
  • [^/]+更常用于path段。 它匹配除了正斜杠之外的任何内容。
  • \d+只能匹配数字string。
  • \w+匹配字母数字字符。 它基本上是[A-Za-z0-9_]简写。
  • [\w\-]+用于“slug”风格的path段,使用字母,数字,短划线 _
  • [\w\-.,]+添加句号和逗号。 喜欢在charclasses中逃脱\-破折号。
  • \. 表示文字时期。 否则. […]是任何符号的占位符。

每个占位符通常都包含在(…)括号中作为捕获组。 而整个模式往往在^………$开始+结束标记。 引用“模式”是可选的。

的RewriteRules

以下示例以PHP为中心,稍微增加一些,更容易适应类似情况。 他们只是总结,往往链接到更多的变化或详细的问答。

  • 静态映射
    /contact/about

    缩短几个页面名称到内部文件scheme是最简单的:

      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 # └───────────────────────────┘ 
  • S-式的占位符
    /article/with-some-title-slug

    您可以轻松地扩展该规则以允许/article/title-string占位符:

      RewriteRule ^article/([\w-]+)$ article-show.php?title=$1 # └────────────────────────────────┘ 

    请注意, 您的脚本 必须能够(或适应)将这些标题映射回数据库ID。 重写规则本身不能创build或猜测信息。

  • 与数字前缀的S </s>
    /readable/123-plus-title

    因此,您经常会在实践中看到混合/article/529-title-slugpath:

      RewriteRule ^article/(\d+)-([\w-]+)$ article.php?id=$1&title=$2 # └───────────────────────────────┘ 

    现在你可以跳过title=$2 ,因为你的脚本通常依赖于数据库ID。 -title-slug已经成为任意的URL装饰。

  • 统一与替代名单
    /foo/… /bar/… /baz/…

    如果您对多个虚拟页面path有类似的规则,那么可以使用|来匹配和压缩它们 替代名单。 再次将它们重新分配给内部的GET参数:

      # ┌─────────────────────────┐ RewriteRule ^(blog|post|user)/(\w+)$ disp.php?type=$1&id=$2 # └───────────────────────────────────┘ 

    如果这个过于复杂,你可以把它们分解成单独的RewriteRule

  • 将相关的URL分派到不同的后端
    /date/SWITCH/backend

    替代列表更实际的用法是将请求path映射到不同的脚本。 例如,根据date为较旧和较新的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参数。

  • 其他分隔符而不是/path斜杠
    /user-123-name

    你最常看到RewriteRules模拟虚拟目录结构。 但是你不是被迫做出创造力的。 你也可以使用连字符来分割或结构。

      RewriteRule ^user-(\d+)$ show.php?what=user&id=$1 # └──────────────────────────────┘ # This could use `(\w+)` alternatively for user names instead of ids. 

    对于common /wiki:section:Page_Namescheme:

      RewriteRule ^wiki:(\w+):(\w+)$ wiki.php?sect=$1&page=$2 # └─────┼────────────────────┘ │ # └────────────────────────────┘ 

    偶尔也可以用/ -delimiters和:或者. 甚至在相同的规则。 或者再次有两个RewriteRules将变体映射到不同的脚本。

  • 可选尾随/斜杠
    /dir = /dir/

    当select目录风格的path时,你可以使它可以和没有最后/

      RewriteRule ^blog/([\w-]+)/?$ blog/show.php?id=$1 # ┗┛ 

    现在可以处理http://example.com/blog/123/blog/123/ 。 而/?$方法很容易附加到任何其他的RewriteRule。

  • 灵活的虚拟path段
    .*/.*/.*/.*

    您遇到的大多数规则会将受限制的/…/资源path段映射到各个GET参数。 有些脚本处理可变数量的选项 。 Apache的正则expression式引擎不允许可选的任意数量。 但是你可以很容易地把它扩展成一个规则块:

      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 # └─────┴─────┴───────────────────┴────┴────┘ 

    如果您最多需要五个path段,则将此scheme复制到五个规则中。 你当然可以使用一个更具体的[^/]+占位符。 这里的顺序并不重要,因为两者都不重叠。 所以先使用最常用的path是可以的。

    另外,你可以通过这里使用PHP数组参数来通过?p[]=$1&p[]=$2&p[]=3查询string – 如果你的脚本只是喜欢它们的预分割。 (尽pipe使用catch-all规则更为常见,并让脚本自身将段扩大到REQUEST_URI之外。)

    另请参阅: 如何将URLpath段转换为查询string键值对?

  • 可选分段
    prefix/opt?/.*

    一个常见的变化是规则中有可选的前缀。 这通常是有意义的,如果你有静态string或约束更多的占位符:

      RewriteRule ^(\w+)(?:/([^/]+))?/(\w+)$ ?main=$1&opt=$2&suffix=$3 

    现在更复杂的模式(?:/([^/])+)? 那么简单地包装一个非捕获 (?:…)组,并使其可选)? 。 包含的占位符([^/]+)将是replace模式$2 ,但如果没有中间/…/path则为空。

  • 捕获剩下的部分
    /prefix/123-capture/…/*/…whatever…

    如前所述,你不经常想要太泛化的重写模式。 但是,有时将静态和具体的比较与a .*相结合是有意义的。

      RewriteRule ^(specific)/prefix/(\d+)(/.*)?$ speci.php?id=$2&otherparams=$2 

    这个可选的任何/…/…/…尾随path段。 这当然需要处理脚本来分解它们,并且可以自行提取参数(这是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 

    其他常见用途是将过时的.htmlpath重新映射到更新的.php处理程序,或仅针对个别(实际/真实)文件别名目录名称。

  • 乒乓(重新改写和重写)
    /ugly.html /pretty

    所以在某些时候,你正在重写你的HTML页面,以便携带漂亮的链接。 同时,您仍然会收到路的请求,有时甚至会收到书签。 作为解决方法 ,您可以乒乓浏览器来显示/build立新的URL。

    这种常见的技巧包括每当传入的URL遵循过时/丑陋的命名scheme时发送30x /位置redirect 。 然后,浏览器将重新请求新的/漂亮的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版本,您可以使用其他解决方法,除了重新映射查询string参数,例如: 将丑陋redirect到漂亮的URL,重新映射到丑陋的path,没有无限循环

  • 空间模式
    /this+that+

    在浏览器地址栏中并不是那么漂亮 ,但是可以在URL中使用空格。 对于重写模式,请使用反斜线转义的空格。 否则只是"引用整个模式或替代:

      RewriteRule "^this [\w ]+/(.*)$" "index.php?id=$1" [L] 

    客户使用+%20作为空格序列化URL。 然而在RewriteRules中,它们是用所有相对path段的文字字符来解释的。

频繁重复:

  • Catch-all用于中央调度程序 /前端控制器脚本

      RewriteCond %{REQUEST_URI} !-f RewriteCond %{REQUEST_URI} !-d RewriteRule ^.*$ index.php [L] 

    这是PHP框架或WebCMS /门户脚本经常使用的。 然后使用$_SERVER["REQUEST_URI"]在PHP中处理实际的path分割。 所以在概念上,它几乎是URL处理“per mod_rewrite”的反义词。 (只需使用FallBackResource 。)

  • 删除www. 从主机名

    请注意,这不会复制查询string等。

      # ┌──────────┐ RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] │ RewriteRule ^(.*)$ http://%1/$1 [R=301,L] │ # ↓ └───┼────────────┘ # └───────────────┘ 

    也可以看看:
    · .htaccess中的不同协议的URL重写
    · 通用htaccessredirectwww到非www
    · .htaccess – 如何强制“www。” 以通用的方式?

    请注意,RewriteCond / RewriteRule组合可以更复杂,匹配( %1$1 )在两个方向上进行交互:

    RewriteRule和RewriteCond之间的引用%1和$ 2,%3
    Apache手册 – mod_rewrite介绍 ,版权所有2015 Apache软件基金会,AL-2.0

  • redirect到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扩展名

  • 将旧的.htmlpath别名为.php脚本

    请参阅: http : //httpd.apache.org/docs/2.4/rewrite/remapping.html#backward-compatibility

  • 从像“/ page”这样的URL重写为诸如“/index.php/page”的脚本

    请参阅mod_rewrite,php和.htaccess文件

  • 将子域redirect到一个文件夹

    看看我怎样才能让我的htaccess工作(子域)?

普遍的.htaccess陷阱

现在拿一点盐。 并不是每个build议都可以推广到所有的情况。 这只是一个众所周知的和一些不明显的绊脚石的简单总结:

  • 启用mod_rewrite.htaccess

    要在每个目录configuration文件中实际使用RewriteRules,您必须:

    • 检查您的服务器是否启用了AllowOverride All 。 否则,您的每个目录.htaccess指令将被忽略,并且RewriteRules将不起作用。

    • 显然你的httpd.conf模块部分启用了mod_rewrite

    • RewriteEngine On仍然保留每个规则列表。 虽然mod_rewrite在<VirtualHost><Directory>部分中隐式激活,但每个目录.htaccess文件都需要单独调用它。

  • 主导的斜线^/不匹配

    你不应该用^/通常启动你的.htaccess RewriteRule模式:

      RewriteRule ^/article/\d+$ … ↑ 

    这在老的教程中经常出现。 对于古代的Apache 1.x版本,它过去是正确的。 现在,请求path在.htaccess RewriteRules中方便地完全与目录相关 。 只要离开领导/出。

    请注意,尽pipe在<VirtualHost>部分中,前导斜杠仍然正确。 这就是为什么你经常看到它^/? 可select规则奇偶校验。
    ·或者当使用RewriteCond %{REQUEST_URI} ,仍然可以匹配前导/
    另请参见Webmaster.SE:何时在mod_rewrite模式中使用前导斜杠(/)?

  • <IfModule *> wrapper begone!

    你可能在很多例子中看到了这个:

     <IfModule mod_rewrite.c> Rewrite… </IfModule> 
    • 它在<VirtualHost>部分中合理的,如果它与另一个后备选项(如ScriptAliasMatch)结合使用。 (但没有人这样做)。
    • 它通常分布在许多开源项目的默认.htaccess规则集中。 这只是意味着后备,并保持“丑陋”的URL作为默认工作。

    但是,你不需要通常在你自己的.htaccess文件中。

    • 首先,mod_rewrite不会随机分离。 (如果是的话,你会遇到更大的问题)。
    • 如果它真的被禁用,您的RewriteRules仍然无法正常工作。
    • 这是为了防止HTTP 500错误。 它通常完成的是用HTTP 404错误来改变你的用户。 (如果你仔细想想,不要太多用户友好。)
    • 实际上,它只是压制更有用的日志条目或服务器通知邮件。 你不会明白为什么你的RewriteRules永远不会工作。

    似乎是诱惑作为一般保障,常常成为实践中的一个障碍。

  • 除非需要,否则不要使用RewriteBase

    许多复制+粘贴示例包含一个RewriteBase /指令。 无论如何,这恰好是隐含的默认值。 所以你实际上并不需要这个。 这是一个奇怪的VirtualHost重写scheme的解决方法,并为一些共享主机误导DOCUMENT_ROOTpath。

    在更深的子目录中使用单个Web应用程序是有意义的。 在这种情况下可以缩短RewriteRule模式。 通常,最好在每个目录规则集中使用相对path说明符。

    另请参见RewriteBase如何在.htaccess中工作

  • 虚拟path重叠时禁用MultiViews

    URL重写主要用于支持虚拟传入path。 通常你只有一个调度程序脚本( index.php )或一些个人处理程序( articles.phpblog.phpwiki.php ,…)。 后者可能与类似的虚拟RewriteRulepath冲突 。

    例如,对/article/123请求可以隐式映射到/123 PATH_INFO的article.php 。 你要么保守你的规则,然后用普通的RewriteCond !-f + !-d和/或禁用PATH_INFO支持,或者只是禁用Options -MultiViews

    这不是说你一定这么做。 内容谈判只是虚拟资源的自动化。

  • 订购是重要的

    如果你还没有看过关于mod_rewrite的一切, 结合多个RewriteRules通常会导致交互。 根据[L]国旗这不是习惯性的阻止,而是一个你熟悉的scheme。 您可以重新重新编写从一个规则到另一个规则的虚拟path,直到它到达一个实际的目标处理程序。

    尽pipe如此,你仍然希望在早期规则中使用最具体的规则(固定string/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>部分即可。 这隐含地重新绑定了他们以前的相关引用。

    您可以select制作更多的RewriteRules来将.css.pngpath重新绑定到其原始位置。 但是这两者都不需要,或者会导致额外的redirect并阻碍caching。

    另见: 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

    你不能匹配RewriteRule index.php\?x=y ,因为mod_rewrite只是比较每个默认的相对path。 您可以通过以下方式单独匹配:

      RewriteCond %{QUERY_STRING} \b(?:param)=([^&]+)(?:&|$) RewriteRule ^add/(.+)$ add/%1/$1 # ←──﹪₁──┘ 

    另请参见如何将查询stringvariables与mod_rewrite匹配?

  • .htaccess<VirtualHost>

    如果您在每个目录configuration文件中使用RewriteRules,那么担心正则expression式是毫无意义的。 Apache保留编译后的PCRE模式比使用通用路由框架的PHP过程更长。 对于高stream量的站点,您应该考虑将规则集移到虚拟主机服务器configuration中,一旦它们经过testing。

    在这种情况下,更喜欢可选的^/? 目录分隔符前缀。 这允许在PerDir和服务器configuration文件之间自由移动RewriteRules。

  • 每当有什么不工作

    不要烦恼

    • 比较access.logerror.log

      通常你可以通过查看你的error.logaccess.log来找出一个RewriteRule的错误。 将访问时间关联起来,以查看最初进入哪个请求path以及Apache无法parsing哪个path/文件(错误404/500)。

      这并不能告诉你哪个RewriteRule是罪魁祸首。 但像/docroot/21-.itle?index.php这样无法访问的最终path可能会放弃进一步检查的位置。 否则禁用规则,直到你得到一些可预测的path。

    • 启用RewriteLog

      请参阅Apache RewriteLog文档。 对于debugging,您可以在虚拟主机部分启用它:

       # Apache 2.2 RewriteLogLevel 5 RewriteLog /tmp/rewrite.log # Apache 2.4 LogLevel alert rewrite:trace5 #ErrorLog /tmp/rewrite.log 

      这就产生了每条规则如何修改传入请求path的详细摘要:

       [..] 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' 

      这有助于缩小过于一般的规则和正则expression式意外事件。

      也可以看看:
      · .htaccess不能正常工作(mod_rewrite)
      · 提示debugging.htaccess重写规则

    • 在问你自己的问题之前

      正如你可能知道的,Stack Overflow非常适合在mod_rewrite上提出问题。 通过包括事先研究和尝试(避免多余的答案),使他们在主题上 ,展示基本的正则expression式的理解,并且:

      • 包括inputURL的完整示例,错误地重写目标path,您的真实目录结构。
      • 完整的RewriteRule集合,但单挑出有缺陷的一个。
      • Apache和PHP版本,操作系统types,文件系统,DOCUMENT_ROOT和PHP $_SERVER环境,如果它是关于参数不匹配。
      • 从你的access.logerror.log摘录来validation现有的规则解决了什么。 更好的是,一个rewrite.log总结。

      这样可以更快,更准确地回答问题,并使其他人更有用。

  • 评论你的.htaccess

    如果您从某处复制示例,请注意包含# comment and origin link 。 虽然忽略归属仅仅是不礼貌的行为,但它往往会在以后损害维护。 logging任何代码或教程来源。 特别是在不受pipe制的情况下,你应该对不把它们当作魔法黑匣子来对待。

  • 这不是“SEO” – url

    免责声明:只是一个宠物peeve。 你经常会听到漂亮的URL重写scheme被称为“SEO”链接或什么的。 虽然这对于谷歌search的例子很有用,但这是一个过时的名词错误。

    没有一个现代的search引擎真的被path段中的.html.php打扰,或者?id=123查询string。 像AltaVista这样的老版本的search引擎确实可以避免爬取网站,这些网站可能存在隐蔽的访问path。 现代爬虫往往甚至渴望深度networking资源。

    概念上应该使用什么“漂亮的”url来使网站易于使用

    1. 有可读的和明显的资源计划。
    2. 确保URL长寿(AKA 永久链接 )。
    3. 通过/common/tree/nesting提供可发现性。

    但是不要为了顺从而牺牲独特的要求。

工具

有许多在线工具可以为大多数GET参数化的URL生成RewriteRules:

大多数情况下只是输出[^/]+通用的占位符,但可能足够琐碎的网站。

mod_rewrite的替代品

许多基本的虚拟URLscheme可以在不使用RewriteRules的情况下实现。 Apache allows PHP scripts to be invoked without .php extension, and with a virtual PATH_INFO argument.

  1. Use the PATH_INFO , Luke

    Nowadays AcceptPathInfo On is often enabled by default. Which basically allows .php and other resource URLs to carry a virtual argument:

     http://example.com/script.php/virtual/path 

    Now this /virtual/path shows up in PHP as $_SERVER["PATH_INFO"] where you can handle any extra arguments however you like.

    This isn't as convenient as having Apache separate input path segments into $1 , $2 , $3 and passing them as distinct $_GET variables to PHP. It's merely emulating "pretty URLs" with less configuration effort.

  2. Enable MultiViews to hide the .php extension

    The simplest option to also eschew .php "file extensions" in URLs is enabling:

     Options +MultiViews 

    This has Apache select article.php for HTTP requests on /article due to the matching basename. And this works well together with the aforementioned PATH_INFO feature. So you can just use URLs like http://example.com/article/virtual/title . Which makes sense if you have a traditional web application with multiple PHP invocation points/scripts.

    Note that MultiViews has a different/broader purpose though. It incurs a very minor performance penalty, because Apache always looks for other files with matching basenames. It's actually meant for Content-Negotiation , so browsers receive the best alternative among available resources (such as article.en.php , article.fr.php , article.jp.mp4 ).

  3. SetType or SetHandler for extensionless .php scripts

    A more directed approach to avoid carrying around .php suffixes in URLs is configuring the PHP handler for other file schemes. The simplest option is overriding the default MIME/handler type via .htaccess :

     DefaultType application/x-httpd-php 

    This way you could just rename your article.php script to just article (without extension), but still have it processed as PHP script.

    Now this can have some security and performance implications, because all extensionless files would be piped through PHP now. Therefore you can alternatively set this behaviour for individual files only:

     <Files article> SetHandler application/x-httpd-php # or SetType </Files> 

    This is somewhat dependent on your server setup and the used PHP SAPI. Common alternatives include ForceType application/x-httpd-php or AddHandler php5-script .

    Again take note that such settings propagate from one .htaccess to subfolders. You always should disable script execution ( SetHandler None and Options -Exec or php_flag engine off etc.) for static resources, and upload/ directories etc.

  4. Other Apache rewriting schemes

    Among its many options, Apache provides mod_alias features – which sometimes work just as well as mod_rewrite s RewriteRules. Note that most of those must be set up in a <VirtualHost> section however, not in per-directory .htaccess config files.

    • ScriptAliasMatch is primarily for CGI scripts, but also ought to works for PHP. It allows regexps just like any RewriteRule . In fact it's perhaps the most robust option to configurate a catch-all front controller.

    • And a plain Alias helps with a few simple rewriting schemes as well.

    • Even a plain ErrorDocument directive could be used to let a PHP script handle virtual paths. Note that this is a kludgy workaround however, prohibits anything but GET requests, and floods the error.log by definition.

    See http://httpd.apache.org/docs/2.2/urlmapping.html for further tips.