将URL转换为小写格式而不破坏文件系统或文化?

8

URL小写规范化

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

// 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...);
}

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

那么对于非en-US文化的地区呢?我参考了土耳其测试来设计一个测试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 Framework中的字符串的最佳实践》中,有一条注释(大约在文章中部):

注意:文件系统、注册表键和值以及环境变量的字符串行为最好用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?


@John 转换基本拉丁字符可能是一种保守的解决方案,但是.NET没有提供仅影响这些字符的.ToLowerASCII().ToLowerInvariant()则会远远超出范围并破坏许多国际字符。我相信目前没有100%的解决方案。 - Kevin P. Rice
@JonathanDickinson 我没有使用 Accept-LanguageCurrentCulture 仅指服务器端应用程序的配置(除非您使其跟踪 Accept-Language)。人们会期望具有土耳其 URL 的服务器设置为该文化,尽管我想在一台机器上存在多个文化 URL 的噩梦场景可能会出现。也许 John 只转换 ASCII 字符是正确的。 - Kevin P. Rice
@KevinR 我会避免每个URL文化都是文化感知的。即使它是用英语编写的,用户仍可能是土耳其人,并应用自己的大小写规则。在这种情况下,当前用户优先于创建者。 - Jonathan Dickinson
@JonathanDickinson 我不明白为什么在英文URL的服务器上应用用户土耳其大小写规则,或者反之。在我看来,用户文化与此无关,除非服务器为每种语言提供URL。 - Kevin P. Rice
@JonathanDickinson 我想我理解了你的观点,如果我提供土耳其UX,那么这个观点就适用。但我没有。因此,将UA设置为土耳其语并请求“/INFO”的用户应遵循我的文化规则,并将其规范化为“/info”,而不是“/ınfo”,否则会出现404错误。同样地,使用en-US浏览器在图书馆上网的讲土耳其语的用户,在请求土耳其服务器上的“/III”时,可能需要将其规范化为“/ııı”。因此:(1)只有当具有特定于文化的URL和UX时,用户文化才有关系。(2)无法从UA文化(Accept-Language)保证用户的文化。这是一个判断性的问题。 - Kevin P. Rice
显示剩余3条评论
3个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
5

在这里,我要挑战一下尝试自动转换URL为小写是否有任何实用性的前提。

一个完整的URL是否大小写敏感完全取决于Web服务器、Web应用程序框架和底层文件系统。

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

其他所有内容都可能对服务器大小写敏感,包括路径(/)、文件名、查询(?)、片段(#)和权限信息(用户名称/密码在mailtohttpftp和一些其他方案之前的@)。


作为一个法国人(这些奇怪的人使用大量带重音的字符,甚至更多古怪的字符如'ç'),我对于URL没有自动转换为ASCII小写毫不惊讶。我猜土耳其人也会有同样的感觉,所以拥有这个特性没什么意义,尤其是考虑到你详细列出的所有原因都是真正的麻烦。 - Falanwe
@Falanwe 让我明确一点,我并不主张将带重音的字符转换为普通ASCII字符。让我问一下:您是否希望法语URL对大小写敏感,并响应大写和小写的法语重音等效字符?StackOverflow进行小写规范化。W.W.S.O.D.? - Kevin P. Rice
1
作为法国人,我习惯于使用非重音、非大写的URL。事实上,当一个网站能够响应带重音字符时,我会感到惊讶!特殊字符通常被编码成%数字,这使得一些URL看起来很丑。 - Falanwe
@Falanwe,你觉得http://fleurs.fr/EXPÉDITION和http://fleurs.fr/expédition会访问同一个网页吗?那么http://fleurs.fr/expedition呢?谢谢。 - Kevin P. Rice
显示剩余3条评论

2
您有一些不兼容的目标。 1. 进行文化敏感的小写处理。如果土耳其语看起来很差,您也不想知道一些乔治亚文字,更别提 ß 要么大写为 SS,要么不太常见地大写为 SZ - 在任一情况下,要进行完整的大小写折叠,其中 lower("ß") 将匹配 lower(upper("ß")),您需要将其视为这两个字符序列之一的等价物。通常情况下,我们尽可能采用大小写折叠而非大小写处理(在此处不可行)。 2. 在非文化敏感的上下文中使用。URI 最终是不透明的字符串。虽然它们可能具有人类可读的理解,但对于直接区分大小写的比较来说,它们最终的工作是通过标识资源来实现的。 3. 将其映射到 NTFS,它具有基于 $UpCase 文件中映射的保留大小写敏感性的特点,通过比较单词的大写形式来实现(至少它不必决定Σ 是否小写为 σ 或ς,以文化无关的方式)。 4. 可能对 SEO 和人类可读性表现良好。这可能是您最初的目标之一,但是 ThisIsNotVeryEasyToReadOrParse 比 thisseasierforbothpeopleandmachinesthanthis 更容易为人和机器所理解。大小写折叠会丢失信息。 我建议采用不同的方法。
  1. 从你的起始字符串开始,无论它来自哪里(NTFS 文件名、数据库条目、web.config 中的 HttpHandler 绑定),都将其作为规范形式。一定要有人们根据某些规范形式创建这些字符串的规则,并尽可能强制执行,但如果有违反规则的情况,那么无论你多么不喜欢它,都要将其接受为该资源的官方规范名称。
  2. 尽可能只让外部世界看到规范名称。这可以通过程序实现,也可以作为最佳实践的一部分,因为在进行 301 重定向之后进行规范化不能解决外部实体在解除引用 URI 之前不知道您所做的事情的问题。
  3. 接收请求时,根据其使用方式进行测试。因此,虽然您可能选择为自己执行资源查找的那些情况使用特定的文化(或不使用),但对于所谓的“静态”URI,您的逻辑可以故意遵循 NTFS 的逻辑,只需使用 NTFS 来完成工作:
    1. 查找映射文件,暂时忽略大小写敏感性问题。
    2. 如果不匹配,则 404,谁会在意大小写呢?
    3. 如果找到,则进行大小写敏感序比较,如果不匹配,则 301 至区分大小写的映射。
    4. 否则,按照惯例继续执行。

编辑:

某些方面来说,域名的问题更加复杂。IDN 的规则必须涵盖更多的问题,且操作空间更小。然而,至少在规范化大小写方面,它也更简单。

(我将忽略使用或不使用www.等情况的规范化,虽然我猜这也是同一项工作的一部分,但这已经超出了范围,如果我们不停下来,我们可能会写一本书:)

IDN 在 RFC 3491 中定义了其自己的大小写规范化(和其他形式的规范化)规则。如果要对域名进行大小写规范化,请遵循这些规则。

这使得回答变得非常简单,不是吗? :)

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

然而,它们知道example.netEXAMPLE.NET不可能是不同的网站,因此在确保它们相同方面几乎没有SEO优势(对于像缓存和历史记录列表这样不自动跳转的内容仍然很好)。当然,问题仍然存在于www.example.net或甚至maAndPasExampleEmporium.us可能是相同网站的事实,但这又与大小写问题无关。 还有一个简单的问题,大多数情况下我们从未处理过超过几十个不同的域名,因此有时候比聪明更努力地工作(即只需确保它们都正确设置并且不要以编程方式执行任何操作!)即可解决问题。 最后一点需要注意的是,不要将第三方URI规范化。如果更改路径,则可能会破坏某些内容(它们可能不会进行大小写不敏感处理),并且您可能至少会破坏它们略有不同的规范化。最好始终保持原样。

的确,当追求精确解决方案时,目标不兼容变得显而易见。有趣的是,在ASCII字符集中不存在不兼容性,这使得目标似乎变得容易实现。我的意图是编写代码来规范主机名(www和.org/net/us)、字母大小写、尾随斜杠等,主要用于SEO。单个特征可以禁用。您提出的方法结合适度的规范化似乎是合理的,以提供SEO和404-避免坏链接、大写字母键打字员、规范化域等。不是吗?我喜欢你的论点。谢谢。 - Kevin P. Rice
1
ASCII的大部分不兼容性之所以存在,是因为ASCII在处理这些情况时存在局限性(除非你使用一些过时的控制码,这会使它们重新出现并更多)。我能想到一个例子,爱尔兰语中nathairNATHAIR都表示“蛇”,但nAthair在某些情境下会表示“父亲”(大写为nATHAIRN-ATHAIR)。我没有考虑域名的不同问题。等我有时间了再补充更多内容。 - Jon Hanna
你的世俗知识非常启迪人心。我期待着你对域名的评论。通常,只需要将多个顶级域名重定向到“.com”即可。我非常喜欢S.O.提供独特的文章ID,后跟一个对于访问页面无关紧要的SEO标题的方法。无论您在文章ID之后键入什么,都可以找到正确的页面。然而,S.O.仍会重定向到规范URL。我非常好奇S.O.对任何非ASCII字符的小写处理方式是什么。 - Kevin P. Rice

0
首先,永远不要使用大小写转换来比较字符串。这会不必要地分配一个字符串,对性能有不必要的小影响,如果值为 null,则可能导致 ObjectReferenceException,并且很可能导致不正确的比较。 如果这对你很重要,我建议手动遍历文件系统,并针对每个文件/目录名称使用自己的比较方法。您应该能够使用 Accept-Language 或 Accept-Encoding(如果其中包含了文化)HTTP 标头来找到适合使用的文化。一旦您拥有 CultureInfo,就可以使用它来执行字符串比较:
var ci = CultureInfo.CurrentCulture; // Use Accept-Language to derive this.
ci.CompareInfo.Compare("The URL", "the url", CompareOptions.IgnoreCase);

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


大小写转换表示规范路径。如果没有大小写转换,就没有可比较的内容;而且,如果比较不匹配,则大小写转换是用于构建301 URL的字符串。如果没有大小写转换,唯一的选择就是迭代URL路径字符并检查char.IsUpper()。如果找到真正的情况,仍然必须进行大小写转换。 - Kevin P. Rice
@KevinR 我了解到你的文件存储在磁盘上(而不是数据库记录),这使得第二段第一句话非常重要。你的规范路径是由文件系统描述的路径。 - Jonathan Dickinson
你的规范路径是文件系统所描述的路径。是的,我已经想到了。我可以将301重定向到文件系统的字母大小写或者遵循SEO实践,鼓励使用小写并忽略文件系统。或者,可以编写一个应用程序,将磁盘上的所有文件名转换为小写。我有一些包含静态文件和动态URL的应用程序,因此!File.Exists()会触发动态URL大小写规则。 - Kevin P. Rice
@KevinR 不要使用 File.Exists(),而是根据 URL 手动迭代文件并使用 CompareInfo.Compare - 这就是我的意思。 - Jonathan Dickinson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,