Mod Rewrite + QSA:重写目标和查询字符串中的相同查询字符串参数 - 可以强制使用目标吗?

Posted

技术标签:

【中文标题】Mod Rewrite + QSA:重写目标和查询字符串中的相同查询字符串参数 - 可以强制使用目标吗?【英文标题】:Mod Rewrite + QSA : Same querystring param in rewrite target and in querystring - Force use of target possible? 【发布时间】:2012-11-25 19:44:15 【问题描述】:

我们有以下重写规则:

 RewriteRule ^([A-Za-z0-9\_]+)$ index.php?rewrite=$1 [L,QSA]

我们想知道是否有办法让 ?rewrite=$1 优先于通过请求 uri 中的查询字符串传递的方法?

因为目前的情况,如果点击以下网址,则由于 QSA 标志(我们确实需要它):

 http://www.domain.com/this_rewrite_will_match_the_above_rule?rewrite=some_value

PHP 中 $_GET['rewrite'] 的值将是 some_value,而不是 this_rewrite_will_match_the_above 规则

在我们开始修改重写并添加一个 RewriteCond 以匹配查询字符串等之前......我们希望有一个标志可以设置,以便目标 url (index.php?rewrite=$1 in本例)优先于传递的查询字符串值。

希望这是有道理的。

谢谢。

【问题讨论】:

【参考方案1】:

我使用了以下说明:

RewriteRule ([^\?]*)\?(.*) $1?$2 [N]                          (1)
RewriteCond %QUERY_STRING (.*?)&?(rewrite=[^&]+)&?(.*)      
RewriteRule ^(.*)$ $1?%1&%3 [N]                               (2)
RewriteRule ^(.*)$ $1?domain=newValue [L,QSA]                 (3)

这个技巧是通过前两个规则中的 [N] 标志完成的,这会导致重写引擎重新处理输出。

规则 (1) 简单地按原样重写 url。我需要它,因为我将 mod_rewrite 与 mod_proxy_ajp 一起使用,并且在第一次迭代中,查询字符串没有从 url 中拆分出来。第一行执行后,url不变,但引擎会从查询字符串中拆分路径。

规则 (2) 迭代并从查询字符串中删除所有出现的参数“rewrite”。

规则 (3) 设置参数的新值,并附加在规则 (2) 完成的替换中幸存的查询字符串。

【讨论】:

【参考方案2】:

我选择创建自己的答案,因为它比 Jpsy 和 Gerben 提供的示例更简洁。

信用到期,他们的建议让我来到这里,我只是对他们进行了扩展:

所以,我们的最终解决方案包括 2 条规则。

# check if querystring is not empty (this is the addition vs other answers)
RewriteCond %QUERY_STRING !^$
RewriteCond %REQUEST_FILENAME !-d
RewriteCond %REQUEST_FILENAME !-f
RewriteRule ^([A-Za-z0-9\_]+)$ index.php?%QUERY_STRING&rewrite=$1 [L]

如果上述查询字符串失败(主要是查询字符串为空),则适用此规则。

RewriteCond %REQUEST_FILENAME !-d
RewriteCond %REQUEST_FILENAME !-f
RewriteRule ^([A-Za-z0-9\_]+)$ index.php?rewrite=$1 [L]

我选择这种双重规则设置的原因是为了避免在查询字符串为空的情况下使 php 服务器变量被 "?&rewrite=...." 污染。

感谢 jpsy 和 gerben 的帮助。

【讨论】:

是的,我和 Gerben 的解决方案(绝对比我的更优雅和更快!)在查询字符串为空时创建一个“?&”序列。但是这个序列被忽略了。我基本上不会添加另一个条件来避免这种情况。请记住,对于从服务器请求的每个文件(html、图像、CSS 和 JS 文件,...),都会检查此附加条件。另一种方法是将条件 放在 -d 和 -f 检查之后,因此所有现有文件和目录都会跳过它。 我同意。序列被忽略。我选择避免这种情况的主要原因 ?& 是因为代码库大约有 3-4 年的历史,相当大,我不确定是否有任何地方 $_SERVER['QUERY_STRING'] 变量是模式匹配/拆分/等等等......只是为了安全。但是,在任何其他情况下,我都会选择 Gerben 的解决方案。【参考方案3】:

我已经搜索了一些机制来在 Apache 重写中多次且非常强烈地覆盖单个查询字符串参数,但看起来没有选择这样做,即使使用最新的 Apache 版本(当时是 2.4.3本文)。

但是有一个替代方案利用了 PHP 查询字符串解析器只返回多个相同查询字符串参数中的最后一个这一事实。

例子:

http://www.domain.com/index.php?id=123&id=456

这将在 $_GET 中返回以下单个 (!) 条目:

array(1) 
  ["id"]=>
  string(3) "456"

因此,您可以通过简单地将任何覆盖参数附加到现有查询字符串的末尾(而不在查询字符串中删除它们)来解决您的问题。重复参数的最后一次出现是使其进入 $_GET 数组的参数。

不幸的是,QSA 开关不适合这种技术,因为它总是将原始参数附加到新查询字符串的末尾。没有可以预先设置旧参数的开关。因此,您必须绕道而行,使用 RewriteCond 自己捕获并添加原始查询字符串,而不是使用 QSA:

RewriteCond  %QUERY_STRING  ^(.*)$
RewriteRule ^([A-Za-z0-9\_]+)$ index.php?%1&rewrite=$1 [L]

RewriteCond 的唯一功能是捕获 %1 中的查询字符串。条件的正则表达式(.*)总是匹配的,所以下面的RewriteRule总是被执行。

使用这种技术,您的上述测试用例将重写为...

http://www.domain.com/index.php?rewrite=some_value&rewrite=this_rewrite_will_match_the_above_rule

...它将被 PHP 查询字符串解析器解释为

$_GET["id"] => "this_rewrite_will_match_the_above_rule"

...这就是你想要的。

请注意,这仅在您从 PHP 的 $_GET 数组中获取查询字符串值时才有效。如果您自己解析$_SERVER["QUERY_STRING"] 的内容或使用任何其他编程语言,则不一定有效。

【讨论】:

【参考方案4】:

略显老套

RewriteRule ^([A-Za-z0-9\_]+)$ index.php?%QUERY_STRING&rewrite=$1 [L]

这是有效的,因为在 php 中,第二个 rewrite=... 会覆盖第一个。

【讨论】:

以上是关于Mod Rewrite + QSA:重写目标和查询字符串中的相同查询字符串参数 - 可以强制使用目标吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何关闭 QSA? (查询字符串追加)

mod_rewrite - 重写目录以查询字符串,除了 /#!/

求阻 关于 apache mod_rewrite下的rewriteconnd

使用 mod_rewrite RewriteRule 重写所有查询以不需要 .php 扩展名

mod_rewrite:将路径和查询字符串 URL 作为参数传递

用户友好的 URL - mod_rewrite 和 php 重定向