Python调用基于https协议的WebService
近年的Web开发,很多接口数据交换通常倾向于使用json格式作为数据源,相对WebService来说,json有轻量,容易使用在Ajax程序和移动App中的优点。但是最近在开发一个对安全性要求相对较高的应用时,第三方的接口是基于https的WebService,并要求对提交的数据进行签名。对方使用的是java,一开始我想直接就使用java来开发这个功能,但后来考虑到这个应用接下来可能会跟不同的第三方接口打交道,并且会涉及不同的技术,最后还是决定用Python来实现(胶水语言的优势出来了嘛)。
这篇文章只谈Python调用https协议的WebService,不谈数据签名。
网上查了下,基本上无外乎以下几种思路:
1、把WebService视为普通的xml源,用httplib或pycurl来获取(urllib和lurllib2在python2.6后支持https,但不支持证书验证?),但这样一来,就得自己去分析WebService,太繁琐,这么普遍的应用,我猜应该有现成的库,不去“重复造轮子”了。
2、用PyXML, fpconst, SOAPpy一系统包来实现,但这些包04年05年左右就不再维护了,并且多数不再提供python2.5以上的版本的支持,现在我们用的是Python2.7,显然不合适。
3、http://stackoverflow.com/questions/206154/whats-the-best-soap-client-library-for-python-and-where-is-the-documentation-f 这里有一帮人在争论"What's the best SOAP client library for Python"(什么是最好的soap客户端),于是发现了SUDS,对英文有点头痛的同志可以点下这个链接http://www.cnblogs.com/walkerwang/archive/2011/07/27/2118401.html,就这样比较去比较来,最后决定使用SUDS了。
SUDS的安装(Mac OS X/CentOS):
$ sudo easy_install -z suds
如果你看到有类似Finished processing dependencies for suds的一句话出现,那就是安装成功了。当然为了验证是不是真成功了,你还可以在终端里测试一下:
$ python
$ from suds.client import Client
要是没有反应,就是肯定,绝对是安装成功了,要是出现什么No Module之类的,不好意思,你要自己Google之来解决了。
调用WebService的类
# coding=utf-8
import urllib2 as u2
from suds.client import Client
from suds.transport.http import HttpTransport, Reply, TransportError
import httplib
class HTTPSClientAuthHandler(u2.HTTPSHandler):
def __init__(self, key, cert):
u2.HTTPSHandler.__init__(self)
self.key = key
self.cert = cert
def https_open(self, req):
#Rather than pass in a reference to a connection class, we pass in
# a reference to a function which, for all intents and purposes,
# will behave as a constructor
return self.do_open(self.getConnection, req)
def getConnection(self, host, timeout=300):
return httplib.HTTPSConnection(host, key_file=self.key, cert_file=self.cert)
class HTTPSClientCertTransport(HttpTransport):
def __init__(self, key, cert, *args, **kwargs):
HttpTransport.__init__(self, *args, **kwargs)
self.key = key
self.cert = cert
def u2open(self, u2request):
"""
Open a connection.
@param u2request: A urllib2 request.
@type u2request: urllib2.Requet.
@return: The opened file-like urllib2 object.
@rtype: fp
"""
tm = self.options.timeout
url = u2.build_opener(HTTPSClientAuthHandler(self.key, self.cert))
if self.u2ver() < 2.6:
socket.setdefaulttimeout(tm)
return url.open(u2request)
else:
return url.open(u2request, timeout=tm)
# These lines enable debug logging; remove them once everything works.
def getClient(url, key, cert):
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger('suds.client').setLevel(logging.DEBUG)
logging.getLogger('suds.transport').setLevel(logging.DEBUG)
c = Client(url,
transport = HTTPSClientCertTransport(key, cert))
return c
以上代码来自http://stackoverflow.com/questions/6277027/suds-over-https-with-cert
调用方法
wsdl_url = 'https://www.yourdomain.com/services/HelloWorldService?wsdl'
websev = websevice(wsdl_url)
#调用方式websev.方法名(参数)
ws = websev.hello('A.R.')
def websevice(url):
key = '/blablabla/yourcert.key' #这里换成你调用端的私钥文件
cert = '/blablabla/yourcert.cer' #这里换成你要调用的网址的客户端证书文件
client = getClient(url, key, cert)
result = client.service
看起来大功告成了,可是接下来我还是被折腾得够呛,主要是在私钥和证书问题上。
首先,先用IE打开你要调用的WebServie地址,去导出证书(别问我怎么在Chrome或FF里导出,我也不会,你要是会的话,在评论里教我一下)。
打开IE,输入你要调用的WebService地址后,点击下地址栏那个锁
然后点击那个“View certificates",如果是中文的话应该是“查看证书”之类的东东。
在随后出来的窗口中点击“复制文件"
在这一步记住,一定要导出为PEM格式,也就是第二个选项Base64编码,最后Next,Next保存为你想要的文件名就可以了。这就是你编程调用所需要的证书文件了。
生成私钥:
$ openssl genrsa -des3 -out yourcert.key 1024
好了,两个文件都有了,看起来万事具备了,可是测试结果是,总是在输出窗口叫我输入一个什么PEM pass才能正常通过(也就是刚才生成的时候设定的那个),这样子不行啊,这是无人值守的应用,总不能每调用一次就人工来输一次密码。只好又继续查文档,一开始以为是httplib.HTTPSConnection有相应的参数和用法,查了半天不得其解,阿门,悲催的是,昨天访问境外网站总出问题(据说是G**调整?Fuck G**),折腾一个上午没有结果。
终于在今天早上,皇天不负哥这种人,在http://hints.macworld.com/article.php?story=20041129143420344上找到这么一个方法,可以生成不需要每次输入那个劳什子PEM pass的私钥了(基于第一次生成的私钥生成)。
Next, you will have generate a non-password protected copy of the key for Apache so that it can start up without errors.
$ openssl rsa -in yourcert.key -out yourcert.nopass.key
This will generate a non-password protected copy of the private key you just generated. 别忘了把上面调用的那句
key = '/blablabla/yourcert.key'
改成
key = '/blablabla/yourcert.nopass.key'
ok,这个问题总算解决了,看来主要还是对https了解不够深的原因,用小学作文的结尾来感叹的话是“经过这两天的折腾,我认识到了学无止境啊”。
后记:
本机运行好好的,但部署到服务器上出现"Error was: 'module' object has no attribute 'HTTPSHandler'",
以下两个链接可以帮助解决这个问题:
http://www.geektu.com/?p=78
http://paltman.com/2007/11/15/getting-ssl-support-in-python-251/
再后记: 当调用时出现TypeNotFound: Type not found: '(string, http://schemas.xmlsoap.org/soap/encoding/, )时, 点这里