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! 🙂