SOAP净化有线协议(一):SOAP基础知识
2024-07-21 02:21:49
供稿:网友
一、soap简介
如前所述,soap用xml作为数据编码格式。用xml作为数据编码格式并非soap的原创,实际上这是一种相当自然的选择。xml-rpc和ebxml也同样使用xml。要了解这方面的更多信息,请参见本文最后的“参考资源”。
请考虑下面的java接口:
listing 1
public interface hello
{
public string sayhelloto(string name);
}
客户程序在调用sayhelloto()方法时提供了一个名字,它希望从服务器接收到一则个性化的“hello”信息。现在,假定rmi、corba和dcom都不存在,开发者必须负责串行化方法调用,并把消息发送给远程机器。几乎所有的人都会说“这该使用xml”,我同意。因此,让我们先从对服务器的请求格式开始。假设要模拟sayhelloto("john")调用,我打算发送的请求是:
listing 2
<?xml version="1.0"?>
<hello>
<sayhelloto>
<name>john</name>
</sayhelloto>
</hello>
在这里,我把接口的名字作为根结点。另外,我还把方法名字和参数名字都当作节点。接下来,我们要把这个请求发送给服务器。我们不创建自己的tcp/ip消息,而是使用http。因此,下面的步骤应该是把请求封装成http post请求格式,然后把它发送给服务器。实际创建该http post请求的详细过程在本文后面介绍,现在,我们先假定它已经创建完毕。服务器接收到了这个请求,解码xml,然后再以xml格式向客户程序发送应答。假设应答内容如下:
listing 3
<?xml version="1.0"?>
<hello>
<sayhellotoresponse>
<message>hello john, how are you?</message>
</sayhellotoresponse>
</hello>
根节点仍然是接口的名字hello。但这一次,原来对应着方法的节点名字不再是sayhelloto,而是方法的名字加上“response”字符串。客户程序知道自己调用了哪一个方法,要找出被调用方法的返回值,它只需查看名字为方法名字加上“response”字符串的元素。
以上就是soap的根本思路。listing 4显示了同一请求用soap xml编码之后的结果:
listing 4
<soap-env:envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/1999/xmlschema-instance" xmlns:xsd="http://www.w3.org/1999/xmlschema">
<soap-env:header>
</soap-env:header>
<soap-env:body>
<ns1:sayhelloto
xmlns:ns1="hello"
soap-env:encodingstyle="
http://schemas.xmlsoap.org/soap/encoding/">
<name xsi:type="xsd:string">john</name>
</ns1:sayhelloto>
</soap-env:body>
</soap-env:envelope>
看起来稍微复杂了一点,对吧?实际上,它和我们前面编写的请求类似,只是略微扩展了一些东西。首先,注意soap文档通过一个envelope(根节点)、一个header区、一个body区,整洁地组织到一起。header区用来封装那些与方法本身无直接关系的数据,提供环境方面的信息,比如事务id和安全信息。body区包含面向方法本身的信息。在listing 2中,我们自己编写的xml只包含一个body区。
第二,注意listing 4大量地应用了xml名称空间。soap-env映射到名称空间http://schemas.xmlsoap.org/soap/envelope/,xsi映射到http://www.w3.org/1999/xmlschema-instance,而xsd映射到http://www.w3.org/1999/xmlschema。这三者是所有soap文档都拥有的标准名称空间。
最后,在listing 4中,接口名称(即hello)不再象在listing 2中那样成为节点的名字;相反,它引用了一个名称空间nsl。另外,参数的类型信息也随同参数的值一起发送给了服务器。注意信封(envelope)encodingstyle属性的值。这个属性值设置成了http://schemas.xmlsoap.org/soap/encoding/。这个值告诉服务器用来编码(即串行化)方法的编码方式;服务器需要这个信息,以便正确地解除方法的串行化。对于服务器来说,soap文档的自我描述能力是相当完善的。
对于上面的soap请求,服务器的应答如下:
listing 5
<soap-env:envelope
xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/1999/xmlschema-nstance"
xmlns:xsd="http://www.w3.org/1999/xmlschema">
<soap-env:body>
<ns1:sayhellotoresponse
xmlns:ns1="hello"
soap-env:encodingstyle="http://schemas.xmlsoap.org/soap/encoding/">
<return xsi:type="xsd:string">hello john, how are you doing?</return>
</ns1:sayhellotoresponse>
</soap-env:body>
</soap-env:envelope>
listing 5与listing 4的请求消息类似。在上面的代码中,返回值(即个性化的“hello”消息)包含在body区。soap消息文档的格式非常灵活。例如,编码方式不固定,而是由客户程序指定。只要是客户程序和服务器都认可的编码方式,可以是任何合法的xml文档。
另外,分离调用环境信息意味着方法本身并不关心这类信息。在当前的市场上,主流应用服务器都遵从这一理念。早先,我曾经指出环境信息可以包含事务和安全方面的信息。事实上,环境可以涵盖几乎所有的东西。下面是一个soap消息头的例子,它带有一些事务方面的信息:
listing 6
<soap-env:header>
<t:transaction xmlns:t="some-uri" soap-env:mustunderstand="1">
5
</t:transaction>
</soap-env:header>
名称空间t映射到了与特定应用有关的uri。这里的5表示的是该方法从属于其中的事务id。注意soap信封mustunderstand属性的应用。这个属性被设置成了1,它表示服务器要么理解并按照要求处理该事务请求,要么表示无法处理该请求;这是soap规范所要求的。
二、错误处理
使用soap并不意味着任何时候所有的请求都会获得成功。许多地方可能会出现差错。例如,服务器可能无法访问某个关键性的资源(比如数据库),因而无法顺利地处理请求。
让我们返回“hello”实例,为它加上一个假想的约束,即“在星期二向别人说hello不合法。”因此,星期二的时候,即使发送给服务器的请求是合法的,服务器也会把一个错误信息返回给客户端。应答内容将如下所示:
listing 7
<soap-env:envelope xmlns:soap-env="
http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:body>
<soap-env:fault>
<faultcode>soap-env:server</faultcode>
<faultstring>server error</faultstring>
<detail>
<e:myfaultdetails xmlns:e="hello">
<message>
sorry, my silly constraint says that i cannot say hello on tuesday.
</message>
<errorcode>
1001
</errorcode>
</e:myfaultdetails>
</detail>
</soap-env:fault>
</soap-env:body>
</soap-env:envelope>
我们来分析一下http://schemas.xmlsoap.org/soap/envelope/名称空间定义的fault元素。fault元素总是body元素的直接子元素,所有的soap服务器必须始终通过该元素报告所有错误情况。fault元素必须包含faultcode和faultstring元素,不能有例外。faultcode是一个能够标识问题的代码;客户程序按照soap规范的要求利用faultcode进行算法处理。soap规范定义了一小组错误代码供用户使用。另一方面,faultstring是供人类阅读的错误信息。
listing 7的代码还包含了一个detail元素。由于错误在处理soap消息的body区时出现,detail元素必须出现。正如你将在本文后面看到的,如果错误在处理header区时出现,detail元素不应出现。在listing 7中,应用利用detail元素提供当前错误更详细、更自然的解释,即星期二不允许说hello。soap还提供另外一个面向具体应用的错误代码,即半可选的faultfactor元素,但上面的错误信息中没有显示这个元素。之所以称这个元素是半可选的,是因为如果错误消息不是由请求最终处理点的服务器发送,即由一个中间服务器发送,则错误消息必须包含该元素。soap对faultcode元素不应出现的情形没有作任何规定。
在listing 7中,错误起源于方法调用本身,处理该方法的应用导致了这个错误。现在,我们来看一下另一种类型的错误,这种错误由于服务器不能处理请求头信息而导致。举例来说,假设所有的hello消息必须在一个事务环境之内生成,则请求类似于:
listing 8
<soap-env:envelope
xmlns:soap-env="
http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="
http://www.w3.org/1999/xmlschema-instance"
xmlns:xsd="http://www.w3.org/1999/xmlschema">
<soap-env:header>
<t:transaction xmlns:t="some-uri"
soap-env:mustunderstand="1">
5
</t:transaction>
</soap-env:header>
<soap-env:body>
<ns1:sayhelloto
xmlns:ns1="hello"
soap-env:encodingstyle="
http://schemas.xmlsoap.org/soap/encoding/">
<name xsi:type="xsd:string">tarak</name>
</ns1:sayhelloto>
</soap-env:body>
</soap-env:envelope>
上面消息的header区包含一个transaction元素,它指定了方法调用必须从属于其中的事务编号。这里我说“必须”是因为transaction元素使用了mustunderstand属性。如前所述,soap服务器要么遵照属性的指示处理请求,要么声明不能处理请求。假定soap服务器不能处理,它必须返回一个错误信息。这时的应答应该类似于:
listing 9
<soap-env:envelope xmlns:soap-env="
http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:body>
<soap-env:fault>
<faultcode>soap-env:mustunderstand</faultcode>
<faultstring>soap must understand
error</faultstring>
</soap-env:fault>
</soap-env:body>
</soap-env:envelope>
上面的代码类似listing 7显示的错误信息。但应该注意的是,detail元素不再出现。正如我在前面指出的:soap规范规定,如果错误在处理header区的时候出现,则错误消息中不应包含detail元素。事实上,我们可以根据detail元素是否出现,迅速判定错误是在处理body区还是在处理header区时出现。
三、soap与http
在第一个例子中,我通过http把定制的xml请求发送给服务器,但没有详细介绍这么做涉及到了哪些操作。现在我们回过头来看那个问题。怎样才能把一个soap请求(而不是定制的xml)通过http发送给服务器?soap很自然地遵循了http的请求/应答消息模型。这个模型在http请求中提供soap请求参数,在http应答中提供soap应答参数。实际上,soap 1.0特别指明http作为它的传输协议。soap 1.1略有放松。虽然soap 1.1仍旧可以使用http,但它也可以使用其他协议,比如smtp。在这个系列的文章中,我只讨论通过http使用soap的情形。
让我们返回hello示例。如果我们通过http把soap请求发送给服务器,则代码应该类似于:
listing 10
post http://www.smarthello.com/helloapplication http/1.0
content-type: text/xml; charset="utf-8"
content-length: 587
soapaction: "http://www.smarthello.com/helloapplication#sayhelloto"
<soap-env:envelope
xmlns:soap-env="
http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="
http://www.w3.org/1999/xmlschema-instance"
xmlns:xsd="http://www.w3.org/1999/xmlschema">
<soap-env:header>
</soap-env:header>
<soap-env:body>
<ns1:sayhelloto
xmlns:ns1="hello"
soap-env:encodingstyle="
http://schemas.xmlsoap.org/soap/encoding/">
<name xsi:type="xsd:string">tarak</name>
</ns1:sayhelloto>
</soap-env:body>
</soap-env:envelope>
listing 10代表的soap请求与listing 4的请求基本相同,但listing 10的开头加入了一些http特有的代码。第一行代码表明这是一个遵循http 1.1规范的post请求,post的目标是http://www.smarthello.com/helloapplication。下一行指示内容的类型,在http消息中包含soap实体时,内容类型必须是text/xml。content-length指明了post请求有效载荷的长度。
第四行是soap特有的,而且它是必不可少的。soapaction http请求头指明了soap http请求的目标,它的值是一个标识目标的uri。soap不对该uri的格式作任何限制,实际上,这个uri甚至不必对应某个实际的位置。
soapaction的一个可能的应用是,防火墙检查该请求头的值,决定是否允许请求通过防火墙。
一旦服务器处理完请求,它将向客户返回一个应答。应答的内容如listing 11所示(假设没有出现错误):
listing 11
http/1.0 200 ok
content-type: text/xml; charset="utf-8"
content-length: 615
<soap-env:envelope
xmlns:soap-env="
http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="
http://www.w3.org/1999/xmlschema-instance"
xmlns:xsd="http://www.w3.org/1999/xmlschema">
<soap-env:body>
<ns1:sayhellotoresponse
xmlns:ns1="hello"
soap-env:encodingstyle="
http://schemas.xmlsoap.org/soap/encoding/">
<return xsi:type="xsd:string">hello john, how are
you doing?</return>
</ns1:sayhellotoresponse>
</soap-env:body>
</soap-env:envelope>
这个soap应答与listing 5所显示的一样,但前面加上了一些http特有的代码。由于没有出现错误,第一行代码显示应答状态是200。在http协议中,200应答状态代码表示“一切正常”。如果在处理soap消息(header区或者body区)的时候出现了任何错误,则返回的状态代码将是500。在http中,500状态代码表示“internal server error”。此时,上述soap应答的第一行代码将是:
http 500 internal server error
四、http扩充框架
许多应用对服务的需求超过了传统http提供的服务。其结果就是,这类应用扩充了传统的http协议。然而,这种扩充是应用本身私有的。http扩充框架试图确立一个通用的http扩充机制,从而解决这个问题。http扩充框架的扩充之一是增加了一个m-post方法,其中的m表示mandatory(必须遵循的,强制的)。如果一个http请求包含至少一个强制的扩充声明,那么这个请求就称为强制的请求。引入强制的扩充声明通过man或者c-man头进行。强制请求的请求方法名字必须带有“m-”前缀,例如,强制的post方法称为m-post。
soap 1.0要求客户程序首先发送一个http post请求,只有当服务器返回错误510时才发送m-post请求。soap 1.1不再对客户作这种限制,也就是说,soap 1.1允许客户从发送任何一种类型的请求开始。下面的请求就是迄今为止我们一直在讨论的那个请求,但它现在是m-post格式:
listing 12
m-post http://www.smarthello.com/helloapplication http/1.1
content-type: text/xml; charset="utf-8"
content-length: 587
man: "http://schemas.xmlsoap.org/soap/envelope/"; ns=01
01-soapaction: "http://www.smarthello.com/helloapplication#sayhelloto"
<soap-env:envelope
xmlns:soap-env="
http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="
http://www.w3.org/1999/xmlschema-instance"
xmlns:xsd="http://www.w3.org/1999/xmlschema">
<soap-env:header>
</soap-env:header>
<soap-env:body>
<ns1:sayhelloto
xmlns:ns1="hello"
soap-env:encodingstyle="
http://schemas.xmlsoap.org/soap/encoding/">
<name xsi:type="xsd:string">tarak</name>
</ns1:sayhelloto>
</soap-env:body>
</soap-env:envelope>
对于实际的soap消息来说,listing 12和listing 10没有什么不同。但请求头中有一些不同的地方,例如,现在我们发出的不是post请求,而是一个m-post请求。正如前面所介绍的,象m-post这样的强制请求至少有一个强制扩充声明。这里我们就有一个:man域描述了一个强制性的端到端扩充声明,把头前缀01映射到了名称空间http://schemas.xmlsoap.org/soap/envelope/。请注意这个前缀关联到soapaction域的方式。
一旦服务器处理完该请求,它将返回一个应答给客户。应答内容类如(假设没有出现错误):
listing 13
http/1.0 200 ok
ext:
content-type: text/xml; charset="utf-8"
content-length: 615
<soap-env:envelope
xmlns:soap-env="
http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="
http://www.w3.org/1999/xmlschema-instance"
xmlns:xsd="http://www.w3.org/1999/xmlschema">
<soap-env:body>
<ns1:sayhellotoresponse
xmlns:ns1="hello"
soap-env:encodingstyle="
http://schemas.xmlsoap.org/soap/encoding/">
<return xsi:type="xsd:string">hello john, how are
you doing?</return>
</ns1:sayhellotoresponse>
</soap-env:body>
</soap-env:envelope>
同样地,listing 13显示的应答类似于对普通post请求的应答(如listing 11所示),两者的不同之处在于ext域。
在通过http使用soap的过程中,我们欣喜地看到,实际的soap消息(soap信封和它里面的所有内容)总是保持不变,就如消息尚未加载http协议时一样。根据这一事实可以推断出,http不是能够与soap协作的唯一协议。例如,soap可以方便地和smtp协议或者其他定制的私有协议一起运行。唯一的要求是两者——客户端和服务器端——都理解该协议。
五、soap的特点:简单
至此为止,我们讨论了soap定义的方方面面,但有许多领域的问题soap没有定义。soap规范的创立者明确地排除了一些关系密切的领域,比如构造对象模型,还有其他许多已经确立的标准。
造成这种现象的原因可以从分析soap的目标理解。soap的目标除了扩展性之外,另一个主要的设计目标是简单。为了保持soap简单,soap规范的创立者决定,只定义那些对于创建一个轻型协议来说绝对必须的东西。例如,soap没有定义/指定任何有关分布式垃圾收集、类型安全或版本控制、双向http通信、消息盒(message-box)运输或管道处理、对象激活等方面的内容。soap的目标就是成为一种简单的协议——一种在任何操作系统上,单个开发者能够用任何语言化数天时间实现的协议。考虑到这一点,soap在许多方面没有作出明确定义实际上是一件好事,因为在构造分布式系统时,所有现有的技术都可以方便地采用soap,即使不同技术之间的差异象corba和dcom的差异那样明显。国内最大的酷站演示中心!