Search code examples
pythonpython-3.xsoapwsdlspyne

How can I built a soap server from WSDL file?


I'm trying to create a soap server from WSDL file. I'm using Python 3 and Spyne for generate it. This server need to be specific, because the client already exists. I need that my WSDL request looks like the following

   <soapenv:Body>
      <pus:receiveEvents>
         <!--Optional:-->
         <eventQueryResult>
            <!--Optional:-->
            <queryId>?</queryId>
            <!--Optional:-->
            <queryStatus>?</queryStatus>
            <!--Zero or more repetitions:-->
            <events>
               <!--Optional:-->
               <eid>?</eid>
               <!--Optional:-->
               <eventMessage>?</eventMessage>
               <!--Optional:-->
               <eventSeverity>?</eventSeverity>
               <!--Optional:-->
               <eventTime>?</eventTime>
               <!--Optional:-->
               <eventTypeName>?</eventTypeName>
               <!--Optional:-->
               <meterId>?</meterId>
            </events>
            <subscriptionId>?</subscriptionId>
         </eventQueryResult>
      </pus:receiveEvents>
   </soapenv:Body>

But, this is my WSDL request:

   <soapenv:Body>
      <pus:receiveEvents>
         <!--Optional:-->
         <pus:eventQueryResult>
            <!--Optional:-->
            <pus:queryId>?</pus:queryId>
            <!--Optional:-->
            <pus:queryStatus>?</pus:queryStatus>
         </pus:eventQueryResult>
         <!--Optional:-->
         <pus:events>
            <!--Optional:-->
            <pus:eid>?</pus:eid>
            <!--Optional:-->
            <pus:eventMessage>?</pus:eventMessage>
            <!--Optional:-->
            <pus:eventSeverity>?</pus:eventSeverity>
            <!--Optional:-->
            <pus:eventTime>?</pus:eventTime>
            <!--Optional:-->
            <pus:eventTypeName>?</pus:eventTypeName>
            <!--Optional:-->
            <pus:meterId>?</pus:meterId>
         </pus:events>
         <pus:subscriptionId>
            <!--Optional:-->
            <pus:subscriptionId>?</pus:subscriptionId>
         </pus:subscriptionId>
      </pus:receiveEvents>
   </soapenv:Body>

This is the code that I wrote

    class eventQueryResult(ComplexModel):
    __namespace__ = 'http://pushevent.nbapi.cgms.cisco.com/'

    queryId = String
    queryStatus = String

class events(ComplexModel):
    __namespace__ = 'http://pushevent.nbapi.cgms.cisco.com/'   

    eid = String
    eventMessage = String 
    eventSeverity = String
    eventTime = Long
    eventTypeName = String
    meterId = String

class subscriptionId(ComplexModel):
    __namespace__ = 'http://pushevent.nbapi.cgms.cisco.com/'

    subscriptionId = Long

class EventPushService(ServiceBase):
    __tns__ = 'http://pushevent.nbapi.cgms.cisco.com/'
    __wsdl__ = 'http://schemas.xmlsoap.org/wsdl/'

    @rpc(eventQueryResult.customize(min_occurs=0), events.customize(min_occurs=0), subscriptionId.customize(min_occurs=1,nillable = True), _returns=ResponseData)
    def receiveEvents(ctx, eventQueryResult, events, subscriptionId):
        eid = events.eid

        return print(eid)

I need that eventQueryResult contains events, but I don't know how. The documentation of Spyne doesn't help me with that.

Thanks for your comments


Solution

  • There's a few things to work on here. The code is pretty incomplete, so some assumptions were made. This is pretty close to the spyne sample app, matched up with what what could be determined from the code snippets you posted.

    When you are creating the classes to model your data, you want the eventQueryResult to include events. Arrays are document here. That code would look more like:

    class events(ComplexModel):
        eid = String
        eventMessage = String
        eventSeverity = String
        eventTime = Long
        eventTypeName = String
        meterId = String
    
    class eventQueryResult(ComplexModel):
        queryId = String
        queryStatus = String
        events = Array(events)
    

    When you're specifying a RPC, the name of the function is the soapAction, and first is the model of the object the client will be sending you. Here the whole payload is modeled as one object. Bare means that the payload will not be wrapped in an array. Depending on your client, that may need to change.

        @rpc(eventQueryResult, _body_style='bare', _returns=someResponse)
    

    someResponse is the model of the response you want to send back to the client. I didn't really know what you wanted there, how about an array of the events that were accepted? The entire script looks like:

    import logging
    from spyne import Application, rpc, ServiceBase, \
        String, Long
    from spyne.protocol.soap import Soap11
    from spyne.server.wsgi import WsgiApplication
    from spyne.model.complex import ComplexModel, Array
    
    logging.basicConfig(level="WARNING")
    
    class event(ComplexModel):
        eid = String
        eventMessage = String
        eventSeverity = String
        eventTime = Long
        eventTypeName = String
        meterId = String
    
    class eventQueryResult(ComplexModel):
        queryId = String
        queryStatus = String
        events = Array(event)
    
    class someResponse(ComplexModel):
        eventIds = Array(String)
    
    class EventPushService(ServiceBase):
        @rpc(eventQueryResult, _body_style='bare', _returns=someResponse)
        def receiveEvents(ctx, req):
            print(req)
    
            return someResponse(eventIds = [ i.eid for i in req.events ] )
    
    application = Application([EventPushService],
        tns='http://pushevent.nbapi.cgms.cisco.com/',
        in_protocol=Soap11(validator='lxml'),
        out_protocol=Soap11()
    )
    
    if __name__ == '__main__':
        # You can use any Wsgi server. Here, we chose
        # Python's built-in wsgi server but you're not
        # supposed to use it in production.
    
        from wsgiref.simple_server import make_server
        wsgi_app = WsgiApplication(application)
        server = make_server('0.0.0.0', 80, wsgi_app)
        server.serve_forever()
    
    

    I was testing with a payload that looks like

    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://pushevent.nbapi.cgms.cisco.com/">
      <soapenv:Header/>
      <soapenv:Body>
        <tns:receiveEvents>
          <tns:queryId>100</tns:queryId>
          <tns:queryStatus>Success</tns:queryStatus>
          <tns:events>
            <tns:event>
              <tns:eid>someId</tns:eid>
              <tns:eventMessage>someEventMessage</tns:eventMessage>
              <tns:eventSeverity>someEventSeverity</tns:eventSeverity>
              <tns:eventTime>1614974343</tns:eventTime>
              <tns:eventTypeName>eventType</tns:eventTypeName>
              <tns:meterId>meterStayOnTheMeter</tns:meterId>
            </tns:event>
            <tns:event>
              <tns:eid>someId2</tns:eid>
              <tns:eventMessage>someEventMessage2</tns:eventMessage>
              <tns:eventSeverity>someEventSeverity2</tns:eventSeverity>
              <tns:eventTime>1614974344</tns:eventTime>
              <tns:eventTypeName>eventType2</tns:eventTypeName>
              <tns:meterId>meterStayOnTheMeter2</tns:meterId>
            </tns:event>
          </tns:events>
        </tns:receiveEvents>
      </soapenv:Body>
    </soapenv:Envelope>
    

    I was using a curl command like curl localhost -d @payload -H 'SOAPAction: "receiveEvents"' -H "Content-type: text/xml; charset='UTF-8'"