App Engine Python Modules and channel service
Asked Answered
C

3

10

I am using App Engine Modules in my python project. (https://developers.google.com/appengine/docs/python/modules/#Python_Background_threads)

I am also using channels in m project: https://developers.google.com/appengine/docs/python/channel/

I want to direct the connected/disconnected post messages ('/_ah/channel/connected/', '/_ah/channel/disconnected/') to my api module. Right now I can't get them to show up in any module (default or api)

app.yaml

    api_version: 1
    application: integrate
    version: 1-0-0
    runtime: python27
    threadsafe: true

    builtins:
      - deferred: on

    libraries:
      - name: pycrypto
      version: "2.6"

    handlers:
      - url: /favicon\.ico
      static_files: static/favicon.ico
      upload: static/favicon\.ico

      - url: /admin/.+
      script: src.default.main.app
      login: admin

      - url: /.*
      script: src.default.main.app

api.yaml

    api_version: 1
    application: integrate
    module: api
    version: 1-0-0
    runtime: python27
    threadsafe: true

    inbound_services:
      - channel_presence

    builtins:
      - deferred: on

    libraries:
      - name: pycrypto
      version: "2.6"

    handlers:
      - url: /admin/.+
      script: src.api.main.app
      login: admin

      - url: /.*
      script: src.api.main.app

dispatch.yaml

    application: integrate

    dispatch:
       - url: "*/_ah/channel/*"
       module: api

Note: Just to be clear this all works in dev mode locally.

api.main.app

    app = webapp2.WSGIApplication(debug=True)
    _routes = [
        :
        ChannelDisconnectedHandler.mapping(),
        ChannelConnectHandler.mapping()
    ]

    for r in self._routes:
        app.router.add(r)

ChannelDisconnectHandler

    CHANNEL_DISCONNECTED_URL_PATTERN = '/_ah/channel/disconnected/'


    class ChannelDisconnectedHandler(RequestHandler):

        @classmethod
        def mapping(cls):
            return CHANNEL_DISCONNECTED_URL_PATTERN, cls

        def post(self):
            """
            Channel Presence handler. Will be called when a client disconnects.
            """
            channel_id = self.request.get('from')
            logging.info("Channel Disconnect. Id: %s" % channel_id)

ChannelConnectHandler

    CHANNEL_CONNECT_URL_PATTERN = '/_ah/channel/connected/'

    class ChannelConnectHandler(RequestHandler):

        @classmethod
        def mapping(cls):
            return CHANNEL_CONNECT_URL_PATTERN, cls

        def post(self):
            """
            Channel Presence handler. Will be called when a client connects.
            """
            channel_id = self.request.get('from')
            logging.info("Channel Connect. Id: %s" % channel_id)

So my client (written in javascript) posts to my api module and opens a channel.

    var open_channel = function(tokenResponse) {
        console.log("Open Channel. token Response: " + tokenResponse)
        token = tokenResponse.token;
        var channel = new goog.appengine.Channel(token);
        if (socket != null) {
            socket.close();
        }
        socket = channel.open();
        socket.onopen = onOpened;
        socket.onmessage = onMessage;
        socket.onerror = onError;
        socket.onclose = onClose;
    };

    onOpened = function() {
        console.info("Channel API Connection is open.");
    };

    onError = function(e) {
        console.info("CHANNEL Error. Code: " + e.code + ", Description: " + e.description);
    };

    onClose = function() {
        console.info("Close Channel");
    };

    onMessage = function(msg) {
       console.info("Message Received: " + msg + ", Data: " + msg.data);
    };

This callback function is reached with a valid token. I create the socket successfully and complete this function as expected. On my local system the onOpened function is then called and I receive the messages from the server. In production onOpened is never called and I never receive any messages. The /_ah/channel/connected/ is also never called.

Is the Channel service not supported with modules? Any thoughts as to what I am missing?

Czarism answered 3/10, 2013 at 15:23 Comment(0)
M
6

According to Google Enterprise Support (modified slightly from their raw answer):

  1. channel_presence inbound service must be enabled in app.yaml.

    inbound_services:
    - channel_presence
    

    Enabling this inbound service in module’s yaml file (e.g., api.yaml in this question) won’t enable this service.

  2. URL paths starting with */_ah are not dispatchable paths and cannot be routed by dispatch.yaml. Therefore, channel_presence URL paths handlers have to be described in app.yaml.

    handlers:
    - url: /_ah/channel/connected/
      script: mymodule.application
    
Manure answered 29/10, 2014 at 4:40 Comment(3)
What would be the correct line for golang here: script: mymodule.application?Goddess
mattes- I think that line is language agnostic, but Emil- I'd also appreciate more explanation of how that worksArroyo
For more information on app.yaml, see the language-specific documentation it. For Go, the relevant version is here cloud.google.com/appengine/docs/go/config/appconfigManure
D
0

You have to declare a hadler routing for connect and disconects URLs.

Handler routing in main.py:

application = webapp2.WSGIApplication([
    ...
    # Define a URL routing for /_ah/channel/connected/
    webapp2.Route(r'/_ah/channel/connected/',
                  handler=ChannelConnectedHandler,
                  name='channel_connected')

], debug=True, config=webapp2_config)


# Implement class handler of /_ah/channel/connected/
class ChannelConnectedHandler(webapp2.RequestHandler):
    def post(self):
        client_id = self.request.get('from')
        logging.info('client %s has connected!' % client_id)
        ...
Diapason answered 3/10, 2013 at 15:42 Comment(1)
Thanks for answering but I am afraid that is not the issue here. I did not post the code here but I do have the routes added. It wouldn't work in dev mode if I hadn't.Czarism
D
0

I've bumped into issues with using the Channel API in modules as well and I tried to work around them using a similar trick as Emil mentions by redirecting the requests to the modules.

It was a slightly more complicated setup though because I actually had 3 modules, where 2 of them used the Channel API and one was the 'frontend'. Something like this:

  • module frontend (default)
  • module serviceA (using channel api 1)
  • module serviceB (using channel api 2)

I wanted to be able to listen to "notifications" from the two separate services in the frontend.

And the way I managed to work around that (in dev) was to add redirects to the frontend which read the tokens which I had prefixed on each service and redirect to each service.

"Great, It works!" I thought but then when I tried to deploy to app engine I realized there's more to it there as the talkgadget endpoints used internally by the Channel API seemed to expect a certain source app and thus did not allow cross domain communication.

So I ended up using multiple projects instead of modules and by putting an HTML iframe "postMessage bridge" to get around the cross domain issues. And gladly it works really well and as a side effect I get twice as many "free" channels to use.

I found an issue related to this here which may be interesting for you to track: https://code.google.com/p/googleappengine/issues/detail?id=10293

Dobson answered 13/11, 2014 at 9:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.