Search code examples
google-app-enginedeploymentchannel-api

App engine channel deploy


I created an application that has ~50 users. I am trying to use channel API but I've run into a problem while testing with message send. I am saving the token into the database so i can use the same token if an user opens multiple tabs with the same interface and i have a servlet that resets my token when it expires.

It works fine until I redeploy my application or change the version of my app. I stop receiving messages. If I try to open a channel with the old app version token it doesn't throw an error or anything, it opens it but I still don't receive messages on that channel.
If I reset my token it works OK again.

Does anyone know of a solution to this bug, or has anyone had it before? I deploy often while people are working so I can't ignore it.

My best guess is that ChannelServiceFactory.getChannelService() returns a different instance of ChannelService so when I call channelService.sendMessage("id","message"); it sends it to a different channel.


Solution

  • I can't explain why stored tokens wouldn't work on re-deploying your app (they should), but I can explain why they don't work when you change versions. Briefly, tokens are specific to an app version.

    First, the reason for this: we want to ensure that applications that send different data or change message formats or whatever in different versions don't send messages across version boundaries. In the same way that you don't want your javascript bundle from v1 rendering against servlets on v2, you wouldn't want v1 your javascript message handlers receiving messages from v2 servlets (or vice versa).

    So, to hopefully make it clear what's going on:

    A channel is identified by a combination of your appid, your app version, and the clientid that you provide when you call createChannel or sendMessage. The implementation of the Channel API doesn't store any mapping of appid/clientid -> token. To greatly simplify, you can think of createChannel as doing something like this:

    public String createChannel(clientid) {
      // obviously we don't really just append strings to each other for actual implementation.
      return encryptStringSomehow(clientid + globalAppInfo.version + globalAppInfo.appid);
    }
    

    and sendMessage is like this:

    public void sendMessage(clientid, message) {
      // identify the JID used for this channel.
      JID xmppJid = new JID(mutateString(clientid + globalAppInfo.version + globalAppInfo.appid),
                            CHANNEL_XMPP_DOMAIN); // some domain used for channel messages
      // send the <message> stanza to that jid with the application message as the body
      xmppService.sendMessage(xmppJid, encodeSomehow(message));
    }
    

    and on the client side, the servlet responsible for the channel decrypts the token and binds to the endpoint identified by a JID created by the same method as the sendMessage function.

    The upshot is that tokens are only valid for messages sent from the same version of the app that created them.