polo
polo

Reputation: 1442

How do spyne rpc decorated methods consume soap envelope

I'm trying to write a simple soap server. I know how the soap envelope will look like (predefined). For each request that the server will serve, I know the optional soap headers, the name of the method, and parameters, and also I know what the soap response will look like (in other words, the WSDL is defined).

What I'm trying to understand is what my spyne service should look like (inheriting from ServiceBase), to consume it.

Here's an example of the soap request I'm expecting:

<?xml version='1.0' encoding='UTF-8'?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns="http://resources.mydomain.com/soap/1.1">
    <soap:Header>
        <ns:myinfo>someinfo</ns:myinfo>
    </soap:Header>
    <soap:Body>
        <ns:MyMethod>
            <ns:mymethodparam>somevalue</ns:mymethodparam>
        </ns:MyMethod>
    </soap:Body>
</soap:Envelope>

At the moment, what I'm doing is just sending the above envelope via curl. The response I get is:

<?xml version='1.0' encoding='UTF-8'?>
<tns:Envelope xmlns:tns="http://schemas.xmlsoap.org/soap/envelope/">
    <tns:Body>
        <tns:Fault>
            <faultcode>senv:Client.ResourceNotFound</faultcode>
            <faultstring>Requested resource '{http://resources.mydomain.com/soap/1.1}MyMethod' not found</faultstring>
            <faultactor></faultactor>
        </tns:Fault>
    </tns:Body>
</tns:Envelope>

The code I'm using, as suggested by Burak Arslan is: example.py:

from spyne.model.primitive import Boolean, Unicode, Integer
from spyne.model.complex import ComplexModelBase
from spyne.model.complex import ComplexModel
from spyne.model.complex import ComplexModelMeta
from spyne.service import ServiceBase
from spyne.decorator import rpc
import logging

class MyHeader(ComplexModel):
   myinfo = Unicode

class MyMethodRequest(ComplexModel):
   mymethodparam = Unicode

class SomeService(ServiceBase):
    __in_header__ = MyHeader

    @rpc(MyMethodRequest, _body_style='bare')
    def MyMethod(request):
        print "I got:", request.mymethodparam

And the code I use to start the server is: test_run.py:

from spyne.application import Application
from spyne.protocol.soap import Soap11
from spyne.server.wsgi import WsgiApplication
from wsgiref.simple_server import make_server
import logging

logging.basicConfig(level=logging.DEBUG)
logging.info('listening to http://127.0.0.1:8000')

application = Application([SomeService],
                          'http://schemas.xmlsoap.org/soap/envelope/',
                          in_protocol=Soap11(),
                          out_protocol=Soap11())

wsgi_application = WsgiApplication(application)
server = make_server('127.0.0.1', 8000, wsgi_application)
server.serve_forever()

Another thing I'm not sure about is if I got the WSDL setup correctly (in the right location in the project), but as far as I understand (and I could be completely off, I'm new to soap and spyne), this should not really matter, as long as I build the API accordingly (using spyne). Am I correct?

I'm making the request with curl:

curl --header "content-type: text/soap+xml; charset=utf-8" --data @example.xml http://127.0.0.1:8000

Any pointers on what am I doing wrong? Thank you so much!

Upvotes: 2

Views: 4018

Answers (1)

Burak Arslan
Burak Arslan

Reputation: 8001

Okay, I actually ran your code and turns out it has a couple subtle issues that I'd missed in my earlier answer. You don't actually need any of the 'bare' stuff.

  1. The tns argument to the Application() instance can't be something that already exists. I'll make sure this throws a ValueError() in Spyne 2.11. I filed an issue for that: https://github.com/arskom/spyne/issues/317

    Please read the note in the Application section in http://spyne.io/docs/2.10/manual/01_highlevel.html if you care. If not just pass tns="gobbledygook" to Application and be done with it.

  2. You need to adjust your request document according to this change.

  3. You also need to add ctx before mymethodparam as the first argument to a @rpc-decorated function is always ctx.

  4. As for the data inside <myinfo> in SOAP header, well, that won't work yet due to a bug in Spyne: Only ComplexModel subclasses as arguments to __in_header__ seem to work. I've filed a bug to Spyne's issue tracker (https://github.com/arskom/spyne/issues/316) and expect this to be fixed soon.

    The workaround is to use a ComplexModel subclass as the header type. That means you need to modify the request that your client sends, either by modifying the client or by using a method_accept_document event to modify the request document before objectification.

So, here's the working server:

import logging
from spyne.model.primitive import Boolean, Unicode, Integer
from spyne.model.complex import ComplexModel
from spyne.service import ServiceBase
from spyne.decorator import rpc

class MyHeader(ComplexModel):
   myinfo = Unicode

class SomeService(ServiceBase):
    __in_header__ = MyHeader

    @rpc(Unicode)
    def MyMethod(ctx, mymethodparam):
        print "I got:", mymethodparam
        print "I also got:", ctx.in_header.myinfo


if __name__ == '__main__':
    from spyne.application import Application
    from spyne.protocol.soap import Soap11
    from spyne.server.wsgi import WsgiApplication
    from wsgiref.simple_server import make_server

    logging.basicConfig(level=logging.DEBUG)
    logging.info('listening to http://127.0.0.1:8000')

    application = Application([SomeService],
                          'myapp',
                          in_protocol=Soap11(validator='lxml'),
                          out_protocol=Soap11())

    wsgi_application = WsgiApplication(application)
    server = make_server('127.0.0.1', 8000, wsgi_application)
    server.serve_forever()

And here's a working request:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:ns="http://resources.mydomain.com/soap/1.1"
               xmlns:ns0="myapp">
  <soap:Header>
    <ns0:MyHeader>       
      <ns0:myinfo>someinfo</ns0:myinfo>
    </ns0:MyHeader>       
  </soap:Header>
  <soap:Body>
    <ns0:MyMethod>
      <ns0:mymethodparam>somevalue</ns0:mymethodparam>
    </ns0:MyMethod>
  </soap:Body>
</soap:Envelope>

Upvotes: 4

Related Questions