参考资料:mod_rewrite、URL 重写和“漂亮链接”解释

Reference: mod_rewrite, URL rewriting and "pretty links" explained

提问人:deceze 提问时间:12/13/2013 最后编辑:Peter Mortensendeceze 更新时间:3/11/2022 访问量:47977

问:

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

其他常见名称、别名、干净 URL 的术语:RESTful URL、用户友好的 URL、SEO 友好的 URL、slugging 和 MVC URL(可能是用词不当)

apache .htaccess mod-rewrite friendly-url 正则表达式

评论

3赞 Mike B 12/13/2013
Slug 或 Slugging 是漂亮 url 的另一个常见别名/术语。
2赞 deceze 12/13/2013
@Mike 有点,但蛞蝓通常是漂亮 URL 的一部分。例如,当一篇文章的标题变成一个 URL 友好的形式,然后充当该文章的标识符时,slug 就非常具体了。蛞蝓也是如此,漂亮的URL也是如此。reference-mod-rewrite-url-rewriting-explained/questions/20563772/reference-mod-rewrite-url-rewriting-explained
2赞 Mike Rockétt 2/13/2016
我认为应该更新 and 标签以包含指向此问题的链接,因为它涵盖了定期询问的大部分内容。思潮?.htaccessmod-rewrite
0赞 Amit Verma 6/9/2021
要学习 Apache mod-rewrite 的一些基础知识,您可以按照这个简短的教程进行操作 helponnet.com/2021/04/15/htaccess-tutorial-for-beginers
0赞 Adam Winter 4/11/2023
“MVC URL”是“用词不当”是什么意思?

答:

127赞 deceze 12/13/2013 #1

要了解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&amp;very=pretty">

mod_rewrite无能为力,让它变得漂亮。为了使它成为一个漂亮的链接,你必须:

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

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

(可以结合使用mod_substitute来转换传出的 HTML 页面及其包含的链接。尽管这通常比更新您的 HTML 资源要多得多。

您可以做很多事情mod_rewrite并且可以创建非常复杂的匹配规则,包括链接多个重写、将请求代理到完全不同的服务或机器、返回特定的 HTTP 状态代码作为响应、重定向请求等。它非常强大,如果您了解基本的 HTTP 请求-响应机制,则可以很好地使用它。它不会自动使您的链接漂亮。

有关所有可能的标志和选项,请参阅官方文档

评论

7赞 Darsstar 12/13/2013
也许可以提到 2.2.16 版中引入的 FallbackResource 指令,作为重写调度程序的首选方式
93赞 Nick 12/13/2013 #2

为了扩展 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 个部分:

  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/...
  • 每组括号代表一个正则表达式,我们可以将其捕获为规则右侧的变量。在此示例中:(…)
    • 第一组括号 - - 匹配长度至少为 1 个字符且仅包含数值(即 0-9)的字符串。这可以在规则的右侧引用([0-9]+)$1
    • 第二组括号匹配长度至少为 1 个字符的字符串,仅包含字母数字字符(A-Z、a-z 或 0-9)或 or(注意使用反斜杠转义,因为如果不转义,这将作为正则表达式重复字符执行)。这可以在规则的右侧引用-++$2
  • ?表示前面的字符是可选的,因此在这种情况下,两者都会重写到同一位置/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将检查当前服务器上是否存在该文件,并且仅在不存在时执行重写
  • 重写将尝试在另一个域上加载相同的文件
9赞 mario 7/5/2015 #3

mod_rewrite的替代品

许多基本的虚拟 URL 方案可以在不使用 RewriteRules 的情况下实现。Apache 允许在没有扩展的情况下调用 PHP 脚本,并使用虚拟参数。.phpPATH_INFO

  1. 使用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

  2. 启用 MultiView 以隐藏扩展.php

    避免在 URL 中使用“文件扩展名”的最简单选项是启用:.php

    Options +MultiViews
    

    由于 base name 匹配,这会使 Apache 选择 HTTP 请求。这与上述PATH_INFO功能配合得很好。因此,您可以只使用像 .如果您有一个具有多个 PHP 调用点/脚本的传统 Web 应用程序,这是有道理的。article.php/articlehttp://example.com/article/virtual/title

    请注意,MultiView 具有不同/更广泛的用途。它会产生非常小的性能损失,因为 Apache 总是会查找具有匹配基名的其他文件。它实际上是用于内容协商的,因此浏览器会在可用资源(例如、、)中获得最佳选择。article.en.phparticle.fr.phparticle.jp.mp4

  3. SetType 或 SetHandler 用于无扩展脚本.php

    避免在 URL 中携带后缀的更直接的方法是为其他文件方案配置 PHP 处理程序。最简单的选项是通过以下方式覆盖默认的 MIME/处理程序类型:.php.htaccess

    DefaultType application/x-httpd-php
    

    这样,您可以将脚本重命名为 just(不带扩展名),但仍将其作为 PHP 脚本处理。article.phparticle

    现在,这可能会对安全性和性能产生一些影响,因为所有无扩展名的文件现在都将通过 PHP 进行管道传输。因此,您也可以仅为单个文件设置此行为:

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

    这在某种程度上取决于您的服务器设置和使用的 PHP SAPI。常见的替代方法包括 或 。ForceType application/x-httpd-phpAddHandler php5-script

    同样请注意,此类设置会从一个文件夹传播到子文件夹。您始终应该禁用静态资源的脚本执行(和/等),以及上传/目录等。.htaccessSetHandler NoneOptions -Execphp_flag engine off

  4. 其他 Apache 重写方案

    在众多选项中,Apache 提供了一些功能,这些功能有时与 RewriteRules 一样有效。请注意,其中大多数必须在一个部分中设置,而不是在每个目录的配置文件中设置。mod_aliasmod_rewrite<VirtualHost>.htaccess

    • ScriptAliasMatch 主要用于 CGI 脚本,但也应该适用于 PHP。它允许正则表达式,就像任何 .事实上,它可能是配置包罗万象的前置控制器的最强大的选择。RewriteRule

    • 普通的别名也有助于一些简单的重写方案。

    • 即使是普通的 ErrorDocument 指令也可以用来让 PHP 脚本处理虚拟路径。请注意,这是一个笨拙的解决方法,但是,除了 GET 请求之外,禁止任何内容,并且根据定义会淹没 error.log。

    有关更多提示,请参阅 http://httpd.apache.org/docs/2.2/urlmapping.html

49赞 mario 7/8/2015 #4

引用

Stack Overflow 还有许多其他很棒的入门资源:

对新手友好的正则表达式甚至概述:

经常使用的占位符

  • .*匹配任何内容,即使是空字符串。您不希望在任何地方都使用此模式,但通常在最后一个回退规则中。
  • [^/]+更常用于路径段。它匹配除正斜杠以外的任何内容。
  • \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

频繁重复:

普遍存在的陷阱.htaccess

现在对此持保留态度。并非每个建议都可以推广到所有情况。 这只是对众所周知的和一些不明显的绊脚石的简单总结:

  • Enable 和mod_rewrite.htaccess

    若要在每个目录配置文件中实际使用 RewriteRules,必须:

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

    • 显然,在您的模块部分启用了mod_rewritehttpd.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 错误来装饰您的用户。(如果你仔细想想,并没有那么人性化。500404
    • 实际上,它只是抑制更有用的日志条目或服务器通知邮件。你不会更聪明地解释为什么你的 RewriteRules 永远不会起作用。

    看似诱人的普遍保障措施在实践中往往被证明是一个障碍。

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

    许多复制+粘贴示例都包含指令。无论如何,这恰好是隐式默认值。所以你实际上并不需要这个。这是花哨的 VirtualHost 重写方案的解决方法,并且错误地猜测了一些共享主机商的DOCUMENT_ROOT路径。RewriteBase /

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

    参见 RewriteBase 如何在 .htaccess 中工作

  • 当虚拟路径重叠时禁用MultiViews

    URL 重写主要用于支持虚拟传入路径。通常,您只有一个调度程序脚本 () 或几个单独的处理程序 (, , , ...)。后者可能与类似的虚拟 RewriteRule 路径发生冲突index.phparticles.phpblog.phpwiki.php

    例如,请求可以隐式映射到PATH_INFO。你要么必须用普通的 + 来保护你的规则,和/或禁用PATH_INFO支持,或者只是禁用 ./article/123article.php/123RewriteCond!-f!-dOptions -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  # ←──﹪₁──┘
    

    另请参阅如何将查询字符串变量与mod_rewrite匹配?

  • .htaccess与。<VirtualHost>

    如果您在每个目录的配置文件中使用 RewriteRules,那么担心正则表达式性能是没有意义的。Apache 保留 编译的 PCRE 模式比具有通用路由框架的 PHP 进程更长。但是,对于高流量网站,您应该考虑 在经过实战测试后,将规则集移动到虚拟主机服务器配置中。

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

  • 每当某些东西不起作用

    不用担心。

    • 比较 access.logerror.log

      通常,您只需查看 和 即可弄清楚 RewriteRule 的不当行为。 关联访问时间以查看最初进入的请求路径,以及 Apache 无法解析到的路径/文件(错误 404/500)。error.logaccess.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'
      

      这有助于缩小过于通用的规则和正则表达式事故的范围。

      另请参阅:
      ·.htaccess 无法正常工作(mod_rewrite)
      ·调试 .htaccess 重写规则的提示

    • 在问自己的问题之前

      您可能知道,Stack Overflow 非常适合在mod_rewrite上提问。通过包括先前的研究和尝试(避免多余的答案),展示基本的理解,以及:

      • 包括输入 URL 的完整示例、虚假重写的目标路径、您的真实目录结构。
      • 完整的 RewriteRule 集,但也挑出假定有缺陷的集。
      • Apache 和 PHP 版本、操作系统类型、文件系统、DOCUMENT_ROOT 和 PHP 环境(如果涉及参数不匹配)。$_SERVER
      • 摘自 your 和 以验证现有规则解析的内容。更好的是,总结一下。access.logerror.logrewrite.log

      这样可以更快、更准确地找到答案,并使它们对其他人更有用。

  • 评论你的.htaccess

    如果从某处复制示例,请注意包含 .虽然省略归因只是不礼貌, 它通常真的会伤害以后的维护。记录任何代码或教程源。特别是,虽然不熟悉,但你应该 更感兴趣的是不要把它们当作魔法黑匣子。# comment and origin link

  • 它不是“SEO”-URL

    免责声明:只是一个宠物的烦恼。你经常听到漂亮的URL重写方案,称为“SEO”链接或其他东西。虽然这对于谷歌搜索示例很有用,但它是过时的用词不当。

    没有一个现代搜索引擎真正受到路径段或查询字符串的干扰。旧的搜索引擎,如AltaVista,确实避免了抓取具有潜在模糊访问路径的网站。现代爬虫通常甚至渴望获得深网资源。.html.php?id=123

    从概念上讲,“漂亮”的 URL 应该用于使网站对用户友好

    1. 具有可读且明显的资源方案。
    2. 确保 URL 的生存期较长(又名永久链接)。
    3. 通过提供可发现性。/common/tree/nesting

    但是,不要牺牲墨守成规的独特要求。

工具

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

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

评论

0赞 mario 7/8/2015
仍然需要一些重写,更多的链接,而且许多副标题有些令人讨厌。这里与其他答案有一些重叠,所以也许可以减少。不过,这主要是关于视觉示例,以及常见的陷阱列表。
3赞 Rizier123 7/9/2015
好久没看到这么美的答案了!当我阅读它时,我的眼睛在发光。请不要停止发布此类答案:)
1赞 brz 12/9/2016
优秀的帖子。让我很快理解了mod_rewrite的基本概念!
2赞 IMSoP 4/25/2021 #5

关于 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=42http://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 文件中写一行来创造奇迹。