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/, )时, 点这里

Python WebService https ssl suds
Views (10372) Comments (5) 2012-04-13
gee :
yum install mod_ssl
老楠 Reply to gee :
在后记的那两个链接里有这部份说明。
kee :
生成私钥那一步把-des3选项去掉就不会有输密码的问题了
julysoft :
TypeNotFound: Type not found: '(string, 是啥情况呀
老楠 Reply to julysoft :
点击最后一个链接
For example, "name@something.com". If someone replies to you it will be via email.
For example, "http://someaddress.com"