首页 > 解决方案 > 为什么 http 服务器需要知道文件名的编码?

问题描述

简而言之,场景很简单,HTML 文件的内容大多是 ASCII 编码的,除了包含 UTF-8 字符的图像文件名。此图像名称从不显示。ASCII 是默认编码,所以我们不在任何地方声明它。问题是,为什么图像无法加载?假设图像文件名的文件名和HTML代码中的八位字节相同,为什么图像加载不正确。此外,如果我们明确告诉浏览器使用 UTF-8 编码,问题似乎就得到了解决。为什么声明编码会影响 http 服务器获取文件的能力?

您可以在下面找到具体的技术细节和示例代码:

考虑简单的 html 文件“index.html”:

<img src="cinturón.jpg"> </img>

以及目录中的以下文件:

~/root
|- index.html
|- cinturón.jpg

如果我运行“firefox index.html”或在搜索栏中输入 ~/root/index.html,图像会正确加载。

但是,如果我将文件推送到仅使用经典设置将请求转发到文件系统的 nginx 服务器,则图像将失败,直到我们通过 head 元素中元标记的某些字符集属性在 html 文件中声明编码。

<head>
    <meta charset="UTF-8">
</head 

这个指令告诉浏览器(其余)文件的编码是什么,但浏览器已经按照惯例将此文件解释为 ASCII,这就是它理解 ascii 标记的方式。而且我的文件名没有显示,所以浏览器并不需要知道文件名的编码。

例如,文件系统不知道编码,它们只是存储在写入期间给出的文件名,可能对它们进行哈希处理,然后在读取期间比较给定的文件名。唯一的要求是读写时的输入接口使用相同的编码,产生相同的八位字节序列。在我的情况下,服务器和本地操作系统和文件系统是相同的,并且文件是通过 git 复制的,这会校验它们的内容以验证它们是否相同,因此我们可以确定文件系统中的文件名是跨环境相同。

我仍然无法弄清楚为什么我的图像仅在澄清浏览器的编码时才加载,有两种可能性。

A)我的指令导致我的浏览器改变了它的行为。B)我的指令导致服务器改变了它的行为。

当浏览器向服务器发送图像请求时会发生 A。当服务器将 html 文件发送到服务器时会发生 B。这需要 nginx 以某种方式解释 HTML 代码。我不认为 HTTP 服务器可以阅读或理解 HTML,所以这不太可能。

通过检查浏览器发送的请求,我可以看到浏览器在声明 UTF-8 时发送“/cintur%C3%B3n.jpg”,否则发送“/cintur%C3%83%C2%B3n.jpg”。% 是转义字符。C3、83 C2 和 B3 是不同八位字节的十六进制。浏览器在声明编码时发送 ó 作为 2 个八位字节,但在未声明编码时发送 4 个八位字节。在这一点上,异常表现得更清楚了,声明编码不应该改变消息,只是解释它的方式。

C3 B3 是代表 utf-8 中 ó 字符的 2 个八位字节,这是基本的多语言平面,第二个块,紧随 ascii(在 utf-8 规范中也称为基本拉丁文)。我还没弄清楚C3 83 C2 B3是什么

当使用 od (od -c index.html) 检查静止的 index.html 文件时,我们发现

" cintur 303 263 n . jpg "

其中 303 和 263 是被解释为无符号八进制数的八位字节。手动八进制到十六进制转换或使用 -tx1 选项运行 od 确认这些是 B2 和 C3 八位字节。所以 html 文件中的 ó 字符,当在服务器和我的本地机器中的文件系统中休息时,都是 2 个字节长。同样,我们知道在声明 utf-8 编码时从浏览器发送的请求中的 r 和 n 字符之间有 4 个字节。

由于我没有工具来验证 ó 字符在我的浏览器中的长度,我只是假设它是 4 字节长,下一个要回答的问题是服务器是否发送 2 字节ó 或 4 字节 ó。无法找到,因为服务器日志已压缩并与其他数据包捕获混合。

将来我可能会发布额外的调试信息,此时我可能会使用更笨的 http 服务器,比如busybox 或 C TCP 套接字。

标签: htmlhttpnginxfirefoxbrowser

解决方案


文件名通过 URI 进行通信,URI 由 ASCII 字符组成,而不是八位字节。Http 服务器和浏览器要求编码是 ASCII,这与编码中的许多其他字符不兼容。为了使用任何其他编码,您需要使用 ascii 字符和特定的转义机制对其进行编码。

RFC 2616定义了 http 语法,文件名在请求消息中作为 URI 传输。URI 又在RFC 1630中定义。与大多数 HTTP 语法一样,URI 只允许 7 位 ASCII 字符,定义为RFC 20

ASCII 定义了 7 位的 128 个字符,剩下的位被浪费了:

具体而言,我们建议使用标准的 7 位 ASCII 嵌入到高位始终为 0 的 8 位字节中

这意味着包含 ó 字符的消息将被认为是非法的,或者表明流没有用第 8 位填充。因此,任意八位字节不能作为 URI 传输。

URI 定义施加了进一步的限制,这使得多字节编码的使用特别不安全。最重要的是,当作为 HTTP 请求传输时,空格(十六进制 20)被认为是 URI 的开始和结束,所以如果一个字符被编码为多个八位字节,并且其中一个与 ASCII 编码的空格相同,那么获取该资源会有问题。

为了防止此类问题,Web 浏览器会转义受保护的字符和非 ascii 字符。正如 URI 标准定义的那样:

需要能够直接表示 URI 中的许多字符(包括空格)与需要能够在字符集有限或某些字符容易损坏的环境中使用 URI 之间存在冲突。此冲突已通过使用十六进制转义方法解决,该方法可应用于给定上下文中禁止的任何字符。

百分号(“%”,ASCII 25 hex)在编码方案中用作转义字符,不允许用于其他任何字符。

不安全的字符

在规范形式中,某些字符,如空格、控制字符、在 不同国家字符变体 7 位集中
使用不同 ASCII 码的一些字符,以及 ISO Latin-1 集的 DEL(7F 十六进制)之外的所有 8 位字符,不得使用 未编码。


超出 DEL(7F 十六进制)的字符不能在没有表示的情况下作为八位字节发送,URI 应该以书面形式表示,必要时用笔和纸表示。

可以使用 7 位 ASCII 字符表示任何 URI,以便在必要时可以使用钢笔和墨水传递 URI。

为此目的使用了 7 位基本集的简单性,URI 旨在用于人类理解,而不仅仅是机器,因此它们不能被视为八位字节,它们是 ASCII 字符。

简而言之,URI 应该由 ASCII 字符组成,如果要使用另一个字符集,它们应该用 %(十六进制 25)转义,后跟所需八位字节的 ASCII 编码十六进制值。然后,http 服务器需要查找 % 并酌情读取以下两个八位字节,并将其转换为单个八位字节。这是不切实际和复杂的,最明智的解决方案是停止在 URI 中使用非 ASCII 字符。


推荐阅读