我有一个非常简短的示例代码:
print("Content-Type: text/plain; charset=utf-8")
print("Access-Control-Allow-Origin: *")
print()
x = 'Chloë'.encode()
print(x)
print(x.decode())
注意非Ascii ë,这是所有问题的根源。
使用python3 ./test.py
在bash中调用脚本会产生以下(正确的)输入:
Content-Type: text/plain; charset=utf-8
Access-Control-Allow-Origin: *
b'Chlo\xc3\xab'
Chloë
然而,从浏览器调用它,最后一行不存在(标题当然不可见,但它们存在)。所以唯一可见的部分是:
b'Chlo\xc3\xab'
你知道吗,哪里可能有问题?
答案 0 :(得分:2)
您正在将Unicode打印到sys.stdout
句柄(这是print()
写入的默认文件对象)。然后,该对象必须再次对您的数据进行编码,但必须根据它所连接的环境进行编码。
当你运行python3 ./test.py
然后你连接到你的终端或控制台,它通常被配置为告诉脚本什么编解码器是合适的。在POSIX系统(Linux,Mac)上,您可以运行locale
命令来查看该配置是什么。在您的控制台语言环境中,显示非{ASCII}代码点(例如ë
。
但是当作为连接到Web服务器的CGI脚本运行时,不存在这样的语言配置,并且Python几乎肯定已经回归到最低的公分母:ASCII。在这种情况下,尝试打印非Unicode文本将导致异常:
$ LC_ALL="en_US.UTF-8" python3 -c "print(b'Chlo\xc3\xab'.decode())"
Chloë
$ LC_ALL="C" python3 -c "print(b'Chlo\xc3\xab'.decode())" # C => "no locale set"
Traceback (most recent call last):
File "<string>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character '\xeb' in position 4: ordinal not in range(128)
由于异常仅在生成标头和所有其他输出之后发生,因此您不会看到HTTP错误代码。但是,应该已在服务器错误日志中记录该异常。
如果您的脚本要按照发出的Content-Type标头中的配置将UTF-8输出到浏览器,替换 sys.stdout
以强制执行该编解码器:
import sys
from io import TextIOWrapper
sys.stdout = TextIOWrapper(sys.stdout.buffer.detach(), encoding='utf8')
在Python 3中,用于sys.stdout
流的文本文件包含一个缓冲区对象,该缓冲区对象又包含一个二进制文件对象,用于处理实际的二进制数据写入。外部文本文件对象只负责写入时的编码。上面用不同的外部对象替换总是编码为UTF-8的外部对象。