App Engine Python Modules and channel service

2019-04-05 09:01发布

问题:

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?

回答1:

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
    


回答2:

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


回答3:

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