IIS Url 重写:添加尾部斜杠、保留锚点和查询字符串

IIS Url Rewite: Add Trailing Slash, Preserve Anchors and Query Strings

我已经搜索了几个 SO post,但没有找到我要找的东西。它可能存在,但可能已经足够老了,不会出现在我面前。我发现 post (Nginx rewrite: add trailing slash, preserve anchors and query strings) 非常接近我的需要,但它的正则表达式解决方案不适用于 URL Rewrite for IIS,除非我做错了。

问题

我正在尝试在我的 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 在 ? 之前已经包含一个 /,那么测试应该通过了,所以我在这里还有更多的工作要做。

问题

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

您可以尝试使用此正则表达式 https://regex101.com/r/6TSqaP/2。如果 url 已经有结尾 '/'.

,这将匹配每个提供的示例并解决问题
^((?:https?:\/\/[\w\.\-]*)(?:[\w\-]+\/)*(?:[\w\-]+)(?!\/))(\?.*?)?(\#.*?)?$

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

  1. 第一个捕获组匹配 url
  2. scheme://authority/path 部分
  3. 第二个捕获组可选并匹配 ?query
  4. 第三个捕获组也是可选的,并且用于 #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>

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

TL;DR

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

so you can Try it here : https://regexr.com/6ele7


更新

IIS 使用 Trailing Slash 重写(考虑)URI 并保留 Fragment and Query 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 级 - 让我们想想你的例子:
^([^?#]+?)\/?([?#].*)?$

Group #1: ^ 首先,[^?#]?/#以外的任意字符,走多了但是lazy +?(首先停止,通过查看下一个)
忽略: \/? 那么如果/存在与否
第 2 组: [?#] = ?/#.* 旁边的任何字符,直到 $ 结束, (...)? 如果存在

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

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

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

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

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

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

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

所以我们需要在 组 #1

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

(...|...)备选方案 [^/]+:\/\/[^/#?]+ 首先检查(不是懒惰)是否存在 ...://... 直到不存在 / # ? 之类的任何模式?

现在效果很好!


+ 说明(考虑)

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

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

现在更小更快了!


正在分析其他方法

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

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

但是URI path Accepted characters更多。

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

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

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

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


P.S.

实际上我认为我们不应该在 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