脚本在通过REST接口传递Unicode时遇到问题

1 投票
1 回答
1177 浏览
提问于 2025-04-16 18:15

我在用Python脚本通过RESTful HTTP调用发送Unicode数据时遇到了问题。

我有一个脚本,它从网站X读取数据,然后通过REST接口把这些数据推送到网站Y。这两个系统都是开源的,运行在我们的服务器上。网站X使用PHP、Apache和PostgreSQL,而网站Y使用Java、Tomcat和PostgreSQL。处理这些数据的脚本目前是用Python写的。

总体来说,脚本运行得很好。不过,我们有一些国际用户,当处理名字中包含Unicode字符的用户时,就出现了问题。脚本的原始版本是把JSON数据读取到Python中,这些数据会自动转换成Unicode。我很确定在这之前一切都正常。为了输出数据,我使用了subprocess.Popen()来调用curl。这个方法对普通的ASCII字符有效,但在传输过程中,Unicode字符却出现了问题。我没有收到任何错误信息,但在查看网站B的结果时,数据的编码不再正确了。

我知道这些字段支持Unicode,因为我可以用Firefox手动构造请求,正确地将数据添加到网站B。

接下来的想法是,不使用curl,而是直接在Python中完成所有操作。我尝试将一个手动构造的Unicode字符串传递给Python的urllib来进行REST调用,但收到了来自urllib.urlopen()的错误: UnicodeEncodeError: 'ascii' codec can't encode characters in position 103-105: ordinal not in range(128)

有没有什么办法可以解决这个问题?我不想重写太多代码,但如果有其他更合适的脚本语言,我也很乐意听听。

这是我的Python测试脚本:

import urllib

uni = u"abc_\u03a0\u03a3\u03a9"

post = u"xdat%3Auser.login=unitest&"
post += u"xdat%3Auser.primary_password=nauihe4r93nf83jshhd83&"
post += u"xdat%3Auser.firstname=" + uni + "&"
post += u"xdat%3Auser.lastname=" + uni ;

url = u"http://localhost:8081/xnat/app/action/XDATRegisterUser"

data = urllib.urlopen(url,post).read()

1 个回答

2

关于你的测试脚本,它失败的原因是你把一个unicode对象传给了 urllib.urlencode()(这个函数是通过 urlopen() 自动调用的)。这个函数不支持unicode对象,所以它会默认用 ascii 编码来处理,这样就会出错。

处理unicode对象进行POST请求的最简单方法是明确一些;先把你的数据收集起来,构建一个字典,然后用合适的字符集对unicode值进行编码,再把这个字典进行url编码(这样就能得到一个可以POST的ascii字符串),最后发起请求。你的例子可以改写成:

import urllib
import urllib2

## Build our post data dict
data = {
    'xdat:user.login' : u'unitest', 
    'xdat:primary_password' : u'nauihe4r93nf83jshhd83', 
    'xdat:firstname' : u"abc_\u03a0\u03a3\u03a9", 
    'xdat:lastname' : u"abc_\u03a0\u03a3\u03a9", 
}

## Encode the unicode using an appropriate charset
data = dict([(key, value.encode('utf8')) for key, value in data.iteritems()])

## Urlencode it for POSTing
data = urllib.urlencode(data)

## Build a POST request, get the response
url = "http://localhost:8081/xnat/app/action/XDATRegisterUser"
request = urllib2.Request(url, data)
response = urllib2.urlopen(request)

编辑:更一般来说,当你用python发起http请求(比如 urllib2.urlopen)时,返回的内容并不会自动解码成unicode。这意味着你需要注意发送这个内容的服务器使用的编码方式。查看一下 content-type 头部;通常它会包含 charset=xyz

尽早解码你的输入,尽量晚些编码你的输出,这样做是比较明智的。

撰写回答