Is there a way to nicely "chain" WAMP RPC calls (and allow them to potentially be on different realms)?

191 views
Skip to first unread message

Dave Barndt

unread,
Jan 6, 2015, 2:10:56 PM1/6/15
to autob...@googlegroups.com
Hi,

I'm struggling with the right/best way to do something.

I have an RPC "callee" that I would like to have turn around and become a "caller" of a different RPC (potentially on a different WAMP realm). My question is simply, is this doable, and if so, what's the best way to do it?

When an RPC is registered, the ApplicationSession context isn't passed to it, so it can't turn around and just call self.call(...) on another RPC. I had been toying with the idea of creating a whole new ApplicationRunner/Session for the first RPC callee to call, but I can't figure out how to get the final result of such a session back to the original RPC routine.

Below is the basic design of what I have. I don't like it in it's current form; it definitely feels ugly/kludgy/wrong. But the *principle* behind what I want to do - call one RPC from another, and perhaps on a different realm - seems OK. Or isn't it?

Thanks for any design guidance on this one. I'm really enjoying WAMP and am happy with how stable it's proven to be so far.

Dave

-------------------------------------------------
(file: main.py; component is a native worker started by Crossbar, on realm = "realm1")

from autobahn.twisted.wamp import ApplicationSession
from twisted.internet.defer import inlineCallbacks

import myModule

class MainSession(ApplicationSession):

    @inlineCallbacks
    def onJoin(self, details):
        yield self.register(myModule.myFunc, "com.myapp.rpc.myFunc");

-------------------------------------------------
(file: myModule.py; note that this ApplicationSession class is declared *inside* the RPC body; the idea was to run a new, second session just for the duration of the "myFunc" RPC call; I know this is ugly and there are scope issues here that probably can't be resolved, which is why I finally came to post here!)

from autobahn.twisted.wamp import ApplicationRunner, ApplicationSession
from twisted.internet.defer import inlineCallbacks

def myFunc(myData):

    class MyModuleSession(ApplicationSession):

        result = None

        @inlineCallbacks
        def onJoin(self, details):
            MyModuleSession.result = yield self.call("com.myapp.rpc.anotherFunc");
            self.leave()

        def onDisconnect(self):
            reactor.stop()

    runner = ApplicationRunner("wss://127.0.0.1/ws", "realm2")
    runner.run(MyModuleSession)

    return MyModuleSession.result

Tobias Oberstein

unread,
Jan 7, 2015, 4:55:01 AM1/7/15
to autob...@googlegroups.com
Hi Dave,

Am 06.01.2015 um 20:10 schrieb Dave Barndt:
> Hi,
>
> I'm struggling with the right/best way to do something.
>
> I have an RPC "callee" that I would like to have turn around and become
> a "caller" of a different RPC (potentially on a different WAMP realm).
> My question is simply, is this doable, and if so, what's the best way to
> do it?

Sure. Easy:

class MyComponent1(ApplicationSession):

@inlineCallbacks
def onJoin(self, details):
yield self.register(self.add2, u'com.myapp.add2')

def add2(self, a, b):
return a + b


class MyComponent2(ApplicationSession):

@inlineCallbacks
def onJoin(self, details):
yield self.register(self.add2squared, u'com.myapp.add2squared')

@inlineCallbacks
def add2squared(self, a, b):
sum = yield self.call(u'com.myapp.add2', a, b)
returnValue(sum * sum)

Cheers,
/Tobias
> --
> You received this message because you are subscribed to the Google
> Groups "Autobahn" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to autobahnws+...@googlegroups.com
> <mailto:autobahnws+...@googlegroups.com>.
> To post to this group, send email to autob...@googlegroups.com
> <mailto:autob...@googlegroups.com>.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/autobahnws/a063223f-b882-4f23-a403-76b69ecde65b%40googlegroups.com
> <https://groups.google.com/d/msgid/autobahnws/a063223f-b882-4f23-a403-76b69ecde65b%40googlegroups.com?utm_medium=email&utm_source=footer>.
> For more options, visit https://groups.google.com/d/optout.

Tobias Oberstein

unread,
Jan 7, 2015, 12:01:35 PM1/7/15
to autob...@googlegroups.com
> current form; it definitely feels ugly/kludgy/wrong. But the *principle*
> behind what I want to do - call one RPC from another, and perhaps on a
> different realm - seems OK. Or isn't it?

Forgot one thing, Dave: if you want to call out to a procedure from
within a procedure being called, that's as easy as I wrote in my first
reply - as long as it's on the _same_ realm.

Calling out to a different realm requires a 2nd WAMP session.

/Tobias

Dave Barndt

unread,
Jan 7, 2015, 1:52:48 PM1/7/15
to autob...@googlegroups.com
Hi Tobias,

Thanks for replying - I do understand the solution is straightforward if both RPCs can be on the same realm, and in the same module - but if not, those were my two questions...

1) I was wondering what the best way would be to have the first RPC call create a second WAMP session on a different realm in order to make the second RPC call in the second realm. "Embedding" the second ApplicationSession class in the RPC call itself, as I outllined in my first post, seems to have variable scoping issues, so I'm a little stumped as to what design pattern could actually work.

2) If the two RPC calls wind up having to be in the same realm, but the first RPC call resides in a different module than than the module in which it was registered, how could the RPC get a "handle" to the ApplicationSession to be able to invoke the second RPC "call"? For example, given module1 contains the ApplicationSession, and registers the RPC module2.myFunc, how could module2.myFunc invoke the module1 ApplicationSession's "call" method to call the second RPC?  The following has a circular import which I don't think will work:

module1:

import module2
thisSession = None
class MainSession(ApplicationSession):
    @inlineCallbacks
    onJoin(self, details):
        global thisSession;
        thisSession = self
        yield self.register(module2.myProc. "com.myapp.myProc")

module2:

import module1
@inlineCallbacks
def myProc:
    yield module1.thisSession.call("com.myapp.myProc2") # registered elsewhere


Sorry if I'm missing some obvious design pattern/principle on either question - I've used Python for a while but so far am left scratching my head on both of these questions.

Thanks,
Dave

Tobias Oberstein

unread,
Jan 9, 2015, 2:00:29 AM1/9/15
to autob...@googlegroups.com
Hi Dave,

> 1) I was wondering what the best way would be to have the first RPC call
> create a second WAMP session on a different realm in order to make the
> second RPC call in the second realm. "Embedding" the second
> ApplicationSession class in the RPC call itself, as I outllined in my
> first post, seems to have variable scoping issues, so I'm a little
> stumped as to what design pattern could actually work.

Ok, this is the not totally straightforward variant;)

You can create a 2nd WAMP session to a different realm within the onJoin
handler of the 1st.

And then use that 2nd session from calls into the 1st.

So you have problems getting this to work? If so, and you really need
this, I can create a complete example (modulo my work queue).

>
> 2) If the two RPC calls wind up having to be in the same realm, but the
> first RPC call resides in a different module than than the module in
> which it was registered, how could the RPC get a "handle" to the
> ApplicationSession to be able to invoke the second RPC "call"? For
> example, given module1 contains the ApplicationSession, and registers
> the RPC module2.myFunc, how could module2.myFunc invoke the module1
> ApplicationSession's "call" method to call the second RPC? The
> following has a circular import which I don't think will work:

Nope. You don't need a handle to the 2nd module to call it from the 1st.

The call travels via the router.

Hence, you just self.call() out to the 2nd component as you would do
with calling anthing anywhere.

Actually, the 1st component does _not_ know that the 2nd one is actually
running in the same process. Which is good, because of decoupling and
being agnostic. What if you decide to run the 2nd component on a
different host from the 1st? Totally transparent. No app code change at all.

Again, if that is what you actually need and can't get to work, I can
create an example following

https://groups.google.com/d/msg/autobahnws/a0IKaFjz-2A/qXq6i6yNXMQJ

Cheers,
/Tobias

Dave Barndt

unread,
Jan 9, 2015, 10:43:55 PM1/9/15
to autob...@googlegroups.com
Hi Tobias,

I've continued to struggle with this all day...


On Friday, January 9, 2015 at 2:00:29 AM UTC-5, Tobias Oberstein wrote:
You can create a 2nd WAMP session to a different realm within the onJoin
handler of the 1st.

And then use that 2nd session from calls into the 1st.

I follow what you're saying, but how should I be creating the 2nd session? Below is the basic structure of what I've done, which is admittedly a little different. I'd like to use different modules because I want to try to keep things logically separated.

module1.py:
----------------
import module2
class MyClassSession1(ApplicationSession):
    # This class/session is a native Crossbar worker configured in config.json.
    # It runs on realm "realm1" and registers the "first" RPC.
    def onJoin(self, details):
        yield self.register(
                    "com.myapp.realm1.myproc",
                    module2.myProc2,
                    RegisterOptions(details_arg="details"))

module2.py
----------------
import module3
def myProc2(arg, details):
    # This is the "first" RPC implementation.
    return module3.MyClass3().myProc3(arg)

module3.py
----------------
results = {}

class MyClassSession3(ApplicationSession):
    # This class/session is run via the ApplicationRunner below.
    # It runs on realm "realm2" and calls the "second" RPC.
    def onJoin(self, details):
        results[self.config.extra["result_id"] = \
            yield self.call(
                         "com.myapp.realm2.myproc",
                         self.config.extra["arg"])
        self.leave()

class MyClass3(object):
    def myProc3(self, arg):
        result_id = uuid.uuid4().hex
        runner = ApplicationRunner(
                          "wss://127.0.0.1/ws", # same URL as original caller (browser) used for "realm1"
                          "realm2",
                          extra={"arg": arg,
                                     "result_id": result_id})
        runner.run(MyClassSession3)
        result = copy.deepcopy(results[result_id])
        del results[result_id]
        return result

module4.py
----------------
def myProc4(arg):
    # This is the second RPC implementation.
    pass

class MyClassSession4(ApplicationSession):
    # This class/session is a native Crossbar worker configured in config.json.
    # It runs on realm "realm2" and registers the "second" RPC.
    def onJoin(self, details):
        yield self.register(
                    "com.myapp.realm2.myproc",
                    myProc4)

---------------------------------------------------------------------
Currently, when all of the above runs, the two native workers (one per unique realm, configured in config.json) appear to start OK via Crossbar. When I make a the call to the first RPC from a browser, it appears to come into the first session OK, get routed OK to module2.myProc2, which then instantiates module3.MyClass3 and invokes its myProc3 method. The ApplicationRunner appears to get created OK, but when its "run" method is invoked, I see the following in the Crossbar.io log:

2015-01-09 17:53:52-0500 [Router      28838] Unable to write to plugin cache /usr/local/lib/python2.7/dist-packages/twisted/plugins/dropin.cache: error number 13
2015-01-09 17:53:53-0500 [Router      28838] Unable to write to plugin cache /usr/local/lib/python2.7/dist-packages/twisted/plugins/dropin.cache: error number 13
2015-01-09 17:53:53-0500 [Router      28838] Starting factory <autobahn.twisted.websocket.WampWebSocketClientFactory instance at 0x42304b0c>
2015-01-09 17:53:53-0500 [Router      28838] Stopping factory <autobahn.twisted.websocket.WampWebSocketClientFactory instance at 0x42304b0c>

Meanwhile in the browser, I get an exception back with a "wamp.error.runtime_error" but no apparent additional descriptive text.

I know there must be some things I'm doing wrong, and I'm absolutely willing to do "best practices", but I'd really like to keep things logically separate in different modules if possible.

Thanks for your patience and any help/guidance,
Dave

Dave Barndt

unread,
Jan 9, 2015, 11:08:03 PM1/9/15
to autob...@googlegroups.com
Sorry, I should have been more complete on one detail: myProc4 in module4.py actually does return a value (as opposed to just "pass"), so I doubt that's the source of any error.

Tobias Oberstein

unread,
Jan 10, 2015, 4:04:47 PM1/10/15
to autob...@googlegroups.com
Hi Dave,

the specific error you get is exotic .. maybe this helps: http://twistedmatrix.com/pipermail/twisted-python/2007-July/015726.html

"no write access to Twisted plugin cache directory"

To be honest, I have no idea why you run into this.

Can you somewhere upload your _complete_ example, including all code and Crossbar.io config? E.g. on some scratch GitHub repo .. I give it a shot and try to track down whats happening.

Cheers,
/Tobias

Dave Barndt

unread,
Jan 12, 2015, 11:44:35 AM1/12/15
to autob...@googlegroups.com
Hi Tobias,

Yes, I will do this today.

Thanks,
Dave

Dave Barndt

unread,
Jan 12, 2015, 5:46:44 PM1/12/15
to autob...@googlegroups.com
Hi Tobias,

I've put some pared-down code that still exhibits the problem on GitHub at: https://github.com/dbarndt/crossbar-chained-rpcs.git

It's a public repo, but please let me know if you can't clone it for some reason, as it's my first GitHub repo. :^)

Thanks,
Dave

Dave Barndt

unread,
Feb 9, 2015, 9:26:19 AM2/9/15
to autob...@googlegroups.com
Hi Tobias,

I know you're busy; just wondering if you had any insights or ideas on the best ways to chain RPCs from one session/realm to another...

Thanks,
Dave
Reply all
Reply to author
Forward
0 new messages