crossbario / crossbar

Crossbar.io - WAMP application router
https://crossbar.io/
Other
2.05k stars 274 forks source link

WAMP/Klein example (2014) server_web.py trying to update to work in 2022 #2043

Closed antonymott closed 1 year ago

antonymott commented 1 year ago

If someone can help me get it working, I'm happy to do a pull request and update the klein example, which is very old and doesn't work with any current versions of crossbar or autobahn/python.

To handle Klein request/response asynchronously, the example code (2014) needs updating for crossbar 21.3.1 and newer, as the example code uses the keyword "standalone" which errors out as it was for way back when WAMP router shipped with autobahn/python (now separated).

The problem is the last line of https://github.com/crossbario/autobahn-python/blob/master/examples/twisted/wamp/app/klein/example1/server_web.py

wampapp.run("ws://127.0.0.1:9000", "realm1", standalone=False)

What I tried so far:

I included the squaring rRPC function in one of my existing components. That registered the squaring function fine, so no need for server_wamp.py as used in the example.

It also meant I could remove the problem line:

wampapp.run("ws://127.0.0.1:9000", "realm1", standalone=False)

from server_web.py which now is:

from klein import Klein
from twisted.internet import reactor

from twisted.internet.defer import inlineCallbacks

from autobahn.wamp import register

from autobahn.twisted.wamp import ApplicationSession
from autobahn.wamp.types import PublishOptions

webapp = Klein()

class ClientSession(ApplicationSession):

def onConnect(self):
[...]

def onChallenge(self, challenge):
[...]

    @inlineCallbacks
    def onJoin(self, details):

        ## REGISTER the squaring rRPC for the Klein app to call remotely
        ##
        def square(x):
            print(f'square() called with {x}')
            return x * x

        try:
            reg = yield self.register(square, 'com.test.square')
            print(f'procedure square() registered')
        except Exception as e:
            print(f'could not register procedure: {e}')

        try:
            res = yield self.call('com.test.square', 3)
            print(f'call result: {res}')
## works as expected: produces '9' when first running server_web.py

        except Exception as e:
            print(f'call error: {e}')

        @webapp.route('/square/submit', methods=['POST'])
        def square_submit(request):
            print(f'com.test.square called with "request": {request}')
            x = int(request.args.get('x', [0])[0])
            res = yield self.call('com.test.square', x)
            print(f'res: {res}')
            # returnValue("{} squared is {}".format(x, res))

    def onLeave(self, details):
        self.disconnect()

    def onDisconnect(self):
        reactor.stop()

if __name__ == '__main__':
    from autobahn.twisted.wamp import ApplicationRunner

    from twisted.web.server import Site
    reactor.listenTCP(8081, Site(webapp.resource()))

    runner = ApplicationRunner(
        url='ws://127.0.0.1:9000/ws',
        realm='realm1'
    )
    runner.run(ClientSession)

I used test_web.html:

<!DOCTYPE html>
<html>
   <body>
      <form action="http://localhost:8081/square/submit" method="post">
         <p>
            Square this <input type="number" name="x" value="23" />
         </p>
         <p>
            <input type="submit" />
         </p>
      </form>
   </body>
</html>

The above tests the squaring function, so I know that is registered successfully, and server_web.py runs fine until I try to click the submit button in test_web.html, when the following traceback: Unhandled Error writing response [...] builtins.TypeError: object of type 'generator' has no len()

oberstet commented 1 year ago

yes, indeed, this example stems from a time when autobahn and crossbar still were one package. this no longer works, as both are separated now.

what would be needed is a crossbar web service for klein .. probably. we already have a lot of options in crossbar rgd webservices, and also mapping rest and wamp ... so I would first try to understand what you actually want

eg there is a powerful feature

https://github.com/crossbario/crossbar-examples/tree/master/webservices/wap

this allows you to provide REST endpoints, which will forward to WAMP RPC calls, with the result rendered through Jinja2 templates into HTML returned in the REST endpoint, eg

https://github.com/crossbario/crossbar-examples/blob/3dbda8d44f4c8ac261714a97fd94f6d6462743c1/webservices/wap/.crossbar/config.json#L68

antonymott commented 1 year ago

Thanks, I need simply to have Klein (or something else but Klein seems very lightweight and made with Twisted) listen for a POST (twilio app) at a url endpoint, and have python call an rRPC that's already registered and return it to the browser.

My modified server_web.py above works mostly, including having Klein listen and the @webapp.route() part fires. And within that def, this line doesn't cause an error: res = yield self.call('com.test.square', x) but trying to print or use the res results in: builtins.TypeError: object of type 'generator' has no len()

oberstet commented 1 year ago

Given that you already have your backend WAMP procedure you can achieve this using WAP in Crossbar.io with node configuration only

{
    "path": "/square/<int:x>",
    "method": "POST",
    "call": "com.test.square",
    "render": "square_result.html"
}

since the method is POST, the com.test.square will - in addition to x argument automatically parsed from URL - receive two more arguments:

antonymott commented 1 year ago

I'll check that out, thanks!

antonymott commented 1 year ago

Putting the routes in config.json worked great, thanks! And way faster than running a separate listening server (the Klein way).

I built the webservices/wap example you linked to...very useful!

If I need to respond to HTTP requests with just a string rather than rendered html, is there another dict key I should use (instead of "render")?

"sandbox": true,
              "routes": [
                {
                  "path": "/greeting/<name>",
                  "method": "GET",
                  "call": "com.test.greeting",
                  "render": "greeting.html" <== is there a different key for responding with a string rather than html?
                }
              ]