7

我在 Python 2.7 中实现了一个 Pivotal Tracker API 模块。Pivotal Tracker API要求 POST 数据为 XML 文档,“application/xml”为内容类型。

我的代码使用 urllib/httplib 来发布文档,如下所示:

    request = urllib2.Request(self.url, xml_request.toxml('utf-8') if xml_request else None, self.headers)
    obj = parse_xml(self.opener.open(request))

当 XML 文本包含非 ASCII 字符时,这会产生异常:

File "/usr/lib/python2.7/httplib.py", line 951, in endheaders
  self._send_output(message_body)
File "/usr/lib/python2.7/httplib.py", line 809, in _send_output
  msg += message_body
exceptions.UnicodeDecodeError: 'ascii' codec can't decode byte 0xc5 in position 89: ordinal not in range(128)

据我所见,httplib._send_output 正在为消息有效负载创建一个 ASCII 字符串,大概是因为它希望数据是 URL 编码的(application/x-www-form-urlencoded)。只要只使用 ASCII 字符,它就可以与 application/xml 一起正常工作。

是否有一种直接的方法来发布包含非 ASCII 字符的应用程序/xml 数据,或者我将不得不跳过箍(例如,使用 Twistd 和 POST 有效负载的自定义生产者)?

4

4 回答 4

8

您正在混合 Unicode 和字节串。

>>> msg = u'abc' # Unicode string
>>> message_body = b'\xc5' # bytestring
>>> msg += message_body
Traceback (most recent call last):
  File "<input>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc5 in position 0: ordinal \
not in range(128)

要修复它,请确保self.headers内容已正确编码,即 中的所有键、值headers应该是字节串:

self.headers = dict((k.encode('ascii') if isinstance(k, unicode) else k,
                     v.encode('ascii') if isinstance(v, unicode) else v)
                    for k,v in self.headers.items())

注意:标头的字符编码与正文的字符编码无关,即xml文本可以独立编码(从http消息的角度来看,它只是一个八位字节流)。

这同样适用于self.url——如果它有unicode类型;将其转换为字节串(使用 'ascii' 字符编码)。


HTTP 消息由一个起始行、“标题”、一个空行和可能的消息体组成,因此self.headers用于标题,self.url用于起始行(http 方法在这里),可能用于Hosthttp 标题(如果客户端是 http /1.1),XML 文本转到消息正文(作为二进制 blob)。

使用 ASCII 编码总是安全的self.url(IDNA 可用于非 ascii 域名——结果也是 ASCII)。

以下是rfc 7230 关于 http标头字符编码的说明:

从历史上看,HTTP 允许带有 ISO-8859-1 字符集 [ISO-8859-1] 中的文本的字段内容,仅通过使用 [RFC2047] 编码来支持其他字符集。实际上,大多数 HTTP 标头字段值仅使用 US-ASCII 字符集 [USASCII] 的一个子集。新定义的标头字段应该将其字段值限制为 US-ASCII 八位字节。接收者应该将字段内容(obs-text)中的其他八位字节视为不透明数据。

要将 XML 转换为字节串,请参阅application/xml编码注意事项

建议对所有 XML MIME 实体使用不带 BOM 的 UTF-8。

于 2011-11-03T10:31:06.970 回答
2

检查是否self.url是unicode。如果是 unicode,则将httplib数据视为 unicode。

您可以强制将 self.url 编码为 un​​icode,然后 httplib 会将所有数据视为 unicode

于 2013-06-09T06:30:31.933 回答
1

与 JF Sebastian 的答案相同,但我正在添加一个新的,以便代码格式化工作(并且更适合谷歌)

如果您尝试在机械化表单请求的末尾添加标签,会发生以下情况:

br = mechanize.Browser()
br.select_form(nr=0)
br['form_thingy'] = u"Wonderful"
headers = dict((k.encode('ascii') if isinstance(k, unicode) else k, v.encode('ascii') if isinstance(v, unicode) else v) for k,v in br.request.headers.items())
br.addheaders = headers
req = br.submit()
于 2016-04-16T15:33:17.987 回答
0

这里需要介绍 3 件事

  • 非 Unicode 字符串 + Unicode 字符串,结果会自动转换为 Unicode 字符串。
  • Python 2.7 httplib,简单地使用 + 将 header 与 body 连接起来,我认为这不是一个好习惯,我们不应该相信自动类型转换。但是 Python 2.6 httplib 是不同的。
  • HTTP 协议标准建议对标头进行ISO-8859-1编码,但如果要放置非ISO-8859-1字符,则必须将其编码为rfc2047描述

简单的解决方案是在发送之前将 header 和 body 严格编码为 utf-8。

于 2015-07-04T10:20:52.593 回答