Java Client for GAE Channels

Posted by on Jan 31, 2012 in Blog, Featured, Technical Articles | 20 comments

Java Client for GAE Channels

AppEngine Channels

Google AppEngine Channels create a persistent connection between your client and the server running the Channel API Code. Information is passed between the two in real time without the use of polling, even between multiple clients, using the server as a mediator. For instance what if you wanted to create a Java client to use these channels to receive push notifications from a server, or to push data to android devices without the use of sockets and worrying about port numbers. Playing a simple tic-tac-toe game over the internet could get complicated if there wasn’t a way for the other player to easily notify his opponent that a move had taken place. Well… you would have been out of luck. Up until now there wasn’t a way to use Java to do this using Google’s Channel API, it was just available in JavaScript. Luckily there is a solution to this, below is a simple framework that does just that. It allows you to utilize Google’s Channel API in Java.

Framework Overview

Channel Creation

The Client instantiates an instance of the ChannelAPI. The framework then requests a channel to be created with a given key that the user has chosen. The Server creates a channel with the provided key and returns a token which the client then uses to listen to the channel.

Message Sending

The Client sends a message using its channel token. The Server receives the message and propagates it to all the other client channels with the same channel key.

Framework Details

  • Channel Creation
  • Client Side Message Sending
  • Server Side Message Sending

Channel Creation

ChatListener chatListener = new ChatListener();
ChannelAPI channel = new ChannelAPI("http://localhost:8888", "key", chatListener);
channel.open();

There are three main parts to creating a channel:

  • Base URL
  • ChannelService class Implementation
  • Channel Key

The Base URL is the location of where your ChannelServer is stood up. In this example dealing with our ChatServer we are going to use  localhost, so our base URL ends up being “http://localhost:8888″

The ChannelService class Implementation is just simply that. We need to create a class that implements the ChannelService interface class.

package edu.gvsu.cis.masl.chat;
import edu.gvsu.cis.masl.channelAPI.ChannelService;

public class ChatListener implements ChannelService{

In this example the “ChatListener” is our implementation of the ChannelService class. Methods from this class get called by the channel you are about to create. Of all the methods being called the one that is most relevant to our example would be the ‘onMessage’:

/**
* Method gets called when the server sends a message.
 */
@Override
public void onMessage(String message) {
	System.out.println("Server push: " + message);
}

this method gets called when the server or other clients push messages to a channel with the same key that you are using.

The Channel Key is just that, A key. You can use any string you want for it, but keep in mind that channels with the same key receive the same messages from the server. We’ll go into more detail on how the server sends messages to clients, but the highlighted line below shows how messages are sent using a Channel Key on the server side.

public class ChatServlet extends HttpServlet {
  @Override
  public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    String channelKey = req.getParameter("channelKey");
    String message = req.getParameter("message");

    //Send a message based on the 'channelKey' any channel with this key will receive the message
    ChannelService channelService = ChannelServiceFactory.getChannelService();
	channelService.sendMessage(new ChannelMessage(channelKey, message));
  }
}

Every client gets a different channel, but clients can have similar keys.

Once the client has instantiated a ChannelAPI object the class does the rest of the work setting up the channel for you. The constructor calls the method “createChannel(channelKey)” and passes it the Channel Key you defined.

public ChannelAPI(String URL, String channelKey, ChannelService channelService) throws IOException, ClientProtocolException {
    	this.clientId = null;
    	this.BASE_URL = URL;
    	this.channelId = createChannel(channelKey);
    	this.applicationKey = channelKey;

    	if (channelListener != null) {
            this.channelListener = channelService;
        }
    }

The purpose createChannel() is to prompt the server to create a Channel for us and then send back a channel token. The channel token is what we use to communicate with our channel as well as the server and other clients.

private String createChannel(String key) throws IOException, ClientProtocolException{
    	String token = "";
		HttpClient staticClient = new DefaultHttpClient();
		HttpGet httpGet = new HttpGet(BASE_URL + "/?c=" + key);
		try{
			XHR xhr = new XHR(staticClient.execute(httpGet));
			System.out.println(xhr.getResponseText());
			JSONObject json = new JSONObject(xhr.getResponseText());
			token = json.getString("token");
		} catch (JSONException e) {
			System.out.println("Error: Parsing JSON");
		}
    	return token;
    }

Client Side Message Sending

There are two main parts to sending a message from the client to the server:

  • The Message
  • The Location
The message is any string you want to send, but the location is where on the server it should be looking for that message. For example in our ChatExample we use the location ‘/chat’ this means when the server gets a message at that location it knows how to handle it, because we are expecting clients to push messages to us there.
/***
* Sends your message on the open channel
* @param message
*/
public void sendMessage(String message){
try {
		channel.send(message, "/chat");
	} catch (IOException e) {
		System.out.println("Problem Sending the Message");
	}
}

If you wanted to change where the server is looking for the messages you can change the “web.xml”:

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
	<servlet>
		<servlet-name>ChatChannelServlet</servlet-name>
		<servlet-class>edu.gvsu.cis.masl.channel.ChatChannelServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>ChatChannelServlet</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
  <servlet>
    <servlet-name>ChatServlet</servlet-name>
    <servlet-class>edu.gvsu.cis.masl.channel.ChatServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>ChatServlet</servlet-name>
    <url-pattern>/chat</url-pattern>
  </servlet-mapping>
</web-app>

The ‘<url-pattern>’ highlighted above is the line you would change, because the ChatServlet class is the one handling all of the messages from the clients. If you had different classes handling different types of client messages then you would change the appropriate lines in your “web.xml” to match your needs.

Server Side Message Sending

There are three main parts to sending a message on the server-side:

  • ChannelService
  • The ChannelKey
  • The Message
//Send a message based on the 'channelKey' any channel with this key will receive the message
ChannelService channelService = ChannelServiceFactory.getChannelService();
channelService.sendMessage(new ChannelMessage(channelKey, message));

An instance of the ChannelService needs to be instantiated so we are able to pass a message through it. We instantiate it by grabbing an instance of the class from the ChannelServiceFactory. As seen in the highlighted line above. We then call the channelService.sendMessage() function and pass it an instance of a ChannelMessage. Seen below.

//Send a message based on the 'channelKey' any channel with this key will receive the message
ChannelService channelService = ChannelServiceFactory.getChannelService();
channelService.sendMessage(new ChannelMessage(channelKey, message));

The ChannelMessage needs two things to send a message: One being the ‘Channel Key‘ and the other being ‘The Message‘. You can either have the client push its channel key to you, or you can keep track of them in a hash table on your server. In the example code below, the client sends its ‘Channel Key’ to the server, and then the server sends a message back to the client using the Channel Key. The ‘Message’ can be anything from xml to json, as long as it can be converted to a string.
***Updated: Production Issue Has Been Resolved. Thanks in large part to Dean Harding and Lohre.***

The Source Code & Framework can be found on GitHub: https://github.com/gvsumasl/jacc happy coding! :-)

Tom Parker is currently working on his Masters Degree in Computer Science at Grand Valley State University. He completed his undergraduate studies in Computer Science at Michigan State University. He works as a Grad Assistant developing mobile phone applications in the GVSU Mobile Applications and Services Lab. When he’s not in the lab or busy doing homework, you can usually find him at the gym, but he also likes to dirt bike, snowboard, workout and run, as well as work on various programming projects of his own.
  

20 Responses to “Java Client for GAE Channels”

  1. Terje Dahl says:

    This is very impressive. I look forwards to trying it out in my Java applet.
    But to what degree will you be able to keep this package syncronized with any changes Google makes to the implementation?
    As I understand it, Google only garantees the stability of the API to its JS – not of any parts of the internal implementation of it server-JS communication.
    Does this in other words mean that you are dependant on constantly monitoring the Google’s setup for changes, and alway have to reverse-engineer and update to make the Java package work again?

  2. Tom Parker says:

    This simple Framework just mimics how the JS communicates with Google’s Channel API. As long as the way Google implemented this interaction doesn’t change, this Framework should work. In that sense it would be dependent on monitoring updates dealing with Channel interaction just to be on the safe side. I’m glad you like it and I hope it works out for you! :-)

  3. Awesome implementation. Would you have a problem if I fork it and maven-ize it? :)

  4. Tom Parker says:

    Not at all, go for it :-)

  5. Cool deal — Mavenized fork is at https://github.com/hatboyzero/jacc

    As usual with most maven projects, a simple ‘mvn install’ should do the trick…

  6. This is great! I was looking for a java implementation for the client for months now. The code is also well organized, short and easy to understand, especially with the examples. I’ll probably write an objective-C version of it in a few months…

    I have a question regarding the GAE localhost server implementation of the Channel API. I ran your examples and everything seemed to work fine. However, a closer look showed that the server returns a 200 immediately with an empty message in the polling function, effectively making this a short-polling mechanism. This isn’t how it’s supposed to work, is it?

  7. Tom Parker says:

    I’m glad you like it! You are right it in saying that it is polling data from the channel. The first initial 200 response is directed to the ‘onOpen()’ function in the ChannelService class that you implement. This simple framework was just an easy way for Java clients to interact with Google’s Channel API :-)

  8. Thanks for your quick reply! The first 200 response from the connect(sendGet(..)) I understand. What I’m trying to verify is the expected behavior of the sendGet in the poll() function.

    I would think it should return only when there is a message waiting for this client, making the channel mechanism long-polling based. And yet, my GAE localhost returns a 200 immediately to the sendGet in poll(), with an empty message, which in turn calls forwardMessage, which then calls poll again, repeating the cycle every TIMEOUT_MS milliseconds.

    It looks to me like you did things the way I understand, but something is strange with the localhost GAE response to the channel service.

    I’m I missing something?

    Thanks again, for your implementation and help… :)

  9. Tom Parker says:

    If I had control over the way Google’s API Handles its Channels poll requests I would certainly make it a long poll function, but what I’ve noticed is that as soon as I make a request to grab data from the Channel it returns what you have also noticed, an empty message. That is why there is a TIMOUT_MS implemented in the function, that way you can variably control how often you check for messages. You definitely understood correctly. I hope that answers your question :-)

  10. Hi, I’m testing this nice frame work in an application and local GAE server. The server is example from google. My TokenServlet accepts POST and I modified the ChannelAPI and now I get token ok, but right after that, inside open() of ChannelAPI and exactly on connect(sendGet(getUrl(“connect”))); I receive this error: “HTTP method GET is not supported by this URL”. As long as I understand from the code, the ‘connect’ command is going to /_ah/channel and is google special command, not about my servlet used. What could be the reason? Any suggestion please?

  11. Inside your “appengine-web.xml” on your server make sure you have Channels Enabled. Hope this helps :-)

    This is an example “appengine-web.xml”:
    <?xml version="1.0" encoding="utf-8"?>
    <appengine-web-app xmlns="http://appengine.google.com/ns/1.0"&gt;
    <application>maslkiosk</application>
    <version>1</version>
    <threadsafe>true</threadsafe>

    <!– Configure java.util.logging –>
    <system-properties>
    <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
    </system-properties>

    <–These 3 Lines Enable App Engine Channels –/>
    <inbound-services>
    <service>channel_presence</service>
    </inbound-services>

    </appengine-web-app>

  12. Hi, Thank you very much, I’ve passed the first stages and got the framework running on dev server. After this success, I uploaded to appspot, but when connecting (I’m running it in Android and on debug I see messages) I get “Error, Not Found”. Tracing the error reaches to the line “connect(sendGet(getUrl(“connect”)));” and I guess that as I am running not on dev appserver, this “/dev?command=…” must be changed. I successfully receive token from appspot but exactly on this line I hit to error. I can’t find to what I have to change. In comments of the code of frame work also can’t find what to do for a production server. Please help. Thank you in advance.

  13. Abdallah says:

    Hi! Please could someone explain me how to add the framework to my project. I’m noob with eclipse. Thanks.

  14. Tom Parker says:

    I recently noticed that Jacc no longer works on App Engine. The command Jacc uses to poll info from the channels no longer works on the Production server (App Engine), but remains to work in Development (localhost). I’m trying to find a way to solve this issue, any insight would be a great help :-)

    **Disclaimer: Jacc only works on Development…hoping to remedy this problem soon**

  15. Terje Dahl says:

    Have you been able to remedy the issue you mentioned on March 14?

    Does this issue mean that the API basically doesn’t work? Or does it just affect a subset of the API’s functionality?

  16. Tom Parker says:

    The Production Issue has been resolved as of today! Jacc now works on both Development and Production Servers. Thanks in large part to Dean Harding and Lohre. :-)

  17. HIRANO Satoshi says:

    Hi,

    Seems great! But what I need is an iOS version. Do you have a plan to write it?

  18. Tom Kaitchuck says:

    It should probably be noted that the URL:
    http://talkgadget.google.com/talkgadget/
    Is a constant and does not need to be changed or overridden. To change the server one can pass in a different URL or change the BASE_URL.

  19. Hi, and thanks for your work!. I am trying to develop and app with this library but I have some -maybe- silly questions.
    When the client send messages to the server it makes a post to the server but i dont see any token related code in the client so I assume it’s internally gestioned. But, Is the client using a channel or simply makes a post to the server?
    If the token is used insternally its automatically renewed each two hours?.

    Thank you very much and hope I dont bother you much.

  20. Hi. Thanks for your great work.

    I need your help. It works fine to join channels on my local dev server with previously created tokens, but it does not work to join the channel on app server.

    I just used ChannelAPI.joinChannel() and ChannelAPI.open().

    Or.. anybody has successful experience to re-join channels on appspot server with his library?

Leave a Reply