Suds write request missing datatype?

霸气de小男生 提交于 2019-12-23 05:09:24

问题


I am trying to communicate to a webservice using Suds, reading from the service works fine, however writing throws an error.

suds.WebFault: Server raised fault: 'The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:tagValues. The InnerException message was 'Element Value from namespace http://schemas.datacontract.org/2004/07/NOV.Api.Messages cannot have child contents to be deserialized as an object. Please use XmlNode[] to deserialize this pattern of XML.'. Please see InnerException for more details.'

The XML produces does not seem to add the neccessary xsi:type="xsd:int"

Produced:

<ns1:TagValue>
   <ns1:Quality>
      <ns1:Id>1</ns1:Id>
      <ns1:QualityData>Quality</ns1:QualityData>
   </ns1:Quality>
   <ns1:TagID>
      <ns1:Id>0</ns1:Id>
      <ns1:TagID>BitDepth</ns1:TagID>
   </ns1:TagID>
   <ns1:Value>23</ns1:Value>
</ns1:TagValue>

Expected:

<ns1:TagValue>
   <ns1:Quality>
      <ns1:Id>1</ns1:Id>
      <ns1:QualityData>Quality</ns1:QualityData>
   </ns1:Quality>
   <ns1:TagID>
      <ns1:Id>0</ns1:Id>
      <ns1:TagID>BitDepth</ns1:TagID>
   </ns1:TagID>
   <ns1:Value xsi:type="xsd:int">23</ns1:Value>
</ns1:TagValue>

After searching around i figured to try the ImportDoctor to see if i could get in the xsi:type

I added

schema_url = 'http://schemas.xmlsoap.org/soap/encoding/'
schema_import = Import(schema_url)
schema_doctor = ImportDoctor(schema_import)

and doctor=schema_doctor in the Client ctor

This now gave me an additional prefix and a much extended list of Types

Prefixes (4)
   ns0 = "http://schemas.datacontract.org/2004/07/NOV.Api.Messages"
   ns1 = "http://schemas.microsoft.com/2003/10/Serialization/"
   ns2 = "http://schemas.xmlsoap.org/soap/encoding/"
   ns3 = "http://tempuri.org/"

I now have a ns2:int

I used the factory to create an object of type ns2:int, setting its value to 23

When sending this, i get the following XML:

<ns1:TagValue>
   <ns1:Quality>
      <ns1:Id>1</ns1:Id>
      <ns1:QualityData>Quality</ns1:QualityData>
   </ns1:Quality>
   <ns1:TagID>
      <ns1:Id>0</ns1:Id>
      <ns1:TagID>BitDepth</ns1:TagID>
   </ns1:TagID>
   <ns1:Value xsi:type="ns2:int">23</ns1:Value>
</ns1:TagValue>

I now get the following exception when trying to send it:

suds.WebFault: Server raised fault: 'The formatter threw an exception while trying to deserialize the message: There was an error while trying to deserialize parameter http://tempuri.org/:tagValues. The InnerException message was 'Error in line 1 position 651. Element 'http://schemas.datacontract.org/2004/07/NOV.Api.Messages:Value' contains data from a type that maps to the name 'http://schemas.xm lsoap.org/soap/encoding/:int'. The deserializer has no knowledge of any type that maps to this name. Consider using a DataContractResolver or add the type corresponding to 'int' to the list of known types - for example, by using the KnownTypeAttribute attribute or by adding it to the list of known types passed to DataContractSerializer.'. Please see InnerException for more details.'

Seems slightly closer, but seems like there is some mess with namespaces?

Full XML produced:

<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:ns3="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns0="http://tempuri.org/" xmlns:ns1="http://schemas.datacontract.org/2004/07/NOV.Api.Messages" xmlns:ns2="http://schemas.xmlsoap.org/soap/encoding/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
   <SOAP-ENV:Header/>
   <ns3:Body>
      <ns0:WriteRealtimeValues>
         <ns0:tagValues>
            <ns1:TagValue>
               <ns1:Quality>
                  <ns1:Id>1</ns1:Id>
                  <ns1:QualityData>Quality</ns1:QualityData>
               </ns1:Quality>
               <ns1:TagID>
                  <ns1:Id>0</ns1:Id>
                  <ns1:TagID>BitDepth</ns1:TagID>
               </ns1:TagID>
               <ns1:Value xsi:type="ns2:int">23</ns1:Value>
            </ns1:TagValue>
         </ns0:tagValues>
      </ns0:WriteRealtimeValues>
   </ns3:Body>
</SOAP-ENV:Envelope>

As reference, I create the client using the following code

credentials = dict(username='%s' % (username), password='%s' % password)
url=   "http://%s:%s/TagValueWriteService?wsdl" % (ip,port)
self.transport  = HttpAuthenticated(**credentials)
suds.client.Client.__init__(self,url, transport=self.transport, cache=None,doctor=schema_doctor)

There seem to be several similar issues here on stackoverflow, most of them mentioning the ImportDoctor in a similar manner as i tried. I am lacking some of the fundamental understanding of SOAP i suspect...


回答1:


I managed to solve it, using the answer from Adding xsi:type and envelope namespace when using SUDS ( https://stackoverflow.com/a/10977734/696768 )

I am not sure this is the only possible solution, and to me it seems more of a hack than anything else, however it will work fine for my current scenario.

The solution i used, is making a plugin for the client, looking for the particular element that i need to be xsi:type="xsd:int", then adding these attributes to those elements.

The code i ended up using for reference (from the aforementioned stackoverflow question with minor adjustments):

from suds.plugin import MessagePlugin
from suds.sax.attribute import Attribute

class SoapFixer(MessagePlugin):
    def marshalled(self, context):
        # Alter the envelope so that the xsd namespace is allowed
        context.envelope.nsprefixes['xsd'] = 'http://www.w3.org/2001/XMLSchema'
        # Go through every node in the document and apply the fix function to patch up incompatible XML. 
        context.envelope.walk(self.fix_any_type_string)
    def fix_any_type_string(self, element):
        """Used as a filter function with walk in order to fix errors.
        If the element has a certain name, give it a xsi:type=xsd:int. Note that the nsprefix xsd must also
         be added in to make this work."""

        # Fix elements which have these names
        fix_names = ['Value', 'anotherelementname']
        if element.name in fix_names:
            element.attributes.append(Attribute('xsi:type', 'xsd:int'))


plugin=SoapFixer()

Then I added plugins=[plugin] to the client ctor.

Example:

client = suds.client.Client("http://127.0.0.1:8099/TagValueWriteService?wsdl",plugins=[plugin])



回答2:


This is not an 'answer' because this question is client-side. But I'm putting this here for the search engines for now.

The problem is the request message is a complex type.

My solution was on the server side. My service now accepts untyped elements in the request. The server-side parsing of the request body must know about the Request schema. Once that happens, the server can typecheck and parse the request without the elements being typed by the client.

Specifically, My error came from a service implemented with Python ZSI module, and Zope.

Any cannot parse untyped element

Here, I got the hint on complex request objects:
http://pypi.python.org/pypi/z3c.soap/ (see ValidateEmailRequest)

Here, I got a crash course in ZSI: Are there any working examples of Zolera SOAP Infrastructure (ZSI)?

And decent ZSI docs here: http://pywebsvcs.sourceforge.net/zsi.html#SECTION0071100000000000000000

To make ZSI happy, you just have to create a class that represents the Request message, and add a typecode to it. This is why you see a lot of services to "operation foo" and "fooRequest" and "fooResponse", so they can type the request and response objects as xml complex types.

for the example above, I would import something like this into the namespace where the soap request body is being parsed. You can get much more complicated, but this is really all that's necessary:

import ZSI

class WriteRealTimeValuesRequest(object):
   tagValues = array() #of TagValue


WriteRealTimeValuesRequest.typecode = ZSI.TC.Struct(WriteRealTimeValuesRequest,
                                                    (ZSI.TC.Array("TagValue",
                                                                 TagValue.typecode,
                                                                 "tagValues"
                                                                ),
                                                     ),
                                                    "WriteRealTimeValuesRequest")

"Whats a tag value?"

class TagValue(object):
   Quality = Quality
   TagId = TagId
   Value = Value

TagValue.typecode = ZSI.TC.Struct(TagValue,
                                  (Quality.typecode,
                                   TagId.typecode,
                                   Value.typecode),
                                  "TagValue")

What's a Quality?

class Quality(object):
   Id = 0
   QualityData = "I'm a string"

Quality.typecode = ZSI.TC.Struct(Quality,
                                 (ZSI.TC.Integer("Id"),  #this is the secret sauce 
                                  ZSI.TC.String("QualityData")  #and here
                                 ),
                                 "Quality")

And so on, until you've drilled down all the way to all the primitive types.



来源:https://stackoverflow.com/questions/13593950/suds-write-request-missing-datatype

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!