Spyne: Why am I getting empty responses for json r

2019-08-18 03:52发布

问题:

I have a working application that accepts SOAP requests, processes the requests, forwards the SOAP request to an API, processes the response, and then forwards the response to the client.

I'm trying to change this application so that it will be JSON between my application and the client but still use SOAP between API and my application

Now, it can successfully accept JSON requests from client and send/receive SOAP with API. However, all the responses to client are empty.

The only case that I receive a non-empty response is when there are validation errors with my JSON request.

Here are some code that might be relevant

app = Application([MyServer],
              MY_NAMESPACE,
              in_protocol=JsonDocument(validator='soft'),
              out_protocol=JsonDocument())

application_server = csrf_exempt(MyDjangoApplication(app))

definition of MyDjangoApplication

class MyDjangoApplication(DjangoApplication):
def __call__(self, request, *args, **kwargs):
    retval = self.HttpResponseObject()

    def start_response(status, headers):
        # Status is one of spyne.const.http
        status, reason = status.split(' ', 1)

        retval.status_code = int(status)
        for header, value in headers:
            retval[header] = value

    environ = request.META.copy()

    if request.method == 'POST':
        response = self.handle_rpc(environ, start_response)
    else:
        home_path = reverse('proxy:list_method')

        uri = MY_ENDPOINT_URL or request.build_absolute_uri(home_path)

        # to generate wsdl content
        response = self._WsgiApplication__handle_wsdl_request(environ, start_response, uri)

        if request.path == home_path and _is_wsdl_request(environ):
            fn = None
        elif 'method_name' in kwargs:
            fn = view_method
        else:
            fn = list_method

        if fn:
            return fn(request, app=self, *args, **kwargs)

    self.set_response(retval, response)

    return retval

Definition of MyServer

class MyServer(ServiceBase):
    @rpc(MyTestMethodRequest, Sign, **method(_returns=MyTestMethodResponse))
    @check_method()
    def TestMethod(ctx, request, signature):
        response = {
            'Data': "test"
        }
        return response

Definitions of MyTestMethodRequest, MyTestMethodResponse:

class MyTestMethodRequest(ComplexModel):
    __namespace__ = MY_NAMESPACE

    MyString = String(encoding=STR_ENCODING)


class MyTestMethodResponse(ComplexModel):
    __namespace__ = MY_NAMESPACE

    Data = String(encoding=STR_ENCODING)

Definition of check_method:

def check_method(error_handler=None):
    def _check_method(func):
        method_name = func.__name__

        def __check_method(ctx, request, signature, *args, **kwargs):
            if hasattr(request, '__dict__'):
                request = request.__dict__

            if hasattr(signature, '__dict__'):
                signature = signature.__dict__

            response = func(ctx, request or {}, signature or {}, *args, **kwargs)

            # setting output protocol
            output_message = generate_out_string(ctx, [response])

            return response

        __check_method.__name__ = method_name
        __check_method.__doc__ = func.__doc__

        return __check_method

    return _check_method

Definition of generate_out_string:

def generate_out_string(ctx, objects):
    ctx.out_protocol = ctx.in_protocol

    return _generate_out_string(ctx, objects)

def _generate_out_string(ctx, objects):
    protocol = ctx.out_protocol

    ctx.out_object = objects

    protocol.serialize(ctx, protocol.RESPONSE)
    protocol.create_out_string(ctx)

    out_string = list(ctx.out_string)

    return out_string[0] if out_string else ''

Note: Most of these definitions have been simplified (I have removed lines which I think are not relevant)

回答1:

Looking at the code you posted, I can't say I understand what good all those additional decorators and modifiers around arguments do.

Removing them should fix all of your problems.

So let:

class MyTestMethodRequest(ComplexModel):
    __namespace__ = MY_NAMESPACE

    MyString = Unicode


class MyTestMethodResponse(ComplexModel):
    __namespace__ = MY_NAMESPACE

    Data = Unicode

Assuming you have the following service:

class MyService(ServiceBase):
    @rpc(MyTestMethodRequest, Sign, _returns=MyTestMethodResponse)
    def TestMethod(ctx, request, signature):
        return MyTestMethodResponse(data="test")

You can have:

app_json = Application([MyService],
              MY_NAMESPACE,
              in_protocol=JsonDocument(validator='soft'),
              out_protocol=JsonDocument())

and

app_soap = Application([MyService],
              MY_NAMESPACE,
              in_protocol=Soap11(validator='lxml'),
              out_protocol=Soap11())

which in turn you can pass to DjangoApplication as usual.:

app_json_dja = csrf_exempt(DjangoApplication(app_json))

app_soap_dja = csrf_exempt(DjangoApplication(app_soap))

which in turn you can mount in Django's url router.

I hope this helps!