【发布时间】:2012-02-22 02:04:03
【问题描述】:
[根据两天前第一篇文章以来的经验进行重大编辑。]
我正在使用 Suds 构建 Python SOAP/XML 脚本,但我正在努力获取生成服务器可接受的 SOAP/XML 的代码。我曾认为问题在于 Suds 没有为内部元素生成前缀,但后来发现缺少前缀(参见 Sh-Data 和内部元素)不是问题,因为 Sh-Data 和 MetaSwitchData元素声明适当的命名空间(见下文)。
<SOAP-ENV:Envelope xmlns:ns3="http://www.metaswitch.com/ems/soap/sh" xmlns:ns0="http://www.metaswitch.com/ems/soap/sh/userdata" xmlns:ns1="http://www.metaswitch.com/ems/soap/sh/servicedata" xmlns:ns2="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Header/>
<ns2:Body>
<ns3:ShUpdate>
<ns3:UserIdentity>Meribel/TD Test Sub Gateway 3</ns3:UserIdentity>
<ns3:DataReference>0</ns3:DataReference>
<ns3:UserData>
<Sh-Data xmlns="http://www.metaswitch.com/ems/soap/sh/userdata">
<RepositoryData>
<ServiceIndication>Meta_SubG_BaseInformation</ServiceIndication>
<SequenceNumber>0</SequenceNumber>
<ServiceData>
<MetaSwitchData xmlns="http://www.metaswitch.com/ems/soap/sh/servicedata" IgnoreSequenceNumber="False" MetaSwitchVersion="?">
<Meta_SubG_BaseInformation Action="apply">
<NetworkElementName>Meribel</NetworkElementName>
<Description>TD Test Sub Gateway 3</Description>
<DomainName>test.datcon.co.uk</DomainName>
<MediaGatewayModel>Cisco ATA</MediaGatewayModel>
<CallFeatureServerControlStatus/>
<CallAgentControlStatus/>
<UseStaticNATMapping/>
<AuthenticationRequired/>
<ProviderStatus/>
<DeactivationMode/>
</Meta_SubG_BaseInformation>
</MetaSwitchData>
</ServiceData>
</RepositoryData>
</Sh-Data>
</ns3:UserData>
<ns3:OriginHost>user@domain.com?clientVersion=7.3</ns3:OriginHost>
</ns3:ShUpdate>
</ns2:Body>
</SOAP-ENV:Envelope>
但这仍然失败。问题是 Suds 为可选元素生成空元素(在 WSDL 中标记为 Mandatory = No)。但是服务器要求可选元素要么存在且具有合理的值,要么不存在,并且我收到以下错误(因为<CallFeatureServerControlStatus/> 元素不是允许值之一。
提供的用户数据未针对用户数据的 MetaSwitch XML 架构进行验证。
详细信息:cvc-enumeration-valid:值''对于枚举'[控制,放弃,谨慎控制]'不是方面有效的。它必须是来自枚举的值。
如果我将生成的 SOAP/XML 放入 SOAPUI 并删除空元素,请求就可以正常工作。
有没有办法让 Suds 不为可选字段生成空元素,或者让我之后在代码中删除它们?
重大更新
我已经解决了这个问题(我在其他地方看到过),但是以一种非常不雅的方式。因此,我发布我当前的解决方案是希望 a) 它可以帮助他人和/或 b) 有人可以提出更好的解决方法。
事实证明,问题不在于 Suds 为可选元素生成空元素(在 WSDL 中标记为 Mandatory = No)。而是 Suds 为可选的 complex 元素生成空元素。例如,以下 Meta_SubG_BaseInformation 元素是简单元素,Suds 不会在 SOAP/XML 中为它们生成任何内容。
<xs:element name="CMTS" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>
<d:DisplayName firstVersion="5.0" lastVersion="7.4">CMTS</d:DisplayName>
<d:ValidFrom>5.0</d:ValidFrom>
<d:ValidTo>7.4</d:ValidTo>
<d:Type firstVersion="5.0" lastVersion="7.4">String</d:Type>
<d:BaseAccess firstVersion="5.0" lastVersion="7.4">RWRWRW</d:BaseAccess>
<d:Mandatory firstVersion="5.0" lastVersion="7.4">No</d:Mandatory>
<d:MaxLength firstVersion="5.0" lastVersion="7.4">1024</d:MaxLength>
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="TAGLocation" type="xs:string" minOccurs="0">
<xs:annotation>
<xs:documentation>
<d:DisplayName>Preferred location of Trunk Gateway</d:DisplayName>
<d:Type>String</d:Type>
<d:BaseAccess>RWRWRW</d:BaseAccess>
<d:Mandatory>No</d:Mandatory>
<d:DefaultValue>None</d:DefaultValue>
<d:MaxLength>1024</d:MaxLength>
</xs:documentation>
</xs:annotation>
</xs:element>
相比之下,下面的 Meta_SubG_BaseInformation 元素是一个复杂的元素,即使它是可选的并且我的代码没有为其赋值,它最终还是在生成的 SOAP/XML 中。
<xs:element name="ProviderStatus" type="tMeta_SubG_BaseInformation_ProviderStatus" minOccurs="0">
<xs:annotation>
<xs:documentation>
<d:DisplayName>Provider status</d:DisplayName>
<d:Type>Choice of values</d:Type>
<d:BaseAccess>R-R-R-</d:BaseAccess>
<d:Mandatory>No</d:Mandatory>
<d:Values>
<d:Value>Unavailable</d:Value>
<d:Value>Available</d:Value>
<d:Value>Inactive</d:Value>
<d:Value>Active</d:Value>
<d:Value>Out of service</d:Value>
<d:Value>Quiescing</d:Value>
<d:Value>Unconfigured</d:Value>
<d:Value>Pending available</d:Value>
</d:Values>
</xs:documentation>
</xs:annotation>
</xs:element>
Suds 为 ProviderStatus 生成以下内容,这(如上所述)扰乱了我的服务器。
<ProviderStatus/>
解决方法是在创建父元素之后、赋值之前将所有Meta_SubG_BaseInformation 元素设置为None,如下所示。这对于简单元素来说是多余的,但确实可以确保未分配的复杂元素不会导致生成 SOAP/XML。
subGatewayBaseInformation = client.factory.create('ns1:Meta_SubG_BaseInformation')
for (el) in subGatewayBaseInformation:
subGatewayBaseInformation.__setitem__(el[0], None)
subGatewayBaseInformation._Action = 'apply'
subGatewayBaseInformation.NetworkElementName = 'Meribel'
etc...
这会导致 Suds 生成没有空元素的 SOAP/XML,这对我的服务器来说是可以接受的。
但是有没有人知道一种更清洁的方法来达到同样的效果?
以下解决方案基于以下 dusan 和 Roland Smith 的答案/cmets。
此解决方案使用 Suds MessagePlugin 在 Suds 将请求发送到网络之前修剪 <SubscriberType/> 形式的“空”XML。我们只需要修剪 ShUpdates(我们正在更新服务器上的数据),并且逻辑(尤其是向下索引到子节点以获取服务指示元素列表)非常特定于 WSDL。它不适用于不同的 WSDL。
class MyPlugin(MessagePlugin):
def marshalled(self, context):
pruned = []
req = context.envelope.children[1].children[0]
if (req.name == 'ShUpdate'):
si = req.children[2].children[0].children[0].children[2].children[0].children[0]
for el in si.children:
if re.match('<[a-zA-Z0-9]*/>', Element.plain(el)):
pruned.append(el)
for p in pruned:
si.children.remove(p)
然后我们只需要在创建客户端时引用该插件即可。
client = Client(url, plugins=[MyPlugin()])
【问题讨论】:
-
我看到了。它们与添加标题有关(除非我误解/误读了下摆),而不是让 Suds 包含它确实知道的命名空间的前缀)。
-
希望有人可以添加更清晰的答案!