翻译:soaplib v2.0.0beta documentation
Hello World
这个例子使用soaplib中简单的wsgi-webservice来部署此服务。
声明一个Soaplib Service
import soaplib
from soaplib.core.service import rpc, DefinitionBase
from soaplib.core.model.primitive import String, Integer
from soaplib.core.server import wsgi
from soaplib.core.model.clazz import Array
class HelloWorldService(DefinitionBase):
@soap(String,Integer,_returns=Array(String))
def say_hello(self,name,times):
results = []
for i in range(0,times):
results.append('Hello, %s'%name)
return results
if __name__=='__main__':
try:
from wsgiref.simple_server import make_server
soap_application = soaplib.core.Application([HelloWorldService], 'tns')
wsgi_application = wsgi.Application(soap_application)
server = make_server('localhost', 7789, wsgi_application)
server.serve_forever()
except ImportError:
print "Error: example server code requires Python >= 2.5"
解析这个示例:DefinitionBase是所有soap服务的基类
from soaplib.core.service import DefinitionBase
rpc装饰器暴露方法名作为soap的调用方法,并且声明它所接收和返回的数据类型
from soaplib.core.service import rpc
导入该方法的模型(稍后会有更多关于该模型的介绍)
from soaplib.core.model.primitive import String, Integer
from soaplib.core.model.clazz import Array
创建一个可以作为WSGI应用程序部署的soap服务的简单方法是:扩展DefinitionBase
class HelloWorldService(DefinitionBase):
rpc装饰器将每个方法标记为soap方法,并定义soap参数的类型,顺序,以及返回值。下面代码定义的方法接受一个String,一个Integer并返回一个String数组->Array(String)
@soap(String,Integer,_returns=Array(String))
方法本身没有任何特殊之处,所有输入的变量和返回类型都是标准的python对象。
def say_hello(self,name,times):
results = []
for i in range(0,times):
results.append('Hello, %s'%name)
return results
部署服务
soaplib已经用其他的几个web服务测试过,这个例子使用简单的wsgi web服务,任何与WSGI兼容的服务器都可以运行。
if __name__=='__main__':
try:
from wsgiref.simple_server import make_server
soap_application = soaplib.core.Application([HelloWorldService], 'tns')
wsgi_application = wsgi.Application(soap_application)
server = make_server('localhost', 7789, wsgi_application)
server.serve_forever()
except ImportError:
print "Error: example server code requires Python >= 2.5"
调用这个服务
>>> from suds.client import Client
>>> hello_client = Client('http://localhost:7789/?wsdl')
>>> result = hello_client.service.say_hello("Dave", 5)
>>> print result
(stringArray){
string[] =
"Hello, Dave",
"Hello, Dave",
"Hello, Dave",
"Hello, Dave",
"Hello, Dave",
}
suds是一个单独的项目,用于在python中构建轻量级的soap客户端。想要了解更多信息,请访问该项目的网页:
https://fedorahosted.org/suds/
用户管理
让我们来试一下比字符串和整型作为参数更复杂的例子!下面是一个使用了复杂嵌套数据类型但极其简单的例子。
from soaplib.core import Application
from soaplib.core.server import wsgi
from soaplib.core.service import soap
from soaplib.core.service import DefinitionBase
from soaplib.core.model.primitive import String, Integer
from soaplib.core.model.clazz import ClassModel, Array
user_database = {}
userid_seq = 1
class Permission(ClassModel):
application = String
feature = String
class User(ClassModel):
userid = Integer
username = String
firstname = String
lastname = String
permissions = Array(Permission)
class UserManager(DefinitionBase):
@soap(User,_returns=Integer)
def add_user(self,user):
global user_database
global userid_seq
user.userid = userid_seq
userid_seq = userid_seq + 1
user_database[user.userid] = user
return user.userid
@soap(Integer,_returns=User)
def get_user(self,userid):
global user_database
return user_database[userid]
@soap(User)
def modify_user(self,user):
global user_database
user_database[user.userid] = user
@soap(Integer)
def delete_user(self,userid):
global user_database
del user_database[userid]
@soap(_returns=Array(User))
def list_users(self):
global user_database
return [v for k,v in user_database.items()]
if __name__=='__main__':
from wsgiref.simple_server import make_server
soap_app = Application([UserManager], 'tns')
wsgi_app = wsgi.Application(soap_app)
server = make_server('localhost', 7789, wsgi_app)
server.serve_forever()
让我们跳到新东西的地方:
class Permission(ClassModel):
application = String
feature = String
class User(ClassModel):
userid = Integer
username = String
firstname = String
lastname = String
permissions = Array(Permission)
例子中的Permission和User结构是标准的python对ClassModel扩展的对象。SoapLib将ClassModel用作一般类型,扩展后将生成可在SOAP服务中使用的复杂可序列化类型。
模型
在Soaplib中,将单独的参数转化组成xml,并提供构建wsdl的必要的信息。Soaplib有许多内置类型,这些内置类型为你提供大多数一般所必需的通用数据类型。
Primitives(基元)
基本的基元类型包括:String,Integer,DateTime,Null,Float,Boolean.
这些事soaplib.core中最基本的块。
>>> from soaplib.core.model.primitive import String
>>> from lxml import etree
>>> parent = etree.Element("parent")
>>> String.to_parent_element("abcd", "tns", parent)
>>> string_element = parent.getchildren()[0]
>>> print etree.tostring(string_element)
<ns0:retval xmlns:ns0="tns" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">abcd</ns0:retval>
>>> print String.from_xml(string_element)
abcd
>>> String.get_type_name()
'string'
>>> String.get_type_name_ns()
'xs:string'
Array(数组)
soaplib中唯一可用的集合类型是Array(数组)类型。与基元类型不同,Arrays需要使用合适的内置类型进行实例化,因此他们可以完全(反)序列化数据。Arrays是同性质的,意味着他所保存的数据都是同一类型。对于混合类型和更多的动态数据,请使用Any类型。
>>> from soaplib.core.model.clazz import Array
>>> from soaplib.core.model.primitive import String
>>> from lxml import etree
>>> parent = etree.Element("parent")
>>> array_serializer = Array(String)
>>> array_serializer.to_parent_element(['a','b','c','d'], 'tns', parent)
>>> print etree.tostring(element)
<ns0:stringArray xmlns:ns0="tns"><ns1:string xmlns:ns1="None" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">a</ns1:string>
<ns2:string xmlns:ns2="None" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">b</ns2:string>
<ns3:string xmlns:ns3="None" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">c</ns3:string>
<ns4:string xmlns:ns4="None" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">d</ns4:string></ns0:stringArray>
>>> print array_serializer.from_xml(element)
['a', 'b', 'c', 'd']
Class(类)
ClassModel类型是用来定义和序列化复杂的嵌套结构。
>>> from soaplib.core.model.primitive import String, Integer
>>> from soaplib.core.model.clazz import ClassModel
>>> from lxml import etree
>>> class Permission(ClassModel):
... __namespace__ = "permission"
... application = String
... feature = String
>>>
>>> class User(ClassModel):
... __namespace__ = "user"
... userid = Integer
... username = String
... firstname = String
... lastname = String
... permissions = Array(Permission)
>>>
>>> u = User()
>>> u.username = 'bill'
>>> u.permissions = []
>>> p = Permission()
>>> p.application = 'email'
>>> p.feature = 'send'
>>> u.permissions.append(p)
>>> parent = etree.Element('parenet')
>>> User.to_parent_element(u, 'tns', parent)
>>> element = parent[0]
>>> etree.tostring(element)
'<ns0:User xmlns:ns0="tns">
<ns1:username xmlns:ns1="None" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">bill</ns1:username>
<ns2:firstname xmlns:ns2="None" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
<ns3:lastname xmlns:ns3="None" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
<ns4:userid xmlns:ns4="None" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
<ns5:permissions xmlns:ns5="None"><ns5:Permission><ns5:application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">email</ns5:application>
>>> User.from_xml(element).username
'bill'
>>>
Attachment(附件)
Attachment序列化程序被用于以base64编码字符串的形式传输二进制数据。在附件类中的数据可以被手动加载,或者从文件中读取。二进制数据的所有编码都将在被发送前完成,并在附件被接收后立即解码。
>>> from soaplib.core.model.binary import Attachment
>>> from lxml import etree
>>> a = Attachment(data='my binary data')
>>> parent = etree.Element('parent')
>>> Attachment.to_parent_element(a, "tns", parent )
>>> element = parent[0]
>>> print etree.tostring(element)
<ns0:retval xmlns:ns0="tns">bXkgYmluYXJ5IGRhdGE=
</ns0:retval>
>>> print Attachment.from_xml(element)
<soaplib.core.model.binary.Attachment object at 0x5c6d90>
>>> print Attachment.from_xml(element).data
my binary data
>>> a2 = Attachment(fileName='test.data') # load from file
Any(任意类型)
Any类型是用于传输非结构化xml数据的序列化程序。Any类型在处理动态数据中非常有用,并为使用soaplib.core传递数据提供了一个非常Python化的方式。Any序列化程序无法执行任何功能性任务,因为传入和返回的都是元素类型。Any类型最主要的目的在于声明它在WSDL中的存在。
AnyAsDict
AnyAsDict类型的作用和Any相同,除了它通过列表来正反序列化字典,而不是从一个原始的lxml.etree._Element对象来进行序列化。
Custom(自定义)
Soaplib提供一个非常简单的接口来自定义类型。只需要继承soaplib.core.model.base.Base并重写 from_xml(), to_parent_element() 和 add_to_schema()。
classmethods:
from soaplib.core.model.base import Base
class MySerializer(Base):
@classmethod
def to_parent_element(self,value,name='retval'):
pass
@classmethod
def from_xml(self,element):
pass
@classmethod
def add_to_schema(self,added_params):
pass
二进制文件
在SOAP中,用来表现二进制数据最常见的方法是base64编码的字符串。Soaplib使用‘Attachment’序列化程序来处理所有二进制数据的编码和解码,并提供一些功能性函数来管理内存和磁盘中的二进制数据。
>>> from soaplib.core.model.binary import Attachment
>>> from lxml import etree
>>> a = Attachment(data="this is my binary data")
>>> parent = etree.Element("parent")
>>> Attachment.to_parent_element(a, "tns", parent)
>>> print et.tostring(parent)
<ns0:retval xmlns:ns0="tns">bXkgYmluYXJ5IGRhdGE=
</ns0:retval>
>>>
如果你想要返回一个二进制文件,很简单:
>>> from soaplib.core.model.binary import Attachment
>>> from lxml import etree as et
>>> a = Attachment(file_name="mydata")
>>> parent = etree.Element("parent")
>>> Attachment.to_parent_element(a, "tns", parent)
>>> print et.tostring(parent)
<ns0:retval xmlns="">dGhpcyBpcyBteSBiaW5hcnkgZGF0YQ==
</ns0:retval>
>>>
一个用于保存文档的服务示例:
from soaplib.core.service import rpc, DefinitionBase
from soaplib.core.model.primitive import String, Integer
from soaplib.core.model.clazz import Array
from soaplib.core.model.binary import Attachment
from soaplib.core.server import wsgi
from tempfile import mkstemp
import os
class DocumentArchiver(DefinitionBase):
@soap(Attachment,_returns=String)
def archive_document(self,document):
'''
This method accepts an Attachment object, and returns the filename of the
archived file
'''
fd,fname = mkstemp()
os.close(fd)
document.file_name = fname
document.save_to_file()
return fname
@soap(String,_returns=Attachment)
def get_archived_document(self,file_path):
'''
This method loads a document from the specified file path
and returns it. If the path isn't found, an exception is
raised.
'''
if not os.path.exists(file_path):
raise Exception("File [%s] not found"%file_path)
document = Attachment(file_name=file_path)
# the service automatically loads the data from the file.
# alternatively, The data could be manually loaded into memory
# and loaded into the Attachment like:
# document = Attachment(data=data_from_file)
return document
if __name__=='__main__':
from wsgiref.simple_server import make_server
soap_app = soaplib.core.Application([DocumentArchiver], 'tns')
wsgi_app = wsgi.Application(soap_app)
server = make_server('localhost', 7789, wsgi_app)
server.serve_forever()
消息API
除了WSGI服务API之外,soaplib还提供一个通用的Message API,这个API允许你程序化构建SOAP消息的xml部分。这个消息类依附于 to_parent_element/from_xml 用作类型的用法模板。
>>> from soaplib.core.soap import Message
>>> from soaplib.core.model.primitive import String, Integer, Float
>>> import lxml.etree as et
>>> message = Message('myFunction',[('a',String),('b',Integer),('c',Float)])
>>> print et.tostring(message.to_parent_element('a',13,3.14))
<myFunction><a xmlns="" xsi:type="xs:string">a</a><b xmlns="" xsi:type="xs:int">13</b><c xmlns="" xsi:type="xs:float">3.14</c></myFunction>
>>>
Message可以和MethodDescriptor对象结合,用来表示一个SOAP方法调用中输入和输出的消息。方法介绍的作用并不是简单的保存方法名称,输入和输出的消息,它还可以和简单的soap客户端一起被用于远程SOAP服务的调用。The Axis stock quote 客户端的示例可以这样写:
in_message = Message('getPrice',[('symbol',String)],ns='http://quickstart.samples/xsd')
out_message = Message('getPriceResponse',[('return',Float)],ns='http://quickstart.samples/xsd')
method = MethodDescriptor('getPrice',in_message,out_message)
client = SimpleSoapClient('localhost:8080','/axis2/services/StockQuoteService',method)
print client('IBM')
消息名称也可以在ElementTree QName 语法中指定(比如 “{http://quickstart.samples/xsd}getPrice” )并且可以为消息指定可选的“type”关键词,该关键词在WSDL生成时被使用。
Hooks(轮子)
下面的例子是HelloWorld示例的加强版,它使用‘hooks’服务将横截行为运用于该服务。在本例中,hooks服务用于收集方法执行和整个调用期间的性能信息,包括序列化和反序列化。这些可用的轮子包括:
on_call()
This is the first thing called in the service
soaplib.core.service.DefinitionBase.on_wsdl()
Called before the wsdl is requested
on_wsdl_exception()
Called after an exception was thrown when generating the wsdl (shouldn’t happen very much)
on_method_call()
Called right before the service method is executed
on_method_return()
Called right after the service method is executed
on_method_exception_object()
Called when an exception occurred in a service method before the exception is serialized.
on_method_exception_xml()
Called after an exception occurred in either the service method or in serialization
on_return()
This is the very last thing called before the wsgi app exits
这些函数可以被轻易的应用于服务中所有的方法的cross-cutting functionality,从而完成数据库事务管理,日志记录和测量性能。此示例中也使用了threadlocal请求(soaplib.core.wsgi_soap.request)对象来保存请求的数据点。
from soaplib.core.service import rpc, DefinitionBase
from soaplib.core.model.primitive import String, Integer
from soaplib.core.model.clazz import Array
from time import time
class HelloWorldService(DefinitionBase):
@soap(String,Integer,_returns=Array(String))
def say_hello(self,name,times):
results = []
for i in range(0,times):
results.append('Hello, %s'%name)
return results
def on_call(self,environ):
request.additional['call_start'] = time()
def on_method_exec(self,environ,body,py_params,soap_params):
request.additional['method_start'] = time()
def on_results(self,environ,py_results,soap_results,http_headers):
request.additional['method_end'] = time()
def on_return(self,environ,returnString):
call_start = request.additional['call_start']
call_end = time()
method_start = request.additional['method_start']
method_end = request.additional['method_end']
print 'Method took [%s] - total execution time[%s]'%(method_end-method_start,call_end-call_start)
if __name__=='__main__':
from wsgiref.simple_server import make_server
server = make_server('localhost', 7789, Application([HelloWorldService]))
server.serve_forever()
运行这个程序:
Method took [0.000195980072021] - total execution time[0.00652194023132]
Method took [0.000250101089478] - total execution time[0.00567507743835]
Method took [0.000144004821777] - total execution time[0.00521206855774]
Method took [0.000141859054565] - total execution time[0.00512409210205]
Method took [0.00377607345581] - total execution time[0.00511980056763]
Method took [0.00118803977966] - total execution time[0.00673604011536]
Method took [0.000146150588989] - total execution time[0.00157499313354]
Method took [0.0231170654297] - total execution time[0.0245010852814]
Method took [0.000166893005371] - total execution time[0.01802110672]
这也许有助于在进程中找到瓶颈,但此技术也可以被用于提交/回滚事务,或为服务中的所有方法提供设置/拆卸操作。
Axis Interoperability(引擎互用性)
Apache Axis 项目是目前最流行的SOAP实现引擎之一,所以对于Soaplib来说,能够轻松的使用它是非常重要的。作为soaplib的子项目,axis_soaplib测试套件被创造出来作为soaplib和Axis能够了解彼此的保证。此测试使用Apache Ant来运行并生成客户端使用wsdl2java命令的java类。
怎样运行测试
- 从soaplib目录中运行soaplib的interop service。
% python tests/interop_service.py - 修改axis_soaplib中的build.xml来映射正确的URL以及axis库的路径
- 跑这个测试
% ant Buildfile: build.xml
wsdl2java:
compile:
[javac] Compiling 9 source files [javac] Note:
Some input files use unchecked or unsafe
operations. [javac] Note: Recompile with -
Xlint:unchecked for details.
test:
[java] Results ——————————————-
[java] Total Tests 16 [java] Failures 0
BUILD SUCCESSFUL Total time: 7 seconds
调整方法参数
在python中,函数的参数包含可选和强制两类。SOAP也允许重复的参数;这是使用SOAP对象的**customize()**方法完成的。
一个SOAP方法的参数具有许多与基数相关的特性。
- min_occurs:定义该参数的最小出现次数;设置为0,将该参数标记为可选类型。
- max_occurs:定义该参数的最大出现次数;如果大于1,该参数将作为列表传递。
- nillable:True或者False,甚至一个’Null’值(一个空的XML元素)可以被该参数传递;该值将转化为Python中的NONE值。
示例:
class MyService(DefinitionBase):
@soap(
String.customize(min_occurs=1, max_occurs=1, nillable=False),
String.customize(min_occurs=0, max_occurs="unbounded", nillable=True),
String.customize(min_occurs=4, max_occurs=10, nillable=False)
)
def my_method(self, mandatory_string, list_of_strings, a_few_strings):
pass
- 第一个参数必须存在,不能为NONE;它还必须是一个空的字符串。
- 第二个参数可以是字符串列表,其中一些值可以是None(例如 :[‘foo’,None,‘bar’,None,None,‘baz’])
- 第三个参数是一个字符串列表,包括4到10个items。不能包含None:[‘foo’,‘bar’,‘baz’,‘foo’,’’,‘foo’]
基元
对于基元类型,不需要调用customize()方法:
# These are equivalent
String(min_occurs=0, max_occurs=1, nillable=False)
String.customize(min_occurs=0, max_occurs=1, nillable=False)
自定义类必须使用customize()(继承于ClassModel)。
报错
在SOAP的WSDL中,errors被视为Faults。这些可以在每个基于soaplib方法中使用Fault类定义。
因为这个类是异常的子类,所以Fault可以像任何标准错误一样被触发。
对于一个特定的方法,它的有效错误被定义于它定义中的**_faults**关键词参数中:
from soaplib.core.model import exception
from soaplib.core import service
class MyFault(exception.Fault):
__namespace__ = 'faults'
class MyService(service.DefinitionBase):
@soap(String, _faults=(MyFault,))
def MyMethod(self, name):
if name != 'foobar':
raise MyFault('Invalid name: %s' % name)