Python 3字节在CGI脚本中使用非ascii字符进行解码

时间:2018-06-01 11:57:01

标签: python python-3.x encoding http-headers decode

我有一个非常简短的示例代码:

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'

你知道吗,哪里可能有问题?

1 个答案:

答案 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的外部对象。