图 6.
关闭新打开的窗口,回到图一所示的 wsdl 编辑界面。双击 getTime 的 output 后面的箭头,定义返回值。将缺省的返回值名字 out 修改为 timeStr,类型 string,不需要改变:
图 7.
这样 getTime 就设计好了。下面回到主窗口,添加 sayHello 的定义。在 Operation 方框上单击鼠标右键,选择 Add Operation:
图 8.
同样,双击方框右边的箭头,分别设置其入口参数和返回值。sayHello 的入口参数为字符型,名字为 username。返回值也是 string 类型。
添加新的 operation,命名为 showUser。这个服务的入口参数也是用户名,类型为 string,但是它的返回值是一个复杂类型。用 WSDL Editor 可以方便地定义复杂类型。进入返回值设计窗口(双击 output 后的箭头),在 element 上单击鼠标右键,弹出菜单中选择 Set Type->New。在弹出对话框中选择 Complex Type,并将新类型命名为 userInfo。
在eclipse的outline 窗口中选中 types->userInfo,定义 userInfo。
图 9.
主窗口显示出 userInfo 的设计界面,鼠标右键单击 userInfo,选择弹出菜单的 add element,增加三个 string 类型的元素 name, gender 和 address。如下图所示:
图 10.
现在可以存盘了,三个服务都已经设计好。下一步,我们将用 python ZSI 提供的脚本处理 WSDL 文件,并生成服务代码框架。
编写 web service 服务端代码
ZSI 包提供了两个脚本用来根据 wsdl 文件生成相应的 server 端和 client 端的 python 代码框架。下面的命令生成 server 端代码:
图 11.
脚本 wsdl2py 的 -b 选项会生成一些辅助代码,后面的描述中将会看到这些辅助代码能简化编程。运行以上两条命令后,会生成三个文件:
myServices_services.py , myServices_services_server.py , myServices_services_types.py
这三个 python 文件就是服务端的代码框架。为了提供最终的 web 服务,我们还需要添加一个文件,用来实现每个 web 服务的具体代码。将新文件命名为 serviceImpl.py(完整的源代码可以在文章最后下载)。仅实现 getTime 的 serviceImpl.py 如下:
from myServices_services_server import *
from time import time,ctime
from ZSI.ServiceContainer import AsServer
class mySoapServices(myServices):
def soap_getTime(self,ps):
try:
rsp = myServices.soap_getTime(self, ps)
request = self.request
rsp.set_element_timeStr(ctime())
except Exception, e:
print str(e)
return rsp
|
首先导入 myServices_services_server,它是由 wsdl2py 脚本生成的 Web 服务框架代码。类 myServices 是 web 服务的基础类,每一个 web 服务都对应其中的一个方法。getTime 对应 myServices 类中的 soap_getTime 方法。缺省的 soap_getTime 方法只是一个基本框架,但完成了 soap 解析并且能返回该服务的入口参数对象。
为了实现 getTime,我们需要重载 soap_getTime 方法。定义新类 mySoapServices,继承自 myServices。在 mySoapServices 类中重载父类的 soap_getTime() 方法。
getTime 的主要功能是返回一个表示当前时间的字符串。python 系统函数 ctime,就可以得到当前的系统时间。重载 soap_getTime() 函数中,首先调用父类的 soap_getTime() 方法,得到返回值对象rsp。
调用返回值对象 rsp 的 set_element_xxx() 方法,就可以对返回值对象中的元素进行赋值。这个方法是由 wsdl2py 的 -b 选项生成的。
set_element_timeStr(ctime()) 将返回值的 timeStr 元素赋值为代表当前时间的字符串。
sayHello() 的代码与此类似。但是与 getTime 不同,sayHello 服务还需要处理客户端调用时传入的入口参数。sayHello 方法的源代码:
def soap_sayHello(self,ps):
try:
rsp = myServices.soap_sayHello(self,ps)
request = self.request
usrName = request.get_element_userName()
rsp.set_element_helloStr("Hello "+usrName)
except Exception, e:
print str(e)
return rsp
|
request 代表入口参数对象。对于 sayHello 服务,入口参数只有一个元素 userName。调用 request 对象的 get_element_userName() 方法就可以得到该元素的值。
调用返回值对象 rsp 的 set_element_helloStr 将返回字符串赋值给 helloStr 元素。
showUser 服务与前面两个服务的不同在于返回值是一个复杂对象,该复杂对象在 python 中可以用下面这个类来表示:
class userInfo:
def __init__(self,nm,gen,addr):
self.name = nm
self.gender = gen
self.address = addr
|
showUser 服务根据客户端传入的用户名在数据库中查找该用户的详细信息并填充 userInfo 对象,相应代码如下:
def soap_showUser(self,ps):
try:
rsp = myServices.soap_showUser(self,ps)
request = self.request
uName = request.get_element_userName()
userDetail = rsp.new_user()
nm=self.users[uName].name
userDetail.set_element_name(nm)
gender=self.users[uName].gender
userDetail.set_element_gender(gender)
addr=self.users[uName].address
userDetail.set_element_address(addr)
rsp.set_element_user(userDetail)
except Exception, e:
print str(e)
return rsp
|
调用 request 对象的 get_element_userName 方法得到入口参数,并赋值给 uName。然后在数据库中查找用户uName的详细信息,将详细信息填充到 userInfo 类对象中,并返回。作为演示,我们并没有真的到数据库中查询,而是在内存中建立一个字典:
u1 = userInfo("u1","M","Shanghai")
u2 = userInfo("u2","F","Beijing")
self.users={}
self.users["u1"]=u1
self.users["u2"]=u2
|
该字典中有两个用户:u1 和 u2。演示代码在该字典中查询用户,将查选结果返回用户。
调用 rsp 对象的 new_element_user() 方法创建一个新的返回对象,并用 userDetail 保存。
调用 userDetail.set_element_gender 将用户性别信息设置到返回值对象的 gender 元素中。同样方法设置用户名和地址。
最后将新建的 userDetail 对象设置到返回值 rsp 中:rsp.set_elememnt_user(userDetail)。
发布 web service
所有 web 服务代码都已经写好了,需要服务器代码来发布它们。在复杂并且有较高要求的应用环境中,用户可能需要用 apache 等强大的 web server 来发布 web services。限于篇幅,本文不打算介绍如何在 apache 上发布 python web services。本文将使用 ZSI 自带的 SOAP server。
正如下图所示,使用 ZSI soap server 只需要很少的几行代码:
from ZSI.ServiceContainer import AsServer
from serviceImpl import mySoapServices
from ZSI import dispatch
if __name__ == "__main__":
port = 8888
AsServer(port,(mySoapServices('test'),))
|
这段代码无需太多解释。port 定义了 web service 发布的端口号。ZSI 包的 AsServer 方法只有两个参数:一个是端口;另外一个是包含了 web 服务实现代码的类,在我们的实验中就是 mySoapServices。字符串 test,表示 web 服务发布时的虚拟路径。当上述代码成功运行之后,就会在 localhost 上开启一个 web server,并在端口 8888 发布 myServices 服务。一切都非常简单,体现了用 python 语言的最吸引人的特点,快速而强大!
我们将在本机访问 myServices,相应的 URL 为 http://localhost/test?wsdl。
编写 java 客户端
现在我们使用 eclipse 集成环境来开发 web services 的客户端程序,调用前面章节描述的那些 web services。
Eclipse 提供了一个简单的方法来创建 web service 应用程序,即 Web Service Wizard。
首先创建一个 Web Project。
打开 File->New->Other…->Dynamic Web Project,创建一个新的工程。
图 12.
然后就可以创建 java 客户端。选择 File -> New -> Other... -> Web Services -> Web Service Client
图 13.
选择 Next,在下一个窗口中的 Service Definition 中填写相应的 webservice 的发布地址 URL。在本文中为: http://localhost:8888/test?wsdl
图 14.
选择 Finish 按钮。将自动生成 java 代码。包括以下几个文件: MyService_PortType.java MyService_Service.java MyService_ServiceLocatior.java MyServiceProxy.java MyServiceSOAPStub.java
另外 showUser() 返回一个复杂对象,所以 eclipse 还创建了一个 java 类表示该复杂对象类,文件名为 UserInfo.java
作为测试,我们写了一个 java 小程序,调用 getTime。
import org.example.www.myService.MyServiceProxy;
public class HelloClient {
public static void main(String[] args){
try {
System.out.println("Step1");
MyServiceProxy hello = new MyServiceProxy();
System.out.println("Step2");
java.lang.String str = hello.getTime();
System.out.println("step over");
System.out.println(str);
}
catch (Exception ex)
{
System.out.println(ex.getMessage());
}
}
}
|
sayHello 和 showUser 的调用代码与上面的示例类似。
总结
用 Eclipse 的 WTP 开发 WSDL 文件,用 python 实现 Web 服务都比较简单而快速。用这两个强大的工具能够迅速地开发 Web 服务应用,适用于原型产品的快速开发。 这样就能抓住先机,比对手更快的推出新的Web应用,从而在市场上立于不败之地。
参考资料