如何使用httplib在content-type为"application/xml"时发送非ASCII字符

7 投票
4 回答
4829 浏览
提问于 2025-04-17 05:33

我在Python 2.7中实现了一个Pivotal Tracker API模块。这个Pivotal Tracker API要求发送的数据是一个XML文档,并且内容类型要设置为"application/xml"。

我的代码使用urlib和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字符的application/xml数据,还是说我得费点劲(比如使用Twistd和自定义的POST负载生产者)?

4 个回答

1

和JF Sebastian的回答一样,不过我加了一个新的,这样代码格式能正常显示(也更容易被谷歌找到)

下面是如果你试图在一个mechanize表单请求的末尾添加内容时会发生什么:

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()
2

检查一下 self.url 是否是 Unicode 格式。如果是 Unicode 格式的话,httplib 就会把这些数据当作 Unicode 来处理。

你可以强制把 self.url 转换成 Unicode 形式,这样 httplib 就会把所有数据都当作 Unicode 来处理。

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方法),还有可能用来存放Host HTTP头(如果客户端是HTTP/1.1),而XML文本则放在消息主体里(作为二进制数据)。

对于self.url,使用ASCII编码总是安全的(对于非ASCII域名可以使用IDNA,结果也是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编码。

撰写回答