|
技术资料 > .Net专区 > SOAP/UDDI/WSDL : SOAP Version 1.2中文手冊(3) |
SOAP Version 1.2中文手冊(3) March 25,2004 |
2. SOAP消息交换模型
从根本上来看,SOAP消息是从发送方到接受方的一种传输方法,但就象前面例子中阐述的那样,SOAP消息一般会和实现模式结合,例如请求/响应。
SOAP的实现可以为特殊网络系统的特有特征来优化。例如,在section 6中描述的HTTP binding将SOAP响应消息通过HTTP响应来传输,使用与相对应请求的同一HTTP连接。
2.1 SOAP结点
SOAP结点可以是初始SOAP发送者,可以是最终SOAP接收者,也可以是同时作为SOAP发送者和接收者的SOAP中介。SOAP并不提供一个路由机制,曾因此SOAP需要识别SOAP发送者产生的SOAP消息应当通过哪些零个或多个SOAP中介被发送到一个最终SOAP接收者。
接收到SOAP消息的SOAP结点必须能够实施处理、产生必要的SOAP错误和SOAP响应,如果合适的话还应当根据本规范的后续描述生成额外的SOAP消息。
2.2 SOAP角色与SOAP结点
当处理一个SOAP消息的时候,SOAP结点将被告知应当以一个或多个SOAP处理角色来处理,这些SOAP角色由SOAP actor名来标识,SOAP actor的名字是一个URI。每个SOAP结点都必须以一个指定的角色来处理,这个角色使用命名为"http://www.w3.org/2001/06/soap-envelope/actor/next"的SOAP actor来表示,同时可以按照需要应用零个或多个其他SOAP actor表示的额外角色。SOAP结点可以通过以匿名SOAP actor的角色来实施处理以使得自己成为最终SOAP接收者。当SOAP结点在处理一个SOAP消息的时候,其表现出的SOAP角色在整个处理过程中不得更改。这是因为本规范只涉及如何处理单个SOAP消息而无需考虑状态,因此是否允许在处理单个SOAP消息的时候转换角色是没有意义的。
SOAP actor名字是用来识别SOAP结点的,并没有与路由或者消息交换的语义相联系。举例来说,一个SOAP actor可以被命名为一个用来发送SOAP消息给适当SOAP结点的URI。相反,也有这样一些SOAP处理角色的名字,这些名字或者直接和消息路由相联系(例如,http://example.org/banking/anyAccountMgr),或者和路由没有联系(例如,当一个消息头被用来携带一个指示,该指示用来告知任何有关的软件该SOAP消息是长期不变的,是能够被安全的缓存和重用的,在这种SOAP消息头中,一个URI可以用于标识“所有缓存管理软件”),通过名字使用这些SOAP处理角色也是合适的。
2.3 定位SOAP Header条目
SOAP header条目包含可选的env:actor属性(参阅section 4.2.2)用来把他们定位到合适的SOAP结点。没有该属性的SOAP Header隐含地定位到一个匿名的SOAP actor,这意味着他们被处理为最终的SOAP接收者。我们把SOAP actor属性的值(隐含的或者直接指明的)作为相应SOAP条目(SOAP Header条目或者SOAP Body条目)的SOAP actor。
如果SOAP条目中SOAP actor(如果出现的话)匹配了一个SOAP结点的角色,或者是SOAP条目没有actor属性(包括SOAP Body条目)而该SOAP结点已经假设为匿名SOAP处理角色,这时我们就说SOAP条目被指向一个SOAP结点。
2.4 理解SOAP Header
我们认为随着时间的过去,会有大量的SOAP Header函数规范出现,而且每个SOAP结点都可以包含一个或多个处理这些扩展所必须的软件。如果SOAP结点的软件是完全兼容的而且实现了那些由条目中完整修饰的最外层元素名所传递的语义,我们说这个SOAP Header被一个SOAP结点理解。
当定位到一个SOAP结点的SOAP Header块的mustUnderstand属性为“1”,被指向的SOAP结点必须:或者依照由条目中完整修饰的最外层元素名传递的语义来处理SOAP块,或者更本不处理SOAP消息而失败(参见section 4.4).。
2.5 处理消息
本节陈述了SOAP消息处理规则。除非另外规定,处理必须在语义上等同于分别执行下述的步骤,同时还必须按照给定的顺序。注意到,无论如何在这个规范中都没有阻止使用如并行、回滚或者其他可以在处理中提高灵活性的技术,只要所有的SOAP消息、SOAP fault和应用程序级的结果和那些直接执行下列规则得到的结果是相同的。
如果一个或多个定位到SOAP结点的SOAP条目有env:mustUnderstand="1"并且没有被结点理解,则产生一个SOAP mustUnderstand错误。如果这样一个错误产生了,那么必须停止进一步的处理。
处理定位到SOAP结点的SOAP条目,如果需要,产生SOAP错误。当定义env:mustUnderstand="1"时,一个SOAP结点必须处理SOAP块。如果没有定义,那么SOAP结点可以处理或忽略该SOAP条目。如果一个SOAP条目被处理,无论如何,这个SOAP结点必须理解该SOAP条目而且必须以和那个SOAP条目说明完全一致的样式进行处理。而对于错误,无论是那种,也必须和SOAP条目的说明一致。有可能处理特殊SOAP条目会控制或者决定其他SOAP条目的处理顺序。例如,一个SOAP条目可能建立一个SOAP Header条目用来强制按词汇的顺序执行其他的SOAP Header条目。如果没有这样一个SOAP条目,处理的顺序是由SOAP结点来判断的。当处理一个SOAP条目的时候,SOAP结点可以引用SOAP envelope中的任何信息。例如如果需要,一个缓存函数可以缓存整个SOAP消息。
如果SOAP结点是一个SOAP中介,SOAP消息的式样和处理的结果(如果没有产生错误)可以要求进一步沿着SOAP消息路径送递SOAP消息。这种接力转递必须以同样顺序包括从SOAP消息源来的所有的SOAP Header条目和SOAP Body条目,除了那些指向SOAP中介的SOAP Header条目,这些条目必须被移去(无论他们是否被处理,这些SOAP条目都将被移去)。附加的SOAP Header条目可以被插入在SOAP消息的任何一点,这样被插入的SOAP Herder条目可能没法和刚刚被移走的一个或多个条目区分开来(实际上是会将他们保留,但强调需要沿着SOAP消息路径重新解释每个SOAP结点)
3. 与XML的关系
所有的SOAP消息都是使用XML格式来编码的(可参阅[7]以获得更多的XML的信息)。
SOAP应用程序在生成由SOAP定义的所有元素和属性的时候,应该包含恰当的SOAP命名空间。SOAP应用程序必须能处理其收到的消息中的SOAP命名空间。它必须丢弃那些包含不正确命名空间(参阅 section 4.4)的消息,并且可以处理那些不包含SOAP命名空间的SOAP消息,就好象他们包含了正确的命名空间一样。
SOAP定义了以下的命名空间 (可参阅[8]以获得更多的XML命名空间的信息):
SOAP信封的命名空间标识为 "http://www.w3.org/2001/06/soap-envelope"
SOAP遍序的命名空间标识为 "http://www.w3.org/2001/06/soap-encoding"
SOAP mustUnderstand fault的命名空间标识为 "http://www.w3.org/2001/06/soap-faults"
SOAP upgrade的命名空间标识为 "http://www.w3.org/2001/06/soap-upgrade"
而这些命名空间的模式文档可以通过解析这些命名空间标识符来获得。
SOAP消息必须不包含DTD,同时SOAP消息也必须不包含PI(Processing Instructions)。 [7]
SOAP使用局部的非限制的ID类型的id属性来指定编码元素(encoded element)的唯一标识,使用局部的非限制的uri-reference类型的href属性来指定编码元素的值的应用,以获得与XML规范[7]、XML Schema规范[11]和XML Linking Language规范[9]的一致。
除SOAP mustUnderstand属性(参阅 section 4.2.3)和SOAP actor属性(参阅 section 4.2.2)外,一般允许属性及属性值自由地选择是在XML实例中描述还是在XML Schema中描述,当然前提是他们具有相同的效果。也就是说,在DTD或模式(schema)中使用默认值或固定值定义在语义上等价于在实例中的定义。
4. SOAP信封
SOAP消息是由一个强制的SOAP Envelope、一个可选的SOAP Header和一个强制的SOAP Body组成的XML文档。作为SOAP消息的该XML文档将在本规范的其余部分被引用。而本节的元素和属性的命名空间标识是"http://www.w3.org/2001/06/soap-envelope"。SOAP消息应当包含如下部分:
一个SOAP envelope。Envelope是表示该消息的XML文档的顶级元素。
一个SOAP Header。Header则是为了支持在松散环境下在通讯方(可能是SOAP发送者、SOAP接受者或者是一个或多个SOAP的传输中介)之间尚未预先达成一致的情况下为SOAP消息增加特性的通用机制。SOAP定义了很少的一些属性来用于指明谁可以处理该特性以及它是可选的还是强制的。(参阅 section 4.2)
一个SOAP Body。Body为该消息的最终接收者所想要得到的那些强制信息提供了一个容器(参阅 section 4.3)。此外,SOAP定义了Body的一个子元素Fault用于报告错误。
语法规则如下:
SOAP Envelope
元素名为"Envelope"。
该元素必须在SOAP消息中出现。
该元素可以包含命名空间申明和额外的属性。如果出现额外属性,则必须有命名空间修饰。类似的,该元素可以包含额外的子元素,这些子元素如果出现,必须有命名空间修饰并且必须跟在SOAP Body元素之后。
SOAP Header (参阅 section 4.2)
元素名为"Header"。
该元素可以在SOAP消息中出现。如果出现,该元素必须是SOAP Envelope元素的第一个直接子元素。
该元素可以包含一系列的Header条目,这些条目都应当是Header元素的直接子元素。Header的所有直接子元素必须有命名空间修饰。
SOAP Body (参阅 section 4.3)
元素名为"Body"。
该元素必须在SOAP消息中出现,同时必须是SOAP Envelope元素的一个直接子元素。若该消息中包含Header元素,则Body元素必须直接跟随Header,为Header元素的相邻兄弟元素。若Header不出现,则其必须是Envelope的第一个直接子元素。
该元素可以包含一系列的Body条目,这些条目都应当是Body元素的直接子元素。Body的所有直接子元素必须有命名空间修饰。SOAP定义了SOAP Fault元素,它用来指示错误消息。(参阅 section 4.4).
4.1.1 SOAP encodingStyle属性
SOAP的全局encodingStyle属性被用于指明在SOAP消息中使用哪种编序规则。该属性可以在任意元素中出现,并且其作用范围包括该元素的内容和所有其子元素中未使用该属性的所有子元素,这就象XML命名空间定义的作用范围。对于一个SOAP消息来说,没有默认的编码定义。
该属性的值是一个或多个用于标识编序规则和用于标识解序SOAP消息的规则的有序列表,其排序是按照详尽程度从大到小排列。Example 3展示了encodingStyle属性的三个例子:
Example 3
encodingStyle="http://www.w3.org/2001/06/soap-encoding"
encodingStyle="http://example.org/encoding/restricted http://example.org/encoding/"
encodingStyle=""
Example values for the encodingStyle attribute
在section 5中定义的编序规则的标识为"http://www.w3.org/2001/06/soap-encoding"。消息若要使用特别的编序应该使用SOAP encodingStyle属性来指明。另外,所有在句法上由"http://www.w3.org/2001/06/soap-encoding"开始的URI序列表明这其中包含的所有URI都与section 5中定义的SOAP编码规则相一致。(虽然可能会添加潜在的更为严格的规则)
一个空值的URI(“”)明确地指明并未为其所包含的元素声明任何编码风格。这可以为包含的元素关闭任何声明。
4.1.2 Envelope版本模型
SOAP并未定义一个传统的基于主辅版本号的版本模型。SOAP消息必须包含一个与命名空间"http://www.w3.org/2001/06/soap-envelope"相关联的Envelope元素。如果SOAP应用程序收到一个SOAP消息,这个消息中的Envelope元素与一个与"http://www.w3.org/2001/06/soap-envelope"不同的命名空间相关联,则该应用程序必须视其为一个版本错误并生成一个VersionMismatch SOAP错误。SOAP VersionMismatch错误消息必须使用SOAP 1.1的信封命名空间"http://schemas.xmlsoap.org/soap/envelope/"(参阅 Appendix C)修饰。
4.2 SOAP Header
SOAP提供了一个可伸缩的机制用于在分散的模块化的环境下扩展SOAP消息,而通讯双方并不需要有预先的约定知识。典型的扩展例子可以是实现一些诸如认证、事务管理以及支付的Header条目。
SOAP Header元素应当被编码为SOAP Envelope XML文档的第一直接子元素。Header的所有直接子元素都被称为Header条目。
Header条目的编码规则包括:
一个SOAP Header条目由一个完整修饰的元素名来标识,所谓完整修饰的元素名是由一个命名空间URI和局部名来组成。SOAP Header元素的所有直接子元素都必须是完整修饰的。
SOAP encodingStyle属性可以用于指明Header条目的编码风格(参阅 section 4.1.1)。
SOAP actor属性(参阅 section 4.2.2)和SOAP mustUnderstand属性(参阅 section 4.2.3)可以用于指明由哪个SOAP结点来处理条目以及如何处理条目。
4.2.1 使用Header属性
本节中定义的SOAP Header属性决定了SOAP消息的接收者应该如何处理消息(参阅 section 2)。一个生成SOAP消息的SOAP应用程序应该仅使用SOAP Header元素的直接子元素的SOAP Header属性。而对于那些并非作为SOAP Header元素的直接子元素出现的SOAP Header属性,SOAP消息的接受者必须忽略。
以下是一个SOAP Header的例子(Example 4),其中包含了一个元素标识Transaction和一个mustUnderstand属性及其值1,以及Transactin的值5:
Example 4
<env:Header xmlns:env="http://www.w3.org/2001/06/soap-envelope" >
<t:Transaction xmlns:t="http://example.org/2001/06/tx" env:mustUnderstand="1" >
5
</t:Transaction>
</env:Header>
Example header with a single header block
4.2.2 SOAP actor属性
EdNote: This section partially overlaps with section 2. We expect this to be reconciled in a future revision of the specification.
SOAP消息从生成者到达最终接受者,将潜在地沿着消息路径(message path)经过一系列的SOAP中间介。SOAP中间介是一个能够接受和转发SOAP消息的应用程序。所有的中间介都如同最终接受者一样由一个URI来标识。
并非一个SOAP消息的所有部分都是最终接收者想要的,其中部分是路径中的一个或多个中间介所需要的。Header元素中接收者角色类似和约的接受者,他并不能将其交给其它方。也就是说,一个接收者接到其想要的一个Header元素必须不转发该Header给SOAP消息路径中的下一个应用程序,因为合约关系是存在于前二者之间的。该接收者可以插入一个类似的Header元素,但在这个情况下,和约关系存在于该应用程序及下一个Header元素的接收者之间了。
SOAP actor全局属性可以被用于指明Header元素的接收者。而SOAP actor属性的值是一个URI。这个特别的URI"http://www.w3.org/2001/06/soap-envelope/actor/next"指明该Header元素是直接的下一个进行消息处理的SOAP应用程序想要的。这与HTTP的连接头字段的hop-by-hop scope model的表示。
若省略SOAP actor属性,则表明接收者是SOAP消息的最终接收者。
这个属性必须出现在SOAP消息的实例中,而不能定义在相关的XML Schema中以期获得同样效果(参阅 section 3 和section 4.2.1)。
4.2.3 SOAP mustUnderstand属性
EdNote: This section partially overlaps with section 2. We expect this to be reconciled in a future revision of the specification.
SOAP mustUnderstand全局属性用于指明一个Header条目是强制的还是可选的要求接收者处理。Header条目的接收结点由SOAP actor属性来定义(参阅 section 4.2.2)。mustUnderstand属性的值可为“0”或“1”。若没有使用SOAP mustUnderstand属性,则在语义上等价于mustUderstand属性出现同时取值为“0”,也就是说这个条目是可选的。
若Header条目带有值为“1”的SOAP mustUnderstand属性,则该Header条目的接收结点要么必须遵循语义(由具备完整修饰的元素名来传达)并正确地处理这些语义,要么必须宣称处理消息失败(参阅 section 4.4)。
SOAP mustUnderstand属性是为了考虑健壮地升级而设置的。所有用值为“1”的SOAP mustUnderstand属性来标记的元素必须被认为是可以影响该元素的上级元素或同级元素的语义。而这种风格标记的元素应保证对语义的修改并不能被那些不能完全理解该修改后的语义的那些元素静默地或假设地、不正确地忽略。
该属性若要生效必须在实例中出现,而不能定义在相关的XML Schema中以期获得同样效果(参阅 section 3 和section 4.2.1)。
4.3 SOAP Body
SOAP Body元素提供一个简单的用于与消息的最终接收者交换强制信息的机制。而Body元素的典型应用包含序列的RPC调用和错误报告。
Body元素在编码上应当作为SOAP Envelope元素的一个直接子元素。如果包含Header元素,则Body元素必须直接跟随Header元素,为Header元素的直接下一个兄弟元素,否则Body元素必须是Envelope元素的第一直接子元素。
所有Body元素的直接子元素被成为SOAP Body条目,同时每一个Body条目都应当编码为SOAP Body元素里的一个独立元素。
Body条目的编码规则包括:
一个Body条目由一个完整修饰的元素名来标识,所谓完整修饰的元素名是由一个命名空间URI和局部名来组成。SOAP Body元素的直接子元素可以是命名空间修饰的。
SOAP encodingStyle属性可以被用来表明Body条目中使用的编码规则(参阅 section 4.1.1)。
SOAP定义了一个Body条目,用于报告错误的Fault条目(参阅 section 4.4)。
4.3.1 SOAP Header和Body的关系
Header和Body在定义上是独立的,但在事实上是相联系的。一个Body条目和一个Header条目的关系是:一个Body条目在语义上与这样一个Header条目等价,该Header条目将由默认参与者(最终接受者)解释同时由值为“1”的SOAP mustUnderstand属性标记。默认参与者可以使用actor属性缺失的方式来指明(参阅 section 4.2.2)。
4.4 SOAP错误
SOAP Fault元素是用于在SOAP消息中传输错误或状态信息。如果SOAP消息需要包含SOAP Fault元素的话,它必须作为一个Body条目出现,同时在Body元素内它必须不出现多于一次(至多出现一次)。
SOAP Fault元素定义了如下子元素:
faultcode
faultcode元素是应那些要提供一个算法上的机制来标识错误的软件的需要。faultcode必须在SOAP Fault元素中出现,同时faultcode的值必须是如[8]中的第3节中定义的一个修饰(限制)名。SOAP定义了一个很小的SOAP错误代码的集合用于覆盖基本的SOAP错误(参阅 section 4.4.1)。
faultstring
faultstring元素是为那些错误代码提供一个人可以读懂的错误解释,它不是为程序处理而设。Faultstring元素有点类似于HTTP中定义的’Reason-Phrase’(参阅 [5], section 6.1)。faultstring必须在SOAP Fault元素中出现,同时它至少应该提供一些解释该错误种类的信息。
faultactor
faultactor元素是为在SOAP消息路径(参阅 section 2)中是谁引起了该错误的发生这一情况描述信息。它类似于SOAP actor属性(参阅 section 4.2.2),不过它不是用于指示Header条目的接收者,而是用于指示错误源。faultactor属性的值是一个标识该源的一个URI。所谓并非作为SOAP消息最终接收者的应用程序必须在SOAP Fault元素中包含faultactor元素。而消息的最终接收者可以使用faultactor元素来明确地指明是它生成了该错误(参阅下面的detail元素]。
detail
detail元素是用于传输与SOAP Body元素相关的应用程序特别的错误信息。如果Body元素中的内容不能被成功地处理的时候,它必须出现。它必须不能被用于传输属于Header条目的错误信息。详细的属于Header条目的错误信息必须在Header条目中表示传输,如果需要例子的话,请参阅section 4.4.2。
若SOAP Fault元素中不出现detail元素则表明其中的错误与Body元素的处理无关。这可以用于区分在错误情况下Body元素是否被SOAP的最终接受者处理过。
detail元素的所有直接子元素都被称为detail条目,同时每个detail条目都作为detail元素中的一个独立的元素进行编码。
Detail条目的编码规则如下(也可以参阅 example 10):
一个detail条目由一个完整修饰的元素名来标识,所谓完整修饰的元素名是由一个命名空间URI和局部名来组成。Detail元素的直接子元素可以是命名空间修饰的。
SOAP encodingStyle属性可以被用来表明detail条目中使用的编码规则(参阅 section 4.1.1)。
4.4.1 SOAP错误代码
当描述由本规范定义的错误的时候,faultcode元素必须使用在本节中定义的faultode的值。这些faultcode值的命名空间标识为"http://www.w3.org/2001/06/soap-envelope"。在现有规范之外定义的方法的规范推荐使用该命名空间(但不是必须的)。
默认的SOAP faultcode值是按照一种可扩展的风格来定义的,它允许在维持已有的faultcode值的向后兼容的基础定义新的SOAP faultcode值。这一机制在使用上非常类似与HTTP中基本状态类的定义1xx, 2xx, 3xx等(参阅[5]中的section 10)。不过,他们是用XML修饰名来定义(参阅[8]中的section 3),而不是用整数。“.”符号是faultcode值的分隔符,用于指明“.”左边的是一个比右边更泛化的错误代码。Example 5显示了这一特性:
Example 5
Client.Authentication
Example of an authentication fault code
在SOAP中定义的faultcode值集合被罗列在下表。
Name
Meaning
VersionMismatch
处理程序发现在SOAP Envelope元素中有一个非法的命名空间。(参阅 section 4.1.2)
MustUnderstand
SOAP Header元素的一个直接子元素无法被理解或者他并不遵守由处理对象要求的SOAP mustUnderstand属性必须取值为“1”的要求。(参阅 section 4.2.3)
Client
Client错误类用于指示以下错误:消息的格式有误或消息中缺乏能成功处理所必须的一些适当信息。例如,消息中可能缺乏适当的认证和支付信息。一般情况下应指明消息不应该在没有修改过的情况下重发。可参阅section 4.4来参阅Fault detail子元素的描述。
Server
Server错误类用于表明消息无法被处理的原因,但那些属于内容上的错误并不属于该范畴,它主要被用于指示那些属于处理上的错误。例如,处理操作需要包含与一个上游处理程序进行通讯,但该程序没有响应。但该消息可能在下一个时间点上被成功处理。可参阅section 4.4来参阅Fault detail子元素的描述。
4.4.2 MustUnderstand错误
当SOAP结点产生一个MustUnderstand错误时,它应该在产生出的出错消息中按照下面描述的的方式提供相应的Header条目。在产生的错误消息中,它应该提供Header条目来描述不能被理解的带修辞的名的细节(Qnames,由XML Schema数据类型说明)。
每个这样的Header条目有个本地的名字为Misunderstood以及一个叫"http://www.w3.org/2001/06/soap-faults"的命名空间。每个块都有一个名为qname的不带修饰的属性,它的值为出错结点所不能理解的Header条目的Qname。
举例来说,如果最初消息的容器不能理解Example 6中的两个Header元素abc:Extension1和def:Extension2,则会产生出错消息,该消息显示在Example 7中。
Example 6
<env:Envelope xmlns:env='http://www.w3.org/2001/06/soap-envelope'>
<env:Header>
<abc:Extension1 xmlns:abc='http://example.org/2001/06/ext'
env:mustUnderstand='1' />
<def:Extension2 xmlns:def='http://example.com/stuff'
env:mustUnderstand='1' />
</env:Header>
<env:Body>
. . .
</env:Body>
</env:Envelope>
SOAP envelope that will cause a SOAP MustUnderstand fault if Extension1 or Extension2 are not understood
Example 7
<env:Envelope xmlns:env='http://www.w3.org/2001/06/soap-envelope'
xmlns:f='http://www.w3.org/2001/06/soap-faults' >
<env:Header>
<f:Misunderstood qname='abc:Extension1'
xmlns:abc='http://example.org/2001/06/ext' />
<f:Misunderstood qname='def:Extension2'
xmlns:def='http://example.com/stuff' />
</env:Header>
<env:Body>
<env:Fault>
<faultcode>MustUnderstand</faultcode>
<faultstring>One or more mandatory headers not understood</faultstring>
</env:Fault>
</env:Body>
</env:Envelope>
SOAP fault generated as a result of not understanding Extension1 and Extension2 in Example 6
注意到这里不需要命名空间前缀返回与源Header元素命名空间相匹配的qname,倘若前缀映射到一个相同的命名空间名,则错误结点可使用任意前缀。
还注意到这里没有保证每个Mustunderstand错误都包含所有的Misunderstood Header 的Qname,SOAP结点可以在第一个Header块产生一个只包含单个Header块错误细节信息之后再产生一个错误。SOAP结点也可以产生一个一次包含所有MustUnderstand问题细节的混合错误。
|
|
Copyright © 2001-2008 Shenzhen Hiblue Software Team All rights reserved