SOAP views with on-demand WSDL generation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# soaplib_handler.py

from soaplib.wsgi_soap import SimpleWSGISoapApp
from soaplib.service import soapmethod
from soaplib.serializers import primitive as soap_types

from django.http import HttpResponse


class DjangoSoapApp(SimpleWSGISoapApp):

    def __call__(self, request):
        django_response = HttpResponse()
        def start_response(status, headers):
            status, reason = status.split(' ', 1)
            django_response.status_code = int(status)
            for header, value in headers:
                django_response[header] = value
        response = super(SimpleWSGISoapApp, self).__call__(request.META, start_response)
        django_response.content = "\n".join(response)

        return django_response


# views.py - sample view

from soaplib_handler import DjangoSoapApp, soapmethod, soap_types


class HelloWorldService(DjangoSoapApp):

    __tns__ = 'http://my.namespace.org/soap/'

    @soapmethod(soap_types.String, soap_types.Integer, _returns=soap_types.Array(soap_types.String))
    def say_hello(self, name, times):
        results = []
        for i in range(0, times):
            results.append('Hello, %s'%name)
        return results

hello_world_service = HelloWorldService()


# urls.py

urlpatterns = patterns(
    '',
    (r'^hello_world/', 'foo.views.hello_world_service'),
    (r'^hello_world/service.wsdl', 'foo.views.hello_world_service'),
)

Comments

adamlofts (on August 15, 2008):

This is a great example. Thanks!

#

csar (on August 15, 2008):

This doesn't work for me because of problems with wsgi.input - have you actually successfully run this? The server just hangs for me trying to read the request from the client. I really wish it would work cause it's such a simple way of structuring this and soaplib looked pretty nice.

#

adamlofts (on August 20, 2008):

I've just started to use this (it works). I have 2 comments:

  1. When you use super() you usually pass the class you are in (not the base class).

  2. To automatically follow Django model relationships I have written a little SoapDjangoArray type; which will follow a reverse foreign key or m2m relationship:

class SoapDjangoArray(soap_types.Array):

def to_xml(self, values, name='retval'):
    return soap_types.Array.to_xml(self, values.all(), name)``

#

jsomara (on September 23, 2008):

If you are having a problem with wsgi input, you may want to ensure your webserver is correctly serving WSGI. If you're using apache, you can follow the guide here to set up mod_wsgi: http://code.google.com/p/modwsgi/wiki/IntegrationWithDjango

I had a problem with it hanging as well, but I didn't have mod_wsgi set up correctly. This fixed the issue.

I'm having another issue though, from inside soaplib if I try to 'say_hello': File "/usr/lib/python2.5/site-packages/soaplib/soap.py", line 129, in from_soap if len(body.getchildren()): AttributeError: 'NoneType' object has no attribute 'getchildren'

#

jsomara (on September 23, 2008):

In reference to my last post : this was a user error. I had set up my client as http://localhost:8000/hello_world/service.wsdl instead of http://localhost:8000/hello_world/. Works as intended now.

#

erratic (on January 28, 2009):

You guys are so close!

Ok, so the way I see it is that manage.py (calling manage.py runserver) is setup by default to execute the command (in /usr/lib/python/site-packages/django/core/management/commands) depending on your install runserver.py which seems to me is written with the RESTful pattern in mind. Here is a tutorial that describes how to write a custom command:

http://oebfare.com/blog/2008/nov/03/writing-custom-management-command/

Note that this tutorial demonstrates some interesting ways to use the WSGIServer provided by CherryPy.

Seeing as how we're working with SOAP, it would stand to reason to follow this pattern by implementing a new command and going from there. Could even go so far as to implement a command that supports both the CherryPy WSGI and the soaplib WSGI.

Personally I intend to write my own which initially I only care to have support for the soaplib wsgi but also the ability to allow each SOAP project app its ability to have a sudo-static instance or an instance of an object that remains intact so long as manage.py runsoapserver is running. Since this is the server instance.

Something I haven't thought of yet is whether or not the Django.core.handler.wsgihandler will suffice for use with the soaplib wsgi server. it would stand to reason that since I need to get a "sudo-static" instance of an object that remains as such so long as the WSGI server is running that I'll need to get that object to the appropriate views somehow. Not to mention the handler's filename is "wsgi.py" which kinda pushes anything I might want to write that's "wsgi" out of the way assuming I can't do that, any thoughts or suggestions to this approach I've come up with would be awesome!

Since requests are handled this way, "sudo-static" object instances would be pretty easy to conceal from views that they don't belong to. The idea being, the server gets a request, decides what app is being called on, then passes the instance that belongs to it. Some uses for this would be a way to have something like for example a thread pool that I can have control over via a SOAP API... something that runs while I'm away :)

... again any thoughts are welcome

#

emilianoheyns (on February 12, 2009):

@csar: I'm having the same problem. Do you have a solution yet?

#

emilianoheyns (on February 12, 2009):

Got it: change soaplib_handler into:

soaplib_handler.py

from soaplib.wsgi_soap import SimpleWSGISoapApp from soaplib.service import soapmethod from soaplib.serializers import primitive as soap_types import StringIO

from django.http import HttpResponse

class DumbStringIO(StringIO.StringIO): def read(self, n): return self.getvalue()

class DjangoSoapApp(SimpleWSGISoapApp):

def __call__(self, request):

    django_response = HttpResponse()
    def start_response(status, headers):
        status, reason = status.split(' ', 1)
        django_response.status_code = int(status)
        for header, value in headers:
            django_response[header] = value

    environ = request.META.copy()
    body = ''.join(['%s=%s' % v for v in request.POST.items()])
    environ['CONTENT_LENGTH'] = len(body)
    environ['wsgi.input'] = DumbStringIO(body)
    environ['wsgi.multithread'] = False

    response = super(DjangoSoapApp, self).__call__(environ, start_response)

    django_response.content = "\n".join(response)

    return django_response

#

zdmytriv (on June 18, 2009):

It also hangs for me also,

emilianoheyns's patch works fine.

Thanks

#

lid (on January 7, 2010):

The snippet works very well, the only problem I have is that the reply has no namespace defined :S

#

harmv (on January 29, 2010):

There is a bug in soaplib v8.1 The generated wsdl contains an error.

see: http://github.com/jkp/soaplib/issues/#issue/12

soapui & axis clients don't accept this wsdl

#

(Forgotten your password?)

You may use Markdown syntax here, but raw HTML will be removed.