我在Python 2.7中实现了Pivotal Tracker API模块。 Pivotal Tracker API期望POST数据为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字符的应用程序/ xml数据,或者我是否必须跳过箍(例如使用Twistd和POST负载的自定义生产者)?
答案 0 :(得分:7)
您正在混合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
类型;将其转换为bytestring(使用'ascii'字符编码)。
HTTP message consists of a start-line, "headers", an empty line and possibly a message-body因此self.headers
用于标题,self.url
用于起始行(http方法在此处),可能用于Host
http标头(如果客户端是http / 1.1),XML文本转到消息体(作为二进制blob)。
对 self.url
使用ASCII编码总是安全的(IDNA可用于非ascii域名 - 结果也是ASCII)。
以下是rfc 7230 says about http headers character encoding:
历史上,HTTP允许字段内容包含文本 ISO-8859-1 charset [ISO-8859-1],仅支持其他字符集 通过使用[RFC2047]编码。在实践中,大多数HTTP标头 字段值仅使用US-ASCII字符集[USASCII]的子集。 新定义的标题字段应该将其字段值限制为 US-ASCII八位字节。收件人应该在字段中处理其他八位字节 内容(obs-text)为不透明数据。
要将XML转换为字节字符串,请参阅application/xml
encoding condsiderations:
对于所有XML MIME实体,建议使用不带BOM的UTF-8。
答案 1 :(得分:2)
检查self.url
是否为unicode。如果是unicode,则httplib
会将数据视为unicode。
你可以强制编码self.url为unicode,然后httplib将所有数据视为unicode
答案 2 :(得分: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()
答案 3 :(得分:0)
这里有三件事要讨论
简单的解决方案是在发送之前严格地将标题和正文编码为utf-8。