soap reponse in wsdl does not match description

Asked by GaryS

Hi, I have noticed that for functions that return complex types, the soap wsdl contents does not match the description or what I am expecting. All is fine in the soap11 description though. This is with Ladon 0.7.5 on red hat linux. eg:

Here is an example I added to calculator.py:

class Response1(LadonType):
        name = str
        a = float
        b = float
        c = float

  @ladonize(str,rtype=Response1)
  def func1(self,s):
    rv = Response1()
    rv.name = "hello"
    rv.a = 1.2
    rv.b = 1.3
    rv.c = 1.5
    return rv

Here is description:
func1 ( string s )
None

Parameters
s: string
Return value
Response1

Here is soap wsdl:

<message name="func1">
<part name="s" type="xsd:string"/>
</message>
<message name="func1Response">
<part name="a" type="xsd:decimal"/>
<part name="b" type="xsd:decimal"/>
<part name="c" type="xsd:decimal"/>
<part name="name" type="xsd:string"/>
</message>

Here is soap11 wsdl:
<wsdl:message name="func1">
<wsdl:part name="s" type="xsd:string"/>
</wsdl:message>
<wsdl:message name="func1Response">
<wsdl:part name="result" type="ns1:Response1"/>

ie, it appears the soap wsdl declares that the method returns individual values rather than the 'response1' type as expected.

Thanks for any advice you can give,

Gary

Question information

Language:
English Edit question
Status:
Expired
For:
ladon Edit question
Assignee:
No assignee Edit question
Last query:
Last reply:
Revision history for this message
Launchpad Janitor (janitor) said :
#1

This question was expired because it remained in the 'Open' state without activity for the last 15 days.

Revision history for this message
Michael Goebel (michael-m) said :
#2

Bump.

Revision history for this message
André (andre-miras) said :
#3

I've also observed this. I don't know how suds manages to make one object out of the response, but C# SOAP client doesn't. Resulting a method signature with out parameters, see below:
public decimal func1(out decimal b, out decimal c, out string name, string s)

Revision history for this message
GaryS (garysh26) said :
#4

I have developed a new interface which solves the problem - this seems to work with java-ws clients and .net clients I have tested it with. I called it soapws:

# -*- coding: utf-8 -*-

from ladon.interfaces.base import BaseInterface,ServiceDescriptor,BaseRequestHandler,BaseResponseHandler,BaseFaultHandler
from ladon.interfaces import expose
from ladon.compat import PORTABLE_STRING,type_to_xsd,pytype_support,BytesIO
from xml.sax.handler import ContentHandler,feature_namespaces
from xml.sax import make_parser
from xml.sax.xmlreader import InputSource
import sys,re,traceback
import logging

LOG = logging.getLogger(__name__)

rx_nil_attr = re.compile(PORTABLE_STRING('^\w*[:]{0,1}nil$'),re.I)

class SOAPWSServiceDescriptor(ServiceDescriptor):

 xsd_type_map = type_to_xsd
 _content_type = 'text/xml'

 def generate(self,servicename,servicenumber,typemanager,methodlist,service_url,encoding):
  """
  Generate WSDL file for SOAPWSInterface
  """
  type_dict = typemanager.type_dict
  type_order = typemanager.type_order

  def map_type(typ):
   if typ in SOAPWSServiceDescriptor.xsd_type_map:
    return SOAPWSServiceDescriptor.xsd_type_map[typ]
   else:
    return typ.__name__

  def map_type2(typ):
   if typ in SOAPWSServiceDescriptor.xsd_type_map:
                                tmpval = SOAPWSServiceDescriptor.xsd_type_map[typ]
    if tmpval == 'decimal':
     tmpval = 'float'
    return tmpval
                        else:
                                return typ.__name__

  import xml.dom.minidom as md
  doc = md.Document()

  # SERVICE DEFINITION
  # Create the definitions element for the service
  definitions = doc.createElement('definitions')
  definitions.setAttribute('xmlns:wsu','http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd')
  definitions.setAttribute('xmlns:wsp','http://www.w3.org/ns/ws-policy')
  definitions.setAttribute('xmlns:wsp1_2','http://schemas.xmlsoap.org/ws/2004/09/policy')
  definitions.setAttribute('xmlns:wsam','http://www.w3.org/2007/05/addressing/metadata')
        definitions.setAttribute('xmlns:soap','http://schemas.xmlsoap.org/wsdl/soap/')

  definitions.setAttribute('name', servicename)
  definitions.setAttribute('targetNamespace','urn:%s' % servicename)
  definitions.setAttribute('xmlns:tns','urn:%s' % servicename)
  definitions.setAttribute('xmlns','http://schemas.xmlsoap.org/wsdl/')
  definitions.setAttribute('xmlns:xsi','http://www.w3.org/2001/XMLSchema-instance')
  definitions.setAttribute('xmlns:xsd','http://www.w3.org/2001/XMLSchema')
  definitions.setAttribute('xmlns:ns%d' % servicenumber,'urn:%s' % servicename)
  doc.appendChild(definitions)

  # TYPES
  # The types element
  types = doc.createElement('types')
  definitions.appendChild(types)

  # Service schema for types required by the target namespace we defined in the definition element
  schema = doc.createElement('xsd:schema')
  schema.setAttribute('targetNamespace','urn:%s' % servicename)
  schema.setAttribute('xmlns:xs','http://www.w3.org/2001/XMLSchema')
  schema.setAttribute('xmlns:ns%d' % servicenumber,'urn:%s' % servicename)
  schema.setAttribute('xmlns:xsi','http://www.w3.org/2001/XMLSchema-instance')
                schema.setAttribute('xmlns:xsd','http://www.w3.org/2001/XMLSchema')

  types.appendChild(schema)

  # Define types, the type_order variable holds all that need to be defined and in the
  # correct order.
  for m in methodlist:
   basictype = doc.createElement('xs:element')
   basictype.setAttribute('name',m.name())
   basictype.setAttribute('type','tns:%s' % m.name())
   schema.appendChild(basictype)
   basictypeR = doc.createElement('xs:element')
                        basictypeR.setAttribute('name',"%sResponse" % m.name())
                        basictypeR.setAttribute('type','tns:%sResponse' % m.name())
   schema.appendChild(basictypeR)

  for m in methodlist:
   complextype = doc.createElement('xs:complexType')
                        complextype.setAttribute('name',m.name())

                        schema.appendChild(complextype)
                        sequence = doc.createElement('xs:sequence')
                        complextype.appendChild(sequence)
   for arg in m.args():
    v = arg['type']
                         element = doc.createElement('xs:element')
                                element.setAttribute('name',arg['name'].replace('_','-'))
                                element.setAttribute('minOccurs','0')
                                if isinstance(v, list):
                                 inner = v[0]
                                        if inner in type_dict:
                                         element.setAttribute('type','tns:%s' % inner.__name__)
                                        else:
                                         element.setAttribute('type','xs:%s' % map_type2(inner))
                                        element.setAttribute('maxOccurs','unbounded')
                                if not(isinstance(v, list)):
                                 if v in type_dict:
                                         element.setAttribute('type','tns:%s' % v.__name__)
                                        else:
                                                element.setAttribute('type','xs:%s' % map_type2(v))
                                sequence.appendChild(element)

   complextype = doc.createElement('xs:complexType')
                        complextype.setAttribute('name',"%sResponse" % m.name())

                        schema.appendChild(complextype)
                        sequence = doc.createElement('xs:sequence')
                        complextype.appendChild(sequence)
                        v = m._rtype
                        element = doc.createElement('xs:element')
                        element.setAttribute('name','return')
                        element.setAttribute('minOccurs','0')
                        if isinstance(v, list):
                         inner = v[0]
                                if inner in type_dict:
                                 element.setAttribute('type','tns:%s' % inner.__name__)
                                 element.setAttribute('maxOccurs','unbounded')
    else:
                                        element.setAttribute('type','xs:%s' % map_type2(inner))
                                 element.setAttribute('maxOccurs','unbounded')
    element.setAttribute('minOccurs','0')
                        if not(isinstance(v, list)):
                                if v in type_dict:
                                        element.setAttribute('type','tns:%s' % v.__name__)
                                else:
                                        element.setAttribute('type','xs:%s' % map_type2(v))
                        sequence.appendChild(element)

  #special types
  for typ in type_order:
   if not(isinstance(typ, list)):
    complextype = doc.createElement('xs:complexType')
    complextype.setAttribute('name',typ['name'])
    schema.appendChild(complextype)
    sequence = doc.createElement('xs:sequence')
    complextype.appendChild(sequence)
    for k,v,props in typ['attributes']:
     element = doc.createElement('xs:element')
     element.setAttribute('name',k.replace('_','-'))
     element.setAttribute('maxOccurs','1')
     element.setAttribute('minOccurs','1')
     if props.get('nullable')==True:
      element.setAttribute('minOccurs','0')
      element.setAttribute('nillable','true')
     if isinstance(v, list):
      inner = v[0]
      #element.setAttribute('type','tns:%s' % map_type2(inner))
      if inner in type_dict:
                                                        element.setAttribute('type','tns:%s' % inner.__name__)
                                                else:
                                                        element.setAttribute('type','xs:%s' % map_type2(inner))

      #inner.__name__)
      element.setAttribute('minOccurs','0')
      element.setAttribute('maxOccurs','unbounded')
      element.setAttribute('nillable','true')
      #element.setAttribute('type','tns:%s' % type(v[0]))
     if not(isinstance(v, list)):
      if v in type_dict:
       element.setAttribute('type','tns:%s' % v.__name__)
      else:
       element.setAttribute('type','xs:%s' % map_type2(v))
     sequence.appendChild(element)

  #messages
  for m in methodlist:
   message = doc.createElement('message')
   message.setAttribute('name',m.name())
   definitions.appendChild(message)
   part = doc.createElement('part')
   part.setAttribute('name','parameters')
   part.setAttribute('element',"tns:%s" % m.name())
   message.appendChild(part)
   message = doc.createElement('message')
   message.setAttribute('name',"%sResponse" % m.name())
   definitions.appendChild(message)
   part2 = doc.createElement('part')
   part2.setAttribute('name','parameters')
   part2.setAttribute('element',"tns:%sResponse" % m.name())
   message.appendChild(part2)
  porttype = doc.createElement('portType')
  porttype.setAttribute('name','%sPortType' % servicename)
  definitions.appendChild(porttype)

  #operations
  for m in methodlist:
   operation = doc.createElement('operation')
   operation.setAttribute('name',m.name())
   porttype.appendChild(operation)
   if m.__doc__:
    documentation = doc.createElement('documentation')
    documentation.appendChild(doc.createTextNode(m.__doc__))
    operation.appendChild(documentation)
   input_tag = doc.createElement('input')
   input_tag.setAttribute('wsam:Action',"%s/%s" % (service_url,m.name()))
   input_tag.setAttribute('message','tns:%s' % m.name())
   operation.appendChild(input_tag)
   output_tag = doc.createElement('output')
   output_tag.setAttribute('wsam:Action',"%s/%sResponse" % (service_url,m.name()))
   output_tag.setAttribute('message','tns:%sResponse' % m.name())
   operation.appendChild(output_tag)

  #binding
  binding = doc.createElement('binding')
  binding.setAttribute('name',servicename)
  binding.setAttribute('type',"tns:%sPortType" % servicename)
  transport = doc.createElement('soap:binding')
  transport.setAttribute('transport','http://schemas.xmlsoap.org/soap/http')
  transport.setAttribute('style','document')
  binding.appendChild(transport)
  definitions.appendChild(binding)

  for m in methodlist:
   operation = doc.createElement('operation')
   operation.setAttribute('name',m.name())
   binding.appendChild(operation)
   soapaction = doc.createElement('soap:operation')
   soapaction.setAttribute('soapAction','')
   operation.appendChild(soapaction)

   m._multipart_response_required
   input_tag = doc.createElement('input')
   body_parent = input_tag
   if m._multipart_request_required:
    multipart_related = doc.createElement('mime:multipartRelated')
    mime_body_part = doc.createElement('mime:part')
    body_parent = mime_body_part
    mime_content_part = doc.createElement('mime:part')
    mime_content = doc.createElement('mime:content')
    mime_content.setAttribute('type','*/*')
    input_tag.appendChild(multipart_related)
    multipart_related.appendChild(mime_body_part)
    multipart_related.appendChild(mime_content_part)
    mime_content_part.appendChild(mime_content)

   input_soapbody = doc.createElement('soap:body')
   input_soapbody.setAttribute('use','literal')
   body_parent.appendChild(input_soapbody)
   operation.appendChild(input_tag)
   output_tag = doc.createElement('output')
   body_parent = output_tag
   if m._multipart_request_required:
    multipart_related = doc.createElement('mime:multipartRelated')
    mime_body_part = doc.createElement('mime:part')
    body_parent = mime_body_part
    mime_content_part = doc.createElement('mime:part')
    mime_content = doc.createElement('content:part')
    mime_content.setAttribute('type','*/*')
    output_tag.appendChild(multipart_related)
    multipart_related.appendChild(mime_body_part)
    multipart_related.appendChild(mime_content_part)
    mime_content_part.appendChild(mime_content)

   output_soapbody = doc.createElement('soap:body')
   output_soapbody.setAttribute('use','literal')
   body_parent.appendChild(output_soapbody)
   operation.appendChild(output_tag)

  service = doc.createElement('service')
  service.setAttribute('name',servicename)
  documentation = doc.createElement('documentation')
  documentation.appendChild(doc.createTextNode('Ladon generated service definition'))
  service.appendChild(documentation)
  port = doc.createElement('port')
  port.setAttribute('name',servicename)
  port.setAttribute('binding','tns:%s' % servicename)
  service.appendChild(port)
  address = doc.createElement('soap:address')
  address.setAttribute('location',service_url)
  port.appendChild(address)
  definitions.appendChild(service)
  if sys.version_info[0]>=3:
   return doc.toxml()
  return doc.toxml(encoding)

def u(instring):
 if sys.version_info[0]==2:
  return PORTABLE_STRING(instring,'utf-8')
 else:
  return PORTABLE_STRING(instring)

class ContainerSetRef(object):
 def __init__(self,c,refval):
  self.c = c
  self.refval = refval

 def set(self,val):
  self.c[self.refval] = val

class SOAPWSRequestHandler(BaseRequestHandler):

 def parse_request(self,soap_body,sinfo,encoding):
  import xml.dom.minidom as md
  self.sinfo = sinfo
  doc = md.parseString(soap_body)
  soap_envelope = doc.getElementsByTagNameNS('*','Envelope')[0]
  soap_body = doc.getElementsByTagNameNS('*','Body')[0]
  EN = soap_body.ELEMENT_NODE
  soap_method = (node for node in soap_body.childNodes
    if node.nodeType == EN).next()
  soap_methodprefix = soap_method.prefix
  try:
   m = re.match("^ns(\d+)$",soap_methodprefix)
  except:
   m = None
  servicenumber = None
  if m: servicenumber = int(m.groups()[0])
  self.soap_methodname = soap_method.localName
  soap_args = {'methodname': self.soap_methodname,'servicenumber':servicenumber}
  #for typ in self.sinfo.typemanager.type_order:
                # if not(isinstance(typ, list)):
  # self.appendToMyLog(typ)
  TN = soap_method.TEXT_NODE
  soap_args['args'] = self.getDictForNode(soap_method)
  #self.appendToMyLog('soap_args is %s' % soap_args)
  return soap_args

 def getDictForNode(self, node):
         localDict={}
         for n in node.childNodes:
              if n.nodeType == n.ELEMENT_NODE and not n.hasAttribute('xsi:nil'):
                      if self.isTagList(n.nodeName):
                          lon = self.getListItems(n,node)
                          tList = []
                          for nn in lon:
                                tList.append(self.getDictForNode(nn))
                                node.removeChild(nn)
                          localDict[n.nodeName]= tList
                      else:
                          localDict[n.nodeName]=self.getDictForNode(n)
              elif n.nodeType==n.TEXT_NODE and not n.hasChildNodes() and node.childNodes.length==1:
                  return n.nodeValue
   elif n.nodeType==n.CDATA_SECTION_NODE:
                  return n.nodeValue
     return localDict

 def isTagList(self, tagnm):
                for typ in self.sinfo.typemanager.type_order:
                        #self.appendToMyLog(typ)
   a=1
                        try:
                                a=1
                                if not isinstance(typ, list):
                                        for k,v,props in typ['attributes']:
                                                if tagnm == k and isinstance(v,list):
                                                        return True
                        except:
                                a=2
                for m in self.sinfo.method_list():
   #self.appendToMyLog(m)
   #self.appendToMyLog(m.name())
   for arg in m.args():
                                v = arg['type']
                                if self.soap_methodname == m.name() and arg['name'] == tagnm and isinstance(v,list):
                                        #self.appendToMyLog("%s is list!!" % m.name())
     return True
   #if isinstance(m['rtype'], list):
   # self.appendToMyLog(m['rtype'])
   # return True
  return False

    def getListItems(self, node, parent):
          tagList=[]
         for n in parent.childNodes:
              if n.nodeName==node.nodeName:
                  tagList.append(n)
         return tagList

 def appendToMyLog(self, s):
                f1=open('/home/ye91009/logs/testfile', 'a')
                print >> f1, s
                f1.close()

class SOAPWSResponseHandler(BaseResponseHandler):

 _content_type = 'text/xml'
 _stringify_res_dict = True

 def dictToList(self,dd):
  #convert dictionary to list i fpossible, to ensure order in type_order is preserved to giev valid xml export format
  retList=[]
  for typ in self.sinfo.typemanager.type_order:
                 if not(isinstance(typ, list)):
    ld={}
    for k,v,props in typ['attributes']:
     ld[k]=None
    if set(ld)==set(dd):
     for k,v,props in typ['attributes']:
      retList.append(k)
     self.appendToMyLog(retList)
     return retList
  for k,v in dd.items():
   retList.append(k)
  return retList

 def value_to_soapxml(self,value,parent,doc,is_toplevel=False):
  if isinstance(value, dict):
   locList = self.dictToList(value)
   self.appendToMyLog(value)
   for aname in locList:
   #for attr_name,attr_val in value.items():
    #self.appendToMyLog(aname)
    attr_name = aname
    attr_val = value[aname]
    xml_attr_name = attr_name.replace('_','-')
                                if isinstance(attr_val, (list,tuple)):
     for item in attr_val:
      attr_elem = doc.createElement(xml_attr_name)
      if is_toplevel:
                                                 attr_elem = doc.createElement('return')
      parent.appendChild(attr_elem)
      self.value_to_soapxml(item,attr_elem,doc)
    else:
     attr_elem = doc.createElement(xml_attr_name)
     if is_toplevel:
      attr_elem = doc.createElement('return')
     parent.appendChild(attr_elem)
     self.value_to_soapxml(attr_val,attr_elem,doc)
  else:
   if is_toplevel:
    value_parent = doc.createElement('return')
    value_parent.setAttribute('dodo','http://schemas.xmlsoap.org/soap/encoding/')
    parent.appendChild(value_parent)
   else:
    value_parent = parent

   if isinstance(value, (list, tuple)):
    if not len(value):
     # Translate empty response arrays to SOAP Null (xsi:nil) value
     value_parent.setAttribute('xsi:nil','true')
    else:
     for item in value:
      item_element = doc.createElement(value_parent.nodeName)
      self.value_to_soapxml(item,item_element,doc)
      value_parent.appendChild(item_element)
   else:
    if value==None:
     # Set xsi:nil to true if value is None
     value_parent.setAttribute('xsi:nil','true')
    else:
     value_parent.appendChild(doc.createTextNode(value))

 def build_response(self,res_dict,sinfo,encoding):
  import xml.dom.minidom as md
  self.sinfo=sinfo
  doc = md.Document()
  envelope = doc.createElement('SOAP-ENV:Envelope')
  envelope.setAttribute('xmlns:SOAP-ENV','http://schemas.xmlsoap.org/soap/envelope/')
  envelope.setAttribute('xmlns:SOAP-ENC','http://schemas.xmlsoap.org/soap/encoding/')
  envelope.setAttribute('xmlns:xsd','http://www.w3.org/2001/XMLSchema')
  envelope.setAttribute('xmlns:ns','urn:%s' % res_dict['servicename'])
  envelope.setAttribute('xmlns:xsi','http://www.w3.org/2001/XMLSchema-instance')
  doc.appendChild(envelope)
  body_elem = doc.createElement('SOAP-ENV:Body')
  body_elem.setAttribute('SOAP-ENV:encodingStyle','http://schemas.xmlsoap.org/soap/encoding/')
  envelope.appendChild(body_elem)
  method_elem = doc.createElement("tns:%sResponse" % res_dict['method'])
  method_elem.setAttribute('xmlns:tns','urn:%s' % res_dict['servicename'])
  #self.appendToMyLog(res_dict)
  if 'result' in res_dict['result']:
   self.value_to_soapxml(res_dict['result'],method_elem,doc,is_toplevel=True)
  else:
   self.value_to_soapxml({'result':res_dict['result']},method_elem,doc,is_toplevel=True)
  body_elem.appendChild(method_elem)
  return doc.toxml(encoding=encoding)

 def appendToMyLog(self, s):
                f1=open('/home/ye91009/logs/testfile', 'a')
                print >> f1, s
                f1.close()

class SOAPWSFaultHandler(BaseFaultHandler):

 _content_type = 'text/xml'
 _stringify_res_dict = True
 soapfault_template = """<?xml version='1.0' encoding='UTF-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:Body>
  <SOAP-ENV:Fault>
   <faultcode xsi:type="xsd:string"></faultcode>
   <faultstring xsi:type="xsd:string"></faultstring>
   <detail></detail>
  </SOAP-ENV:Fault>
 </SOAP-ENV:Body>
</SOAP-ENV:Envelope>"""

 def build_fault_response(self,service_exc,sinfo,methodname,encoding):
  import xml.dom.minidom as md
  if service_exc.detail:
   detail = service_exc.detail
  else:
   detail = traceback.format_exc()
  d = md.parseString(self.soapfault_template)
  # Extract fault DOM elements
  faultcode_elem = d.getElementsByTagName('faultcode')[0]
  faultstring_elem = d.getElementsByTagName('faultstring')[0]
  detail_elem = d.getElementsByTagName('detail')[0]
  # Set the fault values
  faultcode_elem.appendChild(d.createTextNode(service_exc.faultcode))
  faultstring_elem.appendChild(d.createTextNode(service_exc.faultstring))
  detail_elem.appendChild(d.createTextNode(detail))
  # Return the SoapFault XML object
  return d.toxml(encoding=encoding)

@expose
class SOAPWSInterface(BaseInterface):

 def __init__(self,sinfo,**kw):
  def_kw = {
   'service_descriptor': SOAPWSServiceDescriptor,
   'request_handler': SOAPWSRequestHandler,
   'response_handler': SOAPWSResponseHandler,
   'fault_handler': SOAPWSFaultHandler}
  def_kw.update(kw)
  BaseInterface.__init__(self,sinfo,**def_kw)

 @staticmethod
 def _interface_name():
  return 'soapws'

 @staticmethod
 def _accept_basetype(typ):
  return pytype_support.count(typ)>0

 @staticmethod
 def _accept_list():
  return True

 @staticmethod
 def _accept_dict():
  return False