Nginx 是否支持路径中的原始 unicode?

Does Nginx support raw unicode in paths?

浏览器 url 默认将 unicode 字符编码为 %##。

但是,我可以通过 CURL 向 http://localhost:8080/与 发出请求,nginx 将路径视为“”。这怎么可能?那么 Nginx 是否允许在其路径中使用任意 unicode?

例如,使用这个配置我可以设置一个额外的 header 来查看 nginx 看到了什么:

location ~* "(*UTF8)([^\w/\.\-\% ])" {
        add_header "response" ;
        return 200;
}

要求:

* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /与 HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: */*
> 
< HTTP/1.1 200 OK
* Server nginx/1.4.6 (Ubuntu) is not blacklisted
< Server: nginx/1.4.6 (Ubuntu)
< Date: Tue, 20 Jan 2015 21:44:51 GMT
< Content-Type: application/octet-stream
< Content-Length: 0
< Connection: keep-alive
< response: 与                                        <--- SEE THIS?
< 
* Connection #0 to host localhost left intact

但是,当我删除 UTF8 标记时,header 包含“?”好像 nginx 无法理解字符(或者只读取第一个字节)。

location ~* "([^\w/\.\-\% ])" {
        add_header "response" ;
        return 200;
}

要求:

* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET /与 HTTP/1.1
> User-Agent: curl/7.30.0
> Host: localhost:8080
> Accept: */*
> 
< HTTP/1.1 200 OK
* Server nginx/1.4.6 (Ubuntu) is not blacklisted
< Server: nginx/1.4.6 (Ubuntu)
< Date: Tue, 20 Jan 2015 21:45:35 GMT
< Content-Type: application/octet-stream
< Content-Length: 0
< Connection: keep-alive
< response: ?
< 
* Connection #0 to host localhost left intact

注意:更改此 non-utf-8 正则表达式以捕获 one-or-more ([^...]+) 也会导致 response: 与 header 正在发送(字节与多字节字符串?)

将正则表达式匹配记录到文件会导致请求条目如下:

GET /\xE4\xB8\x8E HTTP/1.1

除了正则表达式和终端配置,这与 Unicode 没有任何关系。对您的问题的简短回答是:nginx 不关心 Unicode 编码,但它确实接受 URL 中的 non-ASCII 字节。

这是解释您所见内容的长答案。如果输入命令

curl http://localhost:8080/与

并且您的终端使用 UTF-8 作为编码,它将字符与 (U+4E0E) 编码为 three-byte UTF-8 序列

0xE4 0xB8 0x8E

curl 显然接受 URL 中的 non-ASCII 字节,尽管它们在技术上是非法的。然后它将发送一个带有这些 non-ASCII 字节的 HTTP 请求。由于没有默认方式来显示这些字节,因此从现在开始我将使用粗体 C-style 十六进制转义符 \x00 来表示它们。所以 curl 发送的请求行看起来像:

GET /\xE4\xB8\x8E HTTP/1.1

这是第一个 / 之后的三个字节。如果您查看日志的终端也支持 UTF-8,这将在您的屏幕上显示为

GET /与 HTTP/1.1

但这并不意味着您的 HTTP 请求中有 Unicode 字符。在 HTTP 级别,我们只处理字节。

nginx 似乎也乐于接受 URL 中的 non-ASCII 字节。然后是下面的正则表达式

(*UTF8)([^\w/\.\-\% ])

在 UTF-8 模式下工作将字节序列 \xE4\xB8\x8E 视为匹配 \w 的字符与,因此 header 将是

response: \xE4\xB8\x8E

您的终端显示为

response: 与

另一方面,正则表达式

([^\w/\.\-\% ])

直接在字节上工作,所以它只会匹配你路径的第一个字节,或者根本不匹配。出于某种原因,它认为序列 \xE4\xB8\x8E 的第一个字节匹配 \w (可能是因为它假设了 Latin1 或 Windows-1252 字符串) ,所以 header 将是:

response: \xE4

您的终端决定显示为

response: ?

因为字节 \xE4 后跟一个换行符是无效的 UTF-8。正则表达式 ([^\w/\.\-\% ])+ 匹配整个字节序列,因此它产生与 UTF-8 正则表达式相同的结果。

如果您看到类似

的内容
GET /\xE4\xB8\x8E HTTP/1.1

在您的日志中,那是因为日志记录代码的作者决定对 non-ASCII 字节使用转义序列。一般来说,这是一个好主意,因为无论终端配置如何,它总是产生相同的输出,并且真正显示了正在发生的事情:您的 HTTP 请求仅包含 non-ASCII 字节。

您自己的测试似乎已经回答了您的问题?

是的,nginx 支持路径中的 Unicode。

作为讨论的要点,nginx 将在位置匹配之前规范化 URL,正如 http://nginx.org/r/location. Which is why different "weird" requests (like those containing ../; or those encoding ? as %3F, thus making it part of the filename, instead of signifying the parameters known as $args 的文档中所指出的那样)可能最终仍由看起来不像一个位置的单个位置提供服务肉眼一比。

这种标准化也可以解释为什么 "same" 字符串在 access_log(预标准化)和 error_log(标准化)中出现不同。