HTTP channel created via zato api does not support all methods (mysql odb)

I have written some code to register services. I’m using Mysql as the ODB.

to reproduce

  • deploy the service below
  • curl localhost:11223/sample?msg=helloget
  • receive ZatoException: Missing input

It works fine if i specify GET or POST as the method. Any ideas or suggested next steps for troubleshooting?


from zato.server.service import Service
from zato.client import AnyServiceInvoker

class Sample(Service):
    class SimpleIO:
        input_required = ('msg',)
        output_required = ('msg',)

    def get_name():
        return 'sample.service'

    def after_add_to_store(logger):
        Sample.add_channel(u'sample.service.http','sample.service', u'/sample')

    def add_channel(channelname, servicename, url, method=None):
        address = 'http://localhost:11223'
        path = '/zato/admin/invoke'
        auth = ('admin.invoke', '51d017d288854aa4b0ce22a79071865c')
        client = AnyServiceInvoker(address, path, auth)

        payload = {}
        payload[u'connection'] = u'channel'
        payload[u'name'] = channelname
        payload[u'cluster_id'] = 1
        payload[u'transport'] = u'plain_http'
        payload[u'url_path'] = url
        payload[u'service'] = servicename
        if method is not None:
            payload[u'method'] = method
        payload[u'data_format'] = u'json'

        response = client.invoke('zato.http-soap.create', payload=payload)

    def handle(self):
        self.response.payload.msg = self.request.input.msg

Hi @gagerenzi,

what if you add this piece of code?

payload['merge_url_params_req'] = True

By the way - you do not need to use get_name and instead of:

class MyService(Service):

    def get_name():
        return 'sample.service'

… it can be expressed succinctly through:

class MyService(Service):
    name = 'sample.service'

Thanks. this seems to do the trick, although I’ll know for sure later today.

On the static get_name, it’s good to know that the method is not really needed. That said, this is my standard code for the service:

    def after_add_to_store(logger):
        from cteutil import ChannelUtils'Configuring {}'.format(TaggedDataSetCreateService.get_name()))
        cu = ChannelUtils(logger)

(ok - hit reply by mistake).

Anyway, I think this works. I’ll have some more validation later today.

In the interim, I’m using the static get_name like this:

    def after_add_to_store(logger):
        from cteutil import ChannelUtils'Configuring {}'.format(TaggedDataSetCreateService.get_name()))
        cu = ChannelUtils(logger)

in the configure_service_file method i do this:

 def configure_service_file(self, filepath):
        source = ''
        self.log('Processing {}'.format(filepath))
        with open(filepath, 'r') as f:
            source =
        p = ast.parse(source)

        classes = [ for node in ast.walk(p) if isinstance(node, ast.ClassDef)]
        for classname in classes:
            if classname != 'SimpleIO':
                classobj = load_from_file(filepath, classname)

                namemethod = getattr(classobj, 'get_name')
                servicename = namemethod()

                    channelmethod = getattr(classobj, 'get_channels')
                    channels = channelmethod()

                    for channel in channels:
                            self.log('Adding channel {}'.format(channel['name']))
                            self.add_channel(channel['name'], servicename, channel['path'], channel['method'])
                            self.log("Exception caught on trying to add channel")
                    self.log("Exception caught on trying to get channels")

The intent is to allow classes to declaratively specify their channels, like this:

    def get_channels():
            {'name': u'cte.taggeddataset.get-by-id', 'method': None, 'path': u'/api/taggeddataset/get-by-id'},

ok - turns out that this isn’t a viable way of self registering services, because zato calls after_add_to_store before the server is available to handle requests, resulting in 504 errors.

i just moved the code from after_add_to_store to a function outside of the class (if __name__ == "__main__":) and then run the python files i’ve just deployed to create their channels.

this works ok. i can include pretty easily in my makefile, and the channel information is immediately obvious, and part of the service implementation itself.

	find ./services -name "*.py" -not -name "__init__*" | xargs -i cp {} $(zatodir)/server1/pickup-dir/
	find ./services -name "*.py" -not -name "__init__*" | xargs -i py {}