如何使用httplib在content-type为"application/xml"时发送非ASCII字符
我在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 个回答
和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()
检查一下 self.url
是否是 Unicode 格式。如果是 Unicode 格式的话,httplib
就会把这些数据当作 Unicode 来处理。
你可以强制把 self.url
转换成 Unicode 形式,这样 httplib
就会把所有数据都当作 Unicode 来处理。
你现在遇到的问题是把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)。
历史上,HTTP允许字段内容使用ISO-8859-1字符集的文本[ISO-8859-1],并且只通过使用[RFC2047]编码来支持其他字符集。实际上,大多数HTTP头字段值仅使用US-ASCII字符集的一个子集[USASCII]。新定义的头字段应该将其字段值限制为US-ASCII八位字节。接收方应该将字段内容中的其他八位字节(obs-text)视为不透明数据。
要将XML转换为字节串,可以参考application/xml
编码注意事项:
推荐所有XML MIME实体使用不带BOM的UTF-8编码。