Reputation: 1442
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
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.
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.
You need to adjust your request document according to this change.
You also need to add ctx
before mymethodparam
as the first argument to a @rpc
-decorated function is always ctx
.
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