繁体   English   中英

参考: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 /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不做什么

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 请求-响应机制,那就太好了。 不会自动使您的链接漂亮。

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

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

  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 example.com/foo/blog/...
  • 每组(…)括号代表一个正则表达式,我们可以将其捕获为规则右侧的变量。 在这个例子中:
    • 第一组括号 - ([0-9]+) - 匹配长度至少为 1 个字符且仅具有数值(即 0-9)的字符串。 这可以用规则右侧的$1来引用
    • 第二组括号匹配长度至少为 1 个字符的字符串,仅包含字母数字字符(AZ、az 或 0-9)或-+ (注意+用反斜杠转义,因为没有转义它会执行作为正则表达式重复字符)。 这可以用规则右侧的$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 还有许多其他很棒的入门资源:

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

常用占位符

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

  • 将相关 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 # ┗┛

    现在这处理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 中扩展出来。)

    另请参阅:如何将我的 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

    这可选择任何/…/…/…尾随路径段。 然后当然需要处理脚本将它们拆分,并对提取的参数本身进行可变化(这就是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 )在两个方向上交互,甚至:

    RewriteRule 和 RewriteCond 之间的引用 %1 和 $2、%3
    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

  • “删除” 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”之类的脚本

    请参阅mod_rewrite、php 和 .htaccess 文件

  • 将子域重定向到文件夹

    请参阅如何让我的 htaccess 正常工作(子域)?

普遍存在.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文件中使用它。

    • 首先, mod_rewrite 不会随机脱离。 (如果是这样,你会有更大的问题)。
    • 如果它真的被禁用,你的 RewriteRules 仍然无法正常工作。
    • 它旨在防止 HTTP 500错误。 它通常完成的是用 HTTP 404错误代替您的用户。 (如果您考虑一下,它不会对用户更加友好。)
    • 实际上,它只是抑制了更有用的日志条目或服务器通知邮件。 不会知道为什么你的 RewriteRules 永远不会工作。

    看似诱人的普遍保障措施,往往成为实践中的障碍。

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

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

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

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

  • 虚拟路径重叠时禁用MultiViews

    URL 重写主要用于支持虚拟传入路径。 通常,您只有一个调度程序脚本( index.php )或几个单独的处理程序( articles.phpblog.phpwiki.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

  • RewriteConds 只屏蔽一个 RewriteRule

    一个常见的误解是 RewriteCond 会阻止多个 RewriteRules(因为它们在视觉上排列在一起):

     RewriteCond %{SERVER_NAME} localhost RewriteRule ^secret admin/tools.php RewriteRule ^hidden sqladmin.cgi

    默认情况下它不会。 您可以使用[S=2]标志链接它们 否则,您将不得不重复它们。 虽然有时您可以制定一个“倒置”的主要规则来提早 [END] 重写处理。

  • QUERY_STRING 免于 RewriteRules

    您无法匹配RewriteRule index.php\?x=y ,因为 mod_rewrite 仅与默认情况下的相对路径进行比较。 您可以通过以下方式单独匹配它们:

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

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

  • .htaccess<VirtualHost>

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

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

  • 每当某些事情不起作用

    不要担心。

    • 比较access.logerror.log

      通常你可以通过查看你的error.logaccess.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 上提问。 通过包括先前的研究和尝试(避免多余的答案)使它们成为主题,展示基本的理解,并且:

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

      这可以更快,更准确地获得答案,并使它们对其他人更有用。

  • 评论您的.htaccess

    如果您从某处复制示例,请注意包含# comment and origin link 虽然忽略归因只是一种不礼貌的做法,但它通常确实会损害以后的维护。 记录任何代码或教程源。 特别是在不熟悉的情况下,您应该更感兴趣的是不要将它们视为魔法黑匣子。

  • 这不是“SEO”-URL

    免责声明:只是一个小问题。 您经常听到漂亮的 URL 重写方案被称为“SEO”链接或其他东西。 虽然这对于搜索示例很有用,但它是一个过时的误称。

    现代搜索引擎都不会真正受到路径段中的.html.php?id=123查询字符串的干扰。 旧的搜索引擎,例如 AltaVista,确实避免了抓取具有潜在不明确访问路径的网站。 现代爬虫通常甚至渴望深度网络资源。

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

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

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

工具

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

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

mod_rewrite 的替代品

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

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

  2. 启用MultiViews以隐藏.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.phparticle.fr.phparticle.jp.mp4 )中获得最佳选择。

  3. 用于无扩展名.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-phpAddHandler php5-script

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

  4. 其他 Apache 重写方案

    在其众多选项中,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 文件以使其正常工作?


简单的答案是你不能。

重写规则不会使丑陋的 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-博客/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]

重要的区别是[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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM