在不破坏文件系统或区域性的情况下,将URL规范化为小写



将URL规范化为小写

我希望编写一个将URL转换为小写的HTTP模块。我的第一次尝试忽略了国际字符集,效果很好:

// Convert URL virtual path to lowercase
string lowercase = context.Request.FilePath.ToLowerInvariant();
// If anything changed then issue 301 Permanent Redirect
if (!lowercase.Equals(context.Request.FilePath, StringComparison.Ordinal))
{
    context.Response.RedirectPermanent(...lowercase URL...);
}

土耳其测试(国际文化):

但美国以外的文化呢?我参考了土耳其测试,得出了一个测试URL:

http://example.com/Iıİi

这个阴险的小宝石破坏了URL中大小写转换很简单的任何概念!其小写和大写版本分别为:

http://example.com/ııii
http://example.com/IIİİ

为了使用土耳其URL进行大小写转换,我首先必须将ASP.NET的当前文化设置为土耳其语:

<system.web>
    <globalization culture="tr-TR" />
</system.web>

接下来,我不得不更改代码,以使用当前的区域性进行案例转换:

// Convert URL virtual path to lowercase
string lowercase = context.Request.FilePath.ToLower(CultureInfo.CurrentCulture);
// If anything changed then issue 301 Permanent Redirect
if (!lowercase.Equals(context.Request.FilePath, StringComparison.Ordinal))
{
    context.Response.RedirectPermanent(...);
}

但是等一下!StringComparison.Ordinal还能工作吗?还是应该使用StringComparison.CurrentCulture?我真的不确定!

文件名:它变得更糟了

即使上述方法有效,使用当前区域性进行大小写转换也会破坏NTFS文件系统!假设我有一个名为Iıİi.html:的静态文件

http://example.com/Iıİi.html

尽管Windows文件系统不区分大小写,但它不使用语言区域性。将上面的URL转换为小写会导致404 Not Found,因为文件系统不认为这两个名称相等:

http://example.com/ııii.html

文件名的大小写转换正确吗?世界卫生组织知道

MSDN的文章《在.NET框架中使用字符串的最佳实践》中有一条注释(大约在文章的一半):

注意:文件系统、注册表项和值以及环境变量的字符串行为最好由StringComparison.OrdinalIgnoreCase.表示

最具代表性这是我们在C#中能做的最好的事情吗?那么,匹配文件系统的正确大小写转换是什么呢谁知道我们只能说,使用上述方法进行字符串比较可能在大多数情况下都有效。

摘要:两种情况转换:静态/动态URL

  1. 因此,我们已经看到静态URL——文件路径与文件系统中的真实目录/文件匹配的URL——必须使用仅由StringComparison.OrdinalIgnoreCase"最佳表示"的未知大小写转换。请注意,没有string.ToLowerOrdinal()方法,因此很难确切知道什么大小写转换等同于OrdinalIgnoreCase字符串比较。使用string.ToLowerInvariant()可能是最好的选择,但它打破了语言文化
  2. 另一方面,动态URL——文件路径与磁盘上的真实文件(映射到您的应用程序)不匹配的URL——可以使用string.ToLower(CultureInfo.CurrentCulture),但它会破坏文件系统匹配,而且还不清楚存在哪些边缘情况可能会破坏此策略

因此,在选择两种转换方法之一之前,案例转换似乎首先需要检测URL是静态的还是动态的。对于静态URL,如何在不破坏Windows文件系统的情况下更改大小写是不确定的。对于动态URL,使用区域性的大小写转换是否会类似地破坏URL是值得怀疑的。

哇!有人能解决这个烂摊子吗?还是我应该闭上眼睛,假装一切都是ASCII?

我在这里挑战这样一个前提,即尝试将URL自动转换为小写有任何实用性。

完整的URL是否区分大小写完全取决于web服务器、web应用程序框架和底层文件系统。

在方案(http://等)和URL的主机名部分,您只能保证不区分大小写。请记住,并不是所有的URL方案(例如filenews)都包含主机名。

服务器可以区分大小写,包括路径(/)、文件名、查询(?)、片段(#)和权限信息(在mailtohttpftp和一些其他方案中,@之前的用户名/密码)。

您有一些不兼容的目标。

  1. 降低文化敏感性大小写。如果土耳其语看起来不好,那么你不想知道一些格鲁吉亚语脚本,更不用说ß要么是SS的大写,要么是不太常见的SZ——在任何一种情况下,如果lower("ß")lower(upper("ß"))匹配,你都需要认为它等效于这两个字符序列中的至少一个。一般来说,如果可能的话,我们的目标是折叠箱子,而不是降低箱子(这里不可能)。

  2. 在非区域性敏感的上下文中使用此选项。URI最终是不透明的字符串。他们可能具有人类可读的理解,这对程序员、用户、搜索引擎和营销人员来说都是有用的,但他们的最终工作是通过直接区分大小写的比较来识别资源。

  3. 将其映射到NTFS,NTFS基于$UpCase文件中的映射具有保留大小写的敏感性,它通过比较单词的大写形式来实现这一点(至少它不必以不区分区域性的方式决定Σ小写是σ还是ς

  4. 大概在SEO和人类可读性方面做得很好。这很可能是你最初目标的一部分,但尽管这并不容易阅读或分析,但它对其他人和机器来说更容易。箱子折叠会丢失信息。

我建议采取不同的方法。

  1. 从你的起始字符串开始,无论它是什么,无论它来自哪里(NTFS文件名、数据库条目、web.config中的HttpHandler绑定)。把它作为你的规范形式。无论如何,都要有规则,人们应该根据某种规范形式创建这些字符串,并可能在你可以的地方强制执行它,但如果有什么事情发生了,就会破坏你的规则,然后接受它作为该资源的官方规范名称,无论你多么不喜欢它。

  2. 规范名称应该尽可能是外界"看到"的唯一名称。这可以通过编程方式强制执行,也可以作为最佳实践,因为使用301进行事后规范化并不能解决外部实体在取消引用URI之前不知道你这样做的问题。

  3. 当收到请求时,根据将如何使用它来测试它。因此,当您自己使用所谓的"静态"URI执行资源查找时,您可能会选择使用特定的区域性(或不使用),但您的逻辑可以通过简单地使用NTFS来完成工作来有意遵循NTFS的逻辑:

    1. 查找映射文件暂时忽略大小写敏感性问题
    2. 若不匹配那个么404,谁在乎这个案子
    3. 如果找到,则进行区分大小写的顺序比较,如果不匹配,则301到区分大小写映射
    4. 否则,照常进行

编辑:

在某些方面,域名问题更为复杂。IDN的规则必须涵盖更多的问题,而留给人的空间较小。然而,至少就案例规范化而言,它也更简单。

(我将忽略是否使用www.等的规范化。尽管我想这是同一项工作的一部分,但它正在扩大范围,如果我们不停下来,我们最终可能会写一本书:)

IDN在RFC 3491中定义了自己的事例规范化(以及一些其他形式的规范化)规则。若你们要在case中规范域名,那个么就按照这个来做。

回答起来很简单,不是吗?:)

在某种程度上,压力也较小,因为虽然搜索引擎必须认识到http://example.net/thisisapathhttp://example.net/thisIsAPath可能是同一种资源,但它们也必须认识到它们可能不同,这就是对其中一种资源进行规范化的所有SEO优势(无论是哪种)的来源。

然而,他们知道example.netEXAMPLE.NET不可能是不同的网站,因此确保它们相同几乎没有SEO优势(对于缓存和历史列表等本身不会跳转的东西来说仍然很好)。当然,问题仍然存在于www.example.net甚至maAndPasExampleEmporium.us可能是同一个站点的事实上,但这再次远离了案例问题。

还有一个简单的问题,大多数时候我们永远不需要处理几十个不同的域,所以有时更努力而不是更聪明地工作(即确保它们都设置正确,不要通过编程做任何事情!)可以做到这一点。

不过,最后要注意的是,不要将第三方URI规范化。如果你改变了路径,你可能会最终打破一些东西(他们可能不会对情况不敏感),你至少可能会打破他们略有不同的规范。最好始终保持原样。

首先never使用用例转换来比较字符串。它不必要地分配了一个字符串,对性能的影响也不必要,如果值为null,则可能导致ObjectReferenceException,并且可能导致不正确的比较。

若这对你们来说足够重要的话,我会手动遍历文件系统,并使用你们自己对每个文件/目录名的比较。您应该能够使用Accept-LanguageAccept-Encoding(如果包含区域性)HTTP标头来查找要使用的合适区域性。一旦你有了CultureInfo,你就可以用它来执行字符串比较:

var ci = CultureInfo.CurrentCulture; // Use Accept-Language to derive this.
ci.CompareInfo.Compare("The URL", "the url", CompareOptions.IgnoreCase);

我只会在HTTP404上这样做;HTTP 404处理程序将搜索匹配的文件,然后将用户HTTP 301到大小写正确的URL(因为手动文件系统遍历可能会变得昂贵)。

最新更新