【问题标题】:IIS Url Rewite: Add Trailing Slash, Preserve Anchors and Query StringsIIS 网址重写:添加斜杠、保留锚点和查询字符串
【发布时间】:2022-02-17 11:44:21
【问题描述】:

我已经搜索了几个 SO 帖子,但没有找到我要查找的内容。它可能存在,但可能已经相当老了,不会出现在我面前。我发现一个帖子 (Nginx rewrite: add trailing slash, preserve anchors and query strings) 非常接近我的需要,但它的正则表达式解决方案不适用于 IIS 的 URL 重写,除非我做错了。

问题

我正在尝试将正斜杠 / 添加到我的 url 路径的末尾,同时还保留任何现有的查询字符串 ? 和锚点 #

所需的解决方案

基本上,这是每个问题的预期结果:

Entry: https://my.site.com/about
Result: https://my.site.com/about/

Entry: https://my.site.com/about?query=string
Result: https://my.site.com/about/?query=string

Entry: https://my.site.com/about#TestAnchor
Result: https://my.site.com/about/#TestAnchor

Entry: https://my.site.com/about?query=string#TestAnchor
Result: https://my.site.com/about/?query=string#TestAnchor

当前测试

我们当前的正则表达式会忽略查询字符串和锚点,但我现在想将它们考虑在内。

<rule name="AddTrailingSlash" stopProcessing="true">
  <match url="^([^.?]+[^.?/])$" />
  <action type="Redirect" url="{R:1}/" redirectType="Permanent" />
</rule>

我还测试了另一个正则表达式,但它仅在 url 包含查询字符串和锚点时才有效。

<rule name="AddTrailingSlash" stopProcessing="true">
  <match url="^(.*)(\?.*?)(\#.*?)$" />
  <action type="Redirect" url="{R:1}/{R:2}{R:3}" redirectType="Permanent" />
</rule>

注意:我刚刚测试了最后一个 (^(.*)(\?.*?)(\#.*?)$),但它实际上不起作用。如果 url 在 ? 之前已经包含 / ,则测试不应该通过,所以我还有更多工作要做。

问题

我可以使用一个正则表达式来解决这个问题还是需要使用多个规则?

【问题讨论】:

    标签: regex url-rewriting url-rewrite-module


    【解决方案1】:

    TL;DR

    IIS 重写 (ALL) URI 与 Trailing Slash 并保留 FragmentQuery Strings
    <rule name="AddTrailingSlash" stopProcessing="true">
      <match url="^([^/]+:\/\/[^/#?]+|[^?#]+?)\/?((?:[^/?#]+\.[^/?#]+)?(?:[?#].*)?$)" />
      <action type="Redirect" url="{R:1}/{R:2}" redirectType="Permanent" />
    </rule>
    

    IIS use ECMAScript,您可以在此处试用https://regexr.com/6ele7


    更新

    IIS 使用 Trailing Slash 重写(考虑)URI 并保留 FragmentQuery Strings
    <rule name="AddTrailingSlash" stopProcessing="true">
      <match url="^([^/]+:\/\/[^/#?]+|[^?#]+\/[^/.?#]+)([?#].*)?$" />
      <action type="Redirect" url="{R:1}/{R:2}" redirectType="Permanent" />
    </rule>
    

    尝试一下https://regexr.com/6fk3g


    http://127.0.0.1  -->  http://127.0.0.1/
    https://localhost  -->  https://localhost/
    https://localhost?  -->  https://localhost/?
    https://localhost/  -->  https://localhost/
    https://my.site.com  -->  https://my.site.com/
    https://my.site.com:443?  -->  https://my.site.com:443/?
    https://my.site.com/  -->  https://my.site.com/
    https://my.site.com/about.php  -->  https://my.site.com/about.php
    https://my.site.com/about.php?  -->  https://my.site.com/about.php?
    https://my.site.com/about  -->  https://my.site.com/about/
    https://my.site.com/about?  -->  https://my.site.com/about/?
    https://my.site.com/about/  -->  https://my.site.com/about/
    https://my.site.com/about/?  -->  https://my.site.com/about/?
    https://my.site.com/about?query  -->  https://my.site.com/about/?query
    https://my.site.com/about/?query  -->  https://my.site.com/about/?query
    https://my.site.com/about.php?query  -->  https://my.site.com/about.php?query
    https://my.site.com/about#hash  -->  https://my.site.com/about/#hash
    https://my.site.com/about/#hash  -->  https://my.site.com/about/#hash
    https://my.site.com/about.php#hash  -->  https://my.site.com/about.php#hash
    https://my.site.com/about?query#hash  -->  https://my.site.com/about/?query#hash
    https://my.site.com/about/?query#hash  -->  https://my.site.com/about/?query#hash
    https://my.site.com/folder.name/about?query  -->  https://my.site.com/folder.name/about/?query
    https://my.site.com/about?query#hash:http://test.com?q  -->  https://my.site.com/about/?query#hash:http://test.com?q
    

    说明(全部)

    • 1 级 - 让我们想想你的例子:
    ^([^?#]+?)\/?([?#].*)?$
    

    第 1 组: ^ 首先,[^?#]?/# 之外的任何字符,多但懒惰+?(尽可能先停止,通过查看next)
    忽略: \/? 那么如果/ 存在与否
    第2组: [?#] = ?/@987654359 @ 和 .* 旁边的任何字符直到 $ 结束,(...)? 如果存在

    效果很好。 但是它不会正确处理:

    https://my.site.com/about.php?query  -->  https://my.site.com/about.php/?query  !!!
    

    所以让我们添加一个例外...

    • 2 级 - 如果我们将可能的文件名 Name.name.name.ext 作为 Group #2 怎么办?
    ^([^?#]+?)\/?((?:[^/?#]+\.[^/?#]+)?(?:[?#].*)?)$
    

    (?:...)非捕获组
    ([^/?#]+\.[^/?#]+)?查找任何可能的文件名或(?:[?#].*)?任何可能的查询或锚字符串

    现在一切正常,除了这个:

    https://my.site.com?  -->  https://my.site.com?  !!!
    

    所以我们需要Group #1

    中的另一个例外
    • 第 3 级 - 仅使用域 URI 作为替代项
    ^([^/]+:\/\/[^/#?]+|[^?#]+?)\/?((?:[^/?#]+\.[^/?#]+)?(?:[?#].*)?$)
    

    (...|...) 替代 [^/]+:\/\/[^/#?]+ 首先检查(不是懒惰的)任何像...://... 这样的模式直到不存在/ # ?

    现在效果很好!


    + 解释(考虑)

    • 4 级 - 如果我们只在第一组中添加 Not-Accepting ./ 字符集以匹配考虑的 URI 并忽略其他 URI,该怎么办?
    ^([^/]+:\/\/[^/#?]+|[^?#]+\/[^/.?#]+)([?#].*)?$
    

    \/[^/.?#]+ 检查最后一个/ 之后的字符集是否不是/.?#

    现在它更小更快了!


    分析其他方法

    由于@károly-szabó 回答得很好here,我们可以寻找匹配的模式,而不是寻找 Not-Accepted 字符集。
    因此,如果我们想以更简单的方式使用该方法(2 组)(+ 一些小的优化),则正则表达式将是:

    ^(https?:\/\/[\w.:-]+\/?(?:[\w.-]+\/)*[\w-]+(?!\/))([?#].*)?$
    

    但是URI pathAccepted characters更多。

    因此,该正则表达式的更广泛版本可以是:

    ^(https?:\/\/[\w.:-]+\/?(?:[\w!#-)+-.;=@~]+\/)*[\w!#-);=@~+,-]+(?!\/))([?#].*)?$
    

    在这里试试:https://regexr.com/6elea

    注意:仍然“允许使用multibyte Unicode 作为域名”,但我在此方法中忽略了这一点。


    附言

    实际上我认为我们不应该在 IIS 上重写它,原因如下:

    我的意思是:

    https://my.site.com/  -->  (=Call root)
    https://my.site.com/about  -->  (=Call root > Folder/File name about) 
    https://my.site.com/about/  -->  (=Call root > Folder name about) 
    https://my.site.com/about?query  -->  (=Call root > Folder/File name about + Query)
    https://my.site.com/about/?query  -->  (=Call root > Folder name about + Query)
    https://my.site.com/about.php?query  -->  (=Call root > File name about.php + Query)
    [When browser strip it:]
    https://my.site.com/about#hash  -->  (=Call root > Folder/File name about + Anchor)
    https://my.site.com/about/#hash  -->  (=Call root > Folder name about + Anchor)
    https://my.site.com/about.php#hash  -->  (=Call root > File name about.php + Anchor)
    
    [If not?]
    https://my.site.com/folder#name/?query#hash
    https://my.site.com/folder.name/about.php?query=one/two
    

    【讨论】:

    • 这看起来很棒,我喜欢扩展的解释、警告和资源链接。但是,它似乎匹配每个 url。我希望它不匹配任何已经正确格式化的网址。 @károly-szabó 这样做,但似乎不允许在 url 中使用破折号,所以如果修复,我希望使用那个破折号。
    • @RoLYroLLs 谢谢,这个想法使正则表达式更简单,我更新了答案。
    • 感谢您的更新,很抱歉因为我不在城里而耽搁了。现在有些不对劲。我似乎没有正确匹配其中的一行,就好像它匹配了两行合二为一。 imgur.com/a/MiMTLX6
    • @RoLYroLLs 没有错。实际上,在 URI 中,您永远不会有多行输入,我使用多行示例让您可以看到彼此相邻的示例,仅供查看。我可以通过在否定组 (^([^/]+:\/\/[^/#?\n]+|[^?#\n]+\/[^/.?#\n]+)([?#].*)?$) 中添加 \n 来避免下一行字符,但我更喜欢通过重新排列彼此相邻的示例来处理视图,以免临时 \n 使正则表达式变脏。
    【解决方案2】:

    你可以试试这个正则表达式https://regex101.com/r/6TSqaP/2。如果 url 已经有结尾 '/',这将匹配每个提供的示例并解决问题。

    ^((?:https?:\/\/[\w\.\-]*)(?:[\w\-]+\/)*(?:[\w\-]+)(?!\/))(\?.*?)?(\#.*?)?$
    

    我使用您的第二个示例作为我的正则表达式的基础,具有以下逻辑。 url部分:scheme://authority/path?query#fragment

    1. 第一个捕获组匹配 url 的 scheme://authority/path 部分
    2. 第二个捕获组可选并且匹配?query
    3. 第三个捕获组也是可选的,用于#fragment

    正则表达式解释

    ^(                            # should start with this
        (?:https?:\/\/[\w\.\-]*)  # match the http or https protocol and the domain
        (?:[\w\-]+\/)*            # match the path except the last element of it (optional)
        (?:[\w\-]+)(?!\/)         # match the last path element, but only if it's not closed with '/'
    )                             # {R:1}
    (\?.*?)?                      # {R:2} query (optional)
    (\#.*?)?                      # {R:3} fragment (optional)
    $                             # string should end
    

    Nginx

    <rule name="AddTrailingSlash" stopProcessing="true">
      <match url="^((?:https?:\/\/[\w\.\-]*)(?:[\w\-]+\/)*(?:[\w\-]+)(?!\/))(\?.*?)?(\#.*?)?$" />
      <action type="Redirect" url="{R:1}/{R:2}{R:3}" redirectType="Permanent" />
    </rule>
    

    编辑:更新正则表达式以处理破折号 (-) 和多个路径元素

    【讨论】:

    • 经过多次测试,这看起来是个不错的答案,但我需要一点帮助。我们的域名中包含一个破折号-。您能帮助更新正则表达式以允许域名包含破折号吗?谢谢。
    • 我想应该是这个^((?:https?:\/\/[\w\-\.]*)(?:\w+\/)?(?:\w+)(?!\/))(\?.*?)?(\#.*?)?$(在开头附近添加了\-
    • 我还注意到这不会捕获带有更多破折号的 url,即:https://my.site.com/about-us 未捕获。
    • 最后评论:我也注意到这没有捕获:https://my.site.com/about/us
    • 我用新的正则表达式和新链接更新了我的帖子,正确匹配了这些情况。
    猜你喜欢
    • 1970-01-01
    • 2023-03-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-11
    • 1970-01-01
    • 2015-06-06
    相关资源
    最近更新 更多