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

#1

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

0 Likes

#2

Hi Dave,

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

···

Am 06.01.2015 um 20:10 schrieb Dave Barndt:

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

--
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.

0 Likes

#3

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

0 Likes

#4

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

···

On Wednesday, January 7, 2015 12:01:35 PM UTC-5, Tobias Oberstein wrote:

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

0 Likes

#5

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

0 Likes

#6

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

0 Likes

#7

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.

···

On Friday, January 9, 2015 at 10:43:55 PM UTC-5, Dave Barndt wrote:

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

0 Likes

#8

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

···

Am Samstag, 10. Januar 2015 04:43:55 UTC+1 schrieb Dave Barndt:

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

0 Likes

#9

Hi Tobias,

Yes, I will do this today.

Thanks,

Dave

···

On Saturday, January 10, 2015 at 4:04:47 PM UTC-5, Tobias Oberstein wrote:

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

0 Likes

#10

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

···

On Monday, January 12, 2015 at 11:44:35 AM UTC-5, Dave Barndt wrote:

Hi Tobias,

Yes, I will do this today.

Thanks,

Dave

On Saturday, January 10, 2015 at 4:04:47 PM UTC-5, Tobias Oberstein wrote:

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

0 Likes

#11

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

···

On Monday, January 12, 2015 at 5:46:44 PM UTC-5, Dave Barndt wrote:

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

On Monday, January 12, 2015 at 11:44:35 AM UTC-5, Dave Barndt wrote:

Hi Tobias,

Yes, I will do this today.

Thanks,

Dave

On Saturday, January 10, 2015 at 4:04:47 PM UTC-5, Tobias Oberstein wrote:

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

0 Likes