| page.title=GCM Cloud Connection Server (XMPP) |
| @jd:body |
| |
| <div id="qv-wrapper"> |
| <div id="qv"> |
| |
| |
| <h2>In this document</h2> |
| |
| <ol class="toc"> |
| <li><a href="#usage">How to Use CCS</a> |
| <ol class="toc"> |
| <li><a href="#auth">Authentication</a></li> |
| </ol> |
| </li> |
| <li><a href="#format">Message Format</a> |
| <ol class="toc"> |
| <li><a href="#request">Request format</a></li> |
| <li><a href="#response">Response format</a></li> |
| </ol> |
| </li> |
| <li><a href="#upstream">Upstream Messages</a> </li> |
| <li><a href="#flow">Flow Control</a> </li> |
| <li><a href="#implement">Implementing an XMPP-based App Server</a> |
| <ol class="toc"> |
| <li><a href="#smack">Java sample using the Smack library</a></li> |
| <li><a href="#python">Python sample</a></li> |
| </ol> |
| </li> |
| </ol> |
| |
| <h2>See Also</h2> |
| |
| <ol class="toc"> |
| <li><a href="{@docRoot}google/gcm/http.html">HTTP</a></li> |
| <li><a href="{@docRoot}google/gcm/gs.html">Getting Started</a></li> |
| <li><a href="{@docRoot}google/gcm/server.html">Implementing GCM Server</a></li> |
| <li><a href="{@docRoot}google/gcm/client.html">Implementing GCM Client</a></li> |
| <li><a href="https://services.google.com/fb/forms/gcm/" class="external-link" |
| target="_android">CCS and User Notifications Signup Form</a></li> |
| </ol> |
| |
| </div> |
| </div> |
| |
| <p class="note"><strong>Note:</strong> To try out this feature, sign up using |
| <a href="https://services.google.com/fb/forms/gcm/">this form</a>.</p> |
| |
| <p>The GCM Cloud Connection Server (CCS) is a connection server based on XMPP. |
| CCS allows 3rd-party app servers (which you're |
| responsible for implementing) to communicate |
| with Android devices by establishing a persistent TCP connection with Google |
| servers using the XMPP protocol. This communication is asynchronous and bidirectional.</p> |
| <p>You can continue to use the HTTP request mechanism to send messages to GCM |
| servers, side-by-side with CCS which uses XMPP. Some of the benefits of CCS include:</p> |
| <ul> |
| <li>The asynchronous nature of XMPP allows you to send more messages with fewer |
| resources.</li> |
| <li>Communication is bidirectional—not only can the server send messages |
| to the device, but the device can send messages back to the server.</li> |
| <li>You can send messages back using the same connection used for receiving, |
| thereby improving battery life.</li> |
| </ul> |
| |
| <p>The upstream messaging (device-to-cloud) feature of CCS is part of the Google |
| Play services platform. Upstream messaging is available through the |
| <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html"> |
| {@code GoogleCloudMessaging}</a> |
| APIs. For examples, see |
| <a href="#implement">Implementing an XMPP-based App Server</a>.</p> |
| |
| <p class="note"><strong>Note:</strong> See |
| <a href="server.html#params">Implementing GCM Server</a> for a list of all the message |
| parameters and which connection server(s) supports them.</p> |
| |
| |
| <h2 id="usage">How to Use CCS</h2> |
| |
| <p>GCM Cloud Connection Server (CCS) is an XMPP endpoint, running on |
| {@code http://gcm.googleapis.com} port 5235.</p> |
| |
| <p>CCS requires a Transport Layer Security (TLS) connection. That means the XMPP |
| client must initiate a TLS connection. |
| For example in Java, you would call {@code setSocketFactory(SSLSocketFactory)}.</p> |
| |
| <p>CCS requires a SASL PLAIN authentication mechanism using |
| {@code <your_GCM_Sender_Id>@gcm.googleapis.com} (GCM sender ID) and the |
| API key as the password, where the sender ID and API key are the same as described |
| in <a href="gs.html">Getting Started</a>.</p> |
| |
| <p> You can use most XMPP libraries to interact with CCS.</p> |
| |
| <h3 id="auth">Authentication</h3> |
| |
| <p>The following snippets illustrate how to perform authentication in CCS.</p> |
| <h4>Client</h4> |
| <pre><stream:stream to="gcm.googleapis.com" |
| version="1.0" xmlns="jabber:client" |
| xmlns:stream="http://etherx.jabber.org/streams"/> |
| </pre> |
| <h4>Server</h4> |
| <pre><str:features xmlns:str="http://etherx.jabber.org/streams"> |
| <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> |
| <mechanism>X-OAUTH2</mechanism> |
| <mechanism>X-GOOGLE-TOKEN</mechanism> |
| <mechanism>PLAIN</mechanism> |
| </mechanisms> |
| </str:features> |
| </pre> |
| |
| <h4>Client</h4> |
| <pre><auth mechanism="PLAIN" |
| xmlns="urn:ietf:params:xml:ns:xmpp-sasl">MTI2MjAwMzQ3OTMzQHByb2plY3RzLmdjbS5hb |
| mFTeUIzcmNaTmtmbnFLZEZiOW1oekNCaVlwT1JEQTJKV1d0dw==</auth> |
| </pre> |
| |
| <h4>Server</h4> |
| <pre><success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/></pre> |
| |
| <h2 id="format">Message Format</h2> |
| <p>CCS uses normal XMPP <code><message></code> stanzas. The body of the message must be: |
| </p> |
| <pre> |
| <gcm xmlns:google:mobile:data> |
| <em>JSON payload</em> |
| </gcm> |
| </pre> |
| |
| <p>The JSON payload for server-to-device is similar to what the GCM http endpoint |
| uses, with these exceptions:</p> |
| <ul> |
| <li>There is no support for multiple recipients.</li> |
| <li>{@code to} is used instead of {@code registration_ids}.</li> |
| <li>CCS adds the field {@code message_id}, which is required. This ID uniquely |
| identifies the message in an XMPP connection. The ACK or NACK from CCS uses the |
| {@code message_id} to identify a message sent from 3rd-party app servers to CCS. |
| Therefore, it's important that this {@code message_id} not only be unique, but |
| always present.</li> |
| |
| <li>For ACK/NACK messages that are special control messages, you also need to |
| include a {@code message_type} field in the JSON message. The value can be either |
| 'ack' or 'nack'. For example: |
| |
| <pre>message_type = ('ack');</pre> |
| </li> |
| </ul> |
| <p>For each device message your app server receives from CCS, it needs to send |
| an ACK message. |
| It never needs to send a NACK message. If you don't send an ACK for a message, |
| CCS will just resend it. |
| </p> |
| <p>CCS also sends an ACK or NACK for each server-to-device message. If you do not |
| receive either, it means that the TCP connection was closed in the middle of the |
| operation and your server needs to resend the messages. See |
| <a href="#flow">Flow Control</a> for details. |
| </p> |
| |
| <p class="note"><strong>Note:</strong> See |
| <a href="server.html#params">Implementing GCM Server</a> for a list of all the message |
| parameters and which connection server(s) supports them.</p> |
| |
| <h3 id="request">Request format</h3> |
| |
| <p>Here is an XMPP stanza containing the JSON message from a 3rd-party app server to CCS: |
| |
| </p> |
| <pre><message id=""> |
| <gcm xmlns="google:mobile:data"> |
| { |
| "to":"REGISTRATION_ID", // "to" replaces "registration_ids" |
| "message_id":"m-1366082849205" // new required field |
| "data": |
| { |
| "hello":"world", |
| } |
| "time_to_live":"600", |
| "delay_while_idle": true/false |
| } |
| </gcm> |
| </message> |
| </pre> |
| |
| <h3 id="response">Response format</h3> |
| |
| <p>A CCS response can have 3 possible forms. The first one is a regular 'ack' |
| message. But when the response contains an error, there are 2 |
| different forms the message can take, described below.</p> |
| |
| <h4 id="ack">ACK message</h4> |
| |
| <p>Here is an XMPP stanza containing the ACK/NACK message from CCS to 3rd-party app server: |
| </p> |
| <pre><message id=""> |
| <gcm xmlns="google:mobile:data"> |
| { |
| "from":"REGID", |
| "message_id":"m-1366082849205" |
| "message_type":"ack" |
| } |
| </gcm> |
| </message> |
| </pre> |
| |
| <h4 id="nack">NACK message</h4> |
| |
| <p>A NACK error is a regular XMPP message in which the {@code message_type} status |
| message is "nack". A NACK message contains:</p> |
| <ul> |
| <li>Nack error code.</li> |
| <li>Nack error description.</li> |
| </ul> |
| |
| <p>Below are some examples.</p> |
| |
| <p>Bad registration:</p> |
| <pre><message> |
| <data:gcm xmlns:data="google:mobile:data"> |
| { |
| "error":"BAD_REGISTRATION", // error code |
| "message_id":"msgId1", |
| "from":"PA91bHFOtaQGSwupt5l1og", |
| "message_type":"nack" |
| } |
| </data:gcm> |
| </message></pre> |
| |
| <p>Invalid "time to live":</p> |
| |
| <pre><message> |
| <data:gcm xmlns:data="google:mobile:data"> |
| { |
| "error":"InvalidJson : INVALID_TTL : Invalid value (-1) for \"time_to_live\": must be between 0 and \"2419200\"\n", |
| "message_id":"msgId1", |
| "from":"APA91bHFOtaQGSwupt5l1og", |
| "message_type":"nack" |
| } |
| </data:gcm> |
| </message></pre> |
| |
| <p>JSON type error:</p> |
| |
| <pre><message> |
| <data:gcm xmlns:data="google:mobile:data"> |
| { |
| "error":"InvalidJson : JSON_TYPE_ERROR : Field \"delay_while_idle\" must be a JSON java.lang.Boolean: not-boolean-user-supplied-value\n", |
| "message_id":"msgId1", |
| "from":"APA91bHFOtaQGSwupt5l1og", |
| "message_type":"nack" |
| } |
| </data:gcm> |
| </message></pre> |
| |
| |
| <p>The following table lists some of the more common NACK error codes.</p> |
| |
| <p class="table-caption" id="table1"> |
| <strong>Table 1.</strong> NACK error codes.</p> |
| |
| <table border="1"> |
| <tr> |
| <th>Error Code</th> |
| <th>Description</th> |
| </tr> |
| <tr> |
| <td>{@code BAD_REGISTRATION}</td> |
| <td>The device has a registration ID, but it's invalid.</td> |
| </tr> |
| <tr> |
| <td>{@code DEVICE_UNREGISTERED}</td> |
| <td>The device is not registered.</td> |
| </tr> |
| <tr> |
| <td>{@code INTERNAL_SERVER_ERROR}</td> |
| <td>The server encountered an error while trying to process the request.</td> |
| </tr> |
| <tr> |
| <td>{@code SERVICE_UNAVAILABLE}</td> |
| <td>The CCS connection server is temporarily unavailable, try again later |
| (using exponential backoff, etc.).</td> |
| </tr> |
| <tr> |
| <td>{@code BAD_ACK}</td> |
| <td>The ACK message is improperly formed.</td> |
| </tr> |
| <tr> |
| <td>{@code AUTHENTICATION_FAILED}</td> |
| <td>This is a 401 error indicating that there was an error authenticating the sender account.</td> |
| </tr> |
| <tr> |
| <td>{@code INVALID_TTL}</td> |
| <td>There was an error in the supplied "time to live" value.</td> |
| </tr> |
| <tr> |
| <td>{@code JSON_TYPE_ERROR}</td> |
| <td>There was an error in the supplied JSON data type.</td> |
| </tr> |
| </table> |
| |
| <h4 id="stanza">Stanza error</h4> |
| |
| <p>You can also get a stanza error in certain cases. |
| A stanza error contains:</p> |
| <ul> |
| <li>Stanza error code.</li> |
| <li>Stanza error description (free text).</li> |
| </ul> |
| <p>For example:</p> |
| |
| <pre><message id="3" type="error" to="[email protected]/ABC"> |
| <gcm xmlns="google:mobile:data"> |
| {"random": "text"} |
| </gcm> |
| <error code="400" type="modify"> |
| <bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/> |
| <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> |
| InvalidJson: JSON_PARSING_ERROR : Missing Required Field: message_id\n |
| </text> |
| </error> |
| </message> |
| </pre> |
| |
| |
| <h2 id="upstream">Upstream Messages</h2> |
| |
| <p>Using CCS and the |
| <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html"> |
| {@code GoogleCloudMessaging}</a> |
| API, you can send messages from a user's device to the cloud.</p> |
| |
| <p>Here is how you send an upstream message using the |
| <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html"> |
| {@code GoogleCloudMessaging}</a> |
| API. For a complete example, see <a href="client.html">Implementing GCM Client</a>:</p> |
| |
| <pre>GoogleCloudMessaging gcm = GoogleCloudMessaging.get(context); |
| String GCM_SENDER_ID = "Your-Sender-ID"; |
| AtomicInteger msgId = new AtomicInteger(); |
| String id = Integer.toString(msgId.incrementAndGet()); |
| Bundle data = new Bundle(); |
| // Bundle data consists of a key-value pair |
| data.putString("hello", "world"); |
| // "time to live" parameter |
| // This is optional. It specifies a value in seconds up to 4 weeks. |
| int ttl = [0 seconds, 4 weeks] |
| |
| gcm.send(GCM_SENDER_ID + "@gcm.googleapis.com", id, ttl, data); |
| </pre> |
| |
| <p>This call generates the necessary XMPP stanza for sending the upstream message. |
| The message goes from the app on the device to CCS to the 3rd-party app server. |
| The stanza has the following format:</p> |
| |
| <pre><message id=""> |
| <gcm xmlns="google:mobile:data"> |
| { |
| "category":"com.example.yourapp", // to know which app sent it |
| "data": |
| { |
| "hello":"world", |
| }, |
| "message_id":"m-123", |
| "from":"REGID" |
| } |
| </gcm> |
| </message></pre> |
| |
| <p>Here is the format of the ACK expected by CCS from 3rd-party app servers in |
| response to the above message:</p> |
| |
| <pre><message id=""> |
| <gcm xmlns="google:mobile:data"> |
| { |
| "to":"REGID", |
| "message_id":"m-123" |
| "message_type":"ack" |
| } |
| </gcm> |
| </message></pre> |
| |
| <h2 id="flow">Flow Control</h2> |
| |
| <p>Every message sent to CCS receives either an ACK or a NACK response. Messages |
| that haven't received one of these responses are considered pending. If the pending |
| message count reaches 1000, the 3rd-party app server should stop sending new messages |
| and wait for CCS to acknowledge some of the existing pending messages as illustrated in |
| figure 1:</p> |
| |
| <img src="{@docRoot}images/gcm/CCS-ack.png"> |
| |
| <p class="img-caption"> |
| <strong>Figure 1.</strong> Message/ack flow. |
| </p> |
| |
| <p>Conversely, to avoid overloading the 3rd-party app server, CCS will stop sending |
| if there are too many unacknowledged messages. Therefore, the 3rd-party app server |
| should "ACK" upstream messages, received from the client application via CCS, as soon as possible |
| to maintain a constant flow of incoming messages. The aforementioned pending message limit doesn't |
| apply to these ACKs. Even if the pending message count reaches 1000, the 3rd-party app server |
| should continue sending ACKs for messages received from CCS to avoid blocking delivery of new |
| upstream messages.</p> |
| |
| <p>ACKs are only valid within the context of one connection. If the connection is |
| closed before a message can be ACKed, the 3rd-party app server should wait for CCS |
| to resend the upstream message before ACKing it again. Similarly, all pending messages for which an |
| ACK/NACK was not received from CCS before the connection was closed should be sent again. |
| </p> |
| |
| <h2 id="implement">Implementing an XMPP-based App Server</h2> |
| |
| <p>This section gives examples of implementing an app server that works with CCS. |
| Note that a full GCM implementation requires a client-side implementation, in |
| addition to the server. For more information, see <a href="client.html"> |
| Implementing GCM Client</a>.</a> |
| |
| <h3 id="smack">Java sample using the Smack library</h3> |
| |
| <p>Here is a sample app server written in Java, using the |
| <a href="http://www.igniterealtime.org/projects/smack/">Smack</a> library.</p> |
| |
| <pre>import org.jivesoftware.smack.ConnectionConfiguration; |
| import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode; |
| import org.jivesoftware.smack.ConnectionListener; |
| import org.jivesoftware.smack.PacketInterceptor; |
| import org.jivesoftware.smack.PacketListener; |
| import org.jivesoftware.smack.XMPPConnection; |
| import org.jivesoftware.smack.XMPPException; |
| import org.jivesoftware.smack.filter.PacketTypeFilter; |
| import org.jivesoftware.smack.packet.DefaultPacketExtension; |
| import org.jivesoftware.smack.packet.Message; |
| import org.jivesoftware.smack.packet.Packet; |
| import org.jivesoftware.smack.packet.PacketExtension; |
| import org.jivesoftware.smack.provider.PacketExtensionProvider; |
| import org.jivesoftware.smack.provider.ProviderManager; |
| import org.jivesoftware.smack.util.StringUtils; |
| import org.json.simple.JSONValue; |
| import org.json.simple.parser.ParseException; |
| import org.xmlpull.v1.XmlPullParser; |
| |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Random; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import javax.net.ssl.SSLSocketFactory; |
| /** |
| * Sample Smack implementation of a client for GCM Cloud Connection Server. |
| * |
| * <p>For illustration purposes only. |
| */ |
| public class SmackCcsClient { |
| |
| Logger logger = Logger.getLogger("SmackCcsClient"); |
| |
| public static final String GCM_SERVER = "gcm.googleapis.com"; |
| public static final int GCM_PORT = 5235; |
| |
| public static final String GCM_ELEMENT_NAME = "gcm"; |
| public static final String GCM_NAMESPACE = "google:mobile:data"; |
| |
| static Random random = new Random(); |
| XMPPConnection connection; |
| ConnectionConfiguration config; |
| |
| /** |
| * XMPP Packet Extension for GCM Cloud Connection Server. |
| */ |
| class GcmPacketExtension extends DefaultPacketExtension { |
| String json; |
| |
| public GcmPacketExtension(String json) { |
| super(GCM_ELEMENT_NAME, GCM_NAMESPACE); |
| this.json = json; |
| } |
| |
| public String getJson() { |
| return json; |
| } |
| |
| @Override |
| public String toXML() { |
| return String.format("<%s xmlns=\"%s\">%s</%s>", GCM_ELEMENT_NAME, |
| GCM_NAMESPACE, json, GCM_ELEMENT_NAME); |
| } |
| |
| @SuppressWarnings("unused") |
| public Packet toPacket() { |
| return new Message() { |
| // Must override toXML() because it includes a <body> |
| @Override |
| public String toXML() { |
| |
| StringBuilder buf = new StringBuilder(); |
| buf.append("<message"); |
| if (getXmlns() != null) { |
| buf.append(" xmlns=\"").append(getXmlns()).append("\""); |
| } |
| if (getLanguage() != null) { |
| buf.append(" xml:lang=\"").append(getLanguage()).append("\""); |
| } |
| if (getPacketID() != null) { |
| buf.append(" id=\"").append(getPacketID()).append("\""); |
| } |
| if (getTo() != null) { |
| buf.append(" to=\"").append(StringUtils.escapeForXML(getTo())).append("\""); |
| } |
| if (getFrom() != null) { |
| buf.append(" from=\"").append(StringUtils.escapeForXML(getFrom())).append("\""); |
| } |
| buf.append(">"); |
| buf.append(GcmPacketExtension.this.toXML()); |
| buf.append("</message>"); |
| return buf.toString(); |
| } |
| }; |
| } |
| } |
| |
| public SmackCcsClient() { |
| // Add GcmPacketExtension |
| ProviderManager.getInstance().addExtensionProvider(GCM_ELEMENT_NAME, |
| GCM_NAMESPACE, new PacketExtensionProvider() { |
| |
| @Override |
| public PacketExtension parseExtension(XmlPullParser parser) |
| throws Exception { |
| String json = parser.nextText(); |
| GcmPacketExtension packet = new GcmPacketExtension(json); |
| return packet; |
| } |
| }); |
| } |
| |
| /** |
| * Returns a random message id to uniquely identify a message. |
| * |
| * <p>Note: |
| * This is generated by a pseudo random number generator for illustration purpose, |
| * and is not guaranteed to be unique. |
| * |
| */ |
| public String getRandomMessageId() { |
| return "m-" + Long.toString(random.nextLong()); |
| } |
| |
| /** |
| * Sends a downstream GCM message. |
| */ |
| public void send(String jsonRequest) { |
| Packet request = new GcmPacketExtension(jsonRequest).toPacket(); |
| connection.sendPacket(request); |
| } |
| |
| /** |
| * Handles an upstream data message from a device application. |
| * |
| * <p>This sample echo server sends an echo message back to the device. |
| * Subclasses should override this method to process an upstream message. |
| */ |
| public void handleIncomingDataMessage(Map<String, Object> jsonObject) { |
| String from = jsonObject.get("from").toString(); |
| |
| // PackageName of the application that sent this message. |
| String category = jsonObject.get("category").toString(); |
| |
| // Use the packageName as the collapseKey in the echo packet |
| String collapseKey = "echo:CollapseKey"; |
| @SuppressWarnings("unchecked") |
| Map<String, String> payload = (Map<String, String>) jsonObject.get("data"); |
| payload.put("ECHO", "Application: " + category); |
| |
| // Send an ECHO response back |
| String echo = createJsonMessage(from, getRandomMessageId(), payload, collapseKey, null, false); |
| send(echo); |
| } |
| |
| /** |
| * Handles an ACK. |
| * |
| * <p>By default, it only logs a {@code INFO} message, but subclasses could override it to |
| * properly handle ACKS. |
| */ |
| public void handleAckReceipt(Map<String, Object> jsonObject) { |
| String messageId = jsonObject.get("message_id").toString(); |
| String from = jsonObject.get("from").toString(); |
| logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", messageId: " + messageId); |
| } |
| |
| /** |
| * Handles a NACK. |
| * |
| * <p>By default, it only logs a {@code INFO} message, but subclasses could override it to |
| * properly handle NACKS. |
| */ |
| public void handleNackReceipt(Map<String, Object> jsonObject) { |
| String messageId = jsonObject.get("message_id").toString(); |
| String from = jsonObject.get("from").toString(); |
| logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", messageId: " + messageId); |
| } |
| |
| /** |
| * Creates a JSON encoded GCM message. |
| * |
| * @param to RegistrationId of the target device (Required). |
| * @param messageId Unique messageId for which CCS will send an "ack/nack" (Required). |
| * @param payload Message content intended for the application. (Optional). |
| * @param collapseKey GCM collapse_key parameter (Optional). |
| * @param timeToLive GCM time_to_live parameter (Optional). |
| * @param delayWhileIdle GCM delay_while_idle parameter (Optional). |
| * @return JSON encoded GCM message. |
| */ |
| public static String createJsonMessage(String to, String messageId, Map<String, String> payload, |
| String collapseKey, Long timeToLive, Boolean delayWhileIdle) { |
| Map<String, Object> message = new HashMap<String, Object>(); |
| message.put("to", to); |
| if (collapseKey != null) { |
| message.put("collapse_key", collapseKey); |
| } |
| if (timeToLive != null) { |
| message.put("time_to_live", timeToLive); |
| } |
| if (delayWhileIdle != null && delayWhileIdle) { |
| message.put("delay_while_idle", true); |
| } |
| message.put("message_id", messageId); |
| message.put("data", payload); |
| return JSONValue.toJSONString(message); |
| } |
| |
| /** |
| * Creates a JSON encoded ACK message for an upstream message received from an application. |
| * |
| * @param to RegistrationId of the device who sent the upstream message. |
| * @param messageId messageId of the upstream message to be acknowledged to CCS. |
| * @return JSON encoded ack. |
| */ |
| public static String createJsonAck(String to, String messageId) { |
| Map<String, Object> message = new HashMap<String, Object>(); |
| message.put("message_type", "ack"); |
| message.put("to", to); |
| message.put("message_id", messageId); |
| return JSONValue.toJSONString(message); |
| } |
| |
| /** |
| * Connects to GCM Cloud Connection Server using the supplied credentials. |
| * |
| * @param username GCM_SENDER_ID@gcm.googleapis.com |
| * @param password API Key |
| * @throws XMPPException |
| */ |
| public void connect(String username, String password) throws XMPPException { |
| config = new ConnectionConfiguration(GCM_SERVER, GCM_PORT); |
| config.setSecurityMode(SecurityMode.enabled); |
| config.setReconnectionAllowed(true); |
| config.setRosterLoadedAtLogin(false); |
| config.setSendPresence(false); |
| config.setSocketFactory(SSLSocketFactory.getDefault()); |
| |
| // NOTE: Set to true to launch a window with information about packets sent and received |
| config.setDebuggerEnabled(true); |
| |
| // -Dsmack.debugEnabled=true |
| XMPPConnection.DEBUG_ENABLED = true; |
| |
| connection = new XMPPConnection(config); |
| connection.connect(); |
| |
| connection.addConnectionListener(new ConnectionListener() { |
| |
| @Override |
| public void reconnectionSuccessful() { |
| logger.info("Reconnecting.."); |
| } |
| |
| @Override |
| public void reconnectionFailed(Exception e) { |
| logger.log(Level.INFO, "Reconnection failed.. ", e); |
| } |
| |
| @Override |
| public void reconnectingIn(int seconds) { |
| logger.log(Level.INFO, "Reconnecting in %d secs", seconds); |
| } |
| |
| @Override |
| public void connectionClosedOnError(Exception e) { |
| logger.log(Level.INFO, "Connection closed on error."); |
| } |
| |
| @Override |
| public void connectionClosed() { |
| logger.info("Connection closed."); |
| } |
| }); |
| |
| // Handle incoming packets |
| connection.addPacketListener(new PacketListener() { |
| |
| @Override |
| public void processPacket(Packet packet) { |
| logger.log(Level.INFO, "Received: " + packet.toXML()); |
| Message incomingMessage = (Message) packet; |
| GcmPacketExtension gcmPacket = |
| (GcmPacketExtension) incomingMessage.getExtension(GCM_NAMESPACE); |
| String json = gcmPacket.getJson(); |
| try { |
| @SuppressWarnings("unchecked") |
| Map<String, Object> jsonObject = |
| (Map<String, Object>) JSONValue.parseWithException(json); |
| |
| // present for "ack"/"nack", null otherwise |
| Object messageType = jsonObject.get("message_type"); |
| |
| if (messageType == null) { |
| // Normal upstream data message |
| handleIncomingDataMessage(jsonObject); |
| |
| // Send ACK to CCS |
| String messageId = jsonObject.get("message_id").toString(); |
| String from = jsonObject.get("from").toString(); |
| String ack = createJsonAck(from, messageId); |
| send(ack); |
| } else if ("ack".equals(messageType.toString())) { |
| // Process Ack |
| handleAckReceipt(jsonObject); |
| } else if ("nack".equals(messageType.toString())) { |
| // Process Nack |
| handleNackReceipt(jsonObject); |
| } else { |
| logger.log(Level.WARNING, "Unrecognized message type (%s)", |
| messageType.toString()); |
| } |
| } catch (ParseException e) { |
| logger.log(Level.SEVERE, "Error parsing JSON " + json, e); |
| } catch (Exception e) { |
| logger.log(Level.SEVERE, "Couldn't send echo.", e); |
| } |
| } |
| }, new PacketTypeFilter(Message.class)); |
| |
| |
| // Log all outgoing packets |
| connection.addPacketInterceptor(new PacketInterceptor() { |
| @Override |
| public void interceptPacket(Packet packet) { |
| logger.log(Level.INFO, "Sent: {0}", packet.toXML()); |
| } |
| }, new PacketTypeFilter(Message.class)); |
| |
| connection.login(username, password); |
| } |
| |
| public static void main(String [] args) { |
| final String userName = "Your GCM Sender Id" + "@gcm.googleapis.com"; |
| final String password = "API Key"; |
| |
| SmackCcsClient ccsClient = new SmackCcsClient(); |
| |
| try { |
| ccsClient.connect(userName, password); |
| } catch (XMPPException e) { |
| e.printStackTrace(); |
| } |
| |
| // Send a sample hello downstream message to a device. |
| String toRegId = "RegistrationIdOfTheTargetDevice"; |
| String messageId = ccsClient.getRandomMessageId(); |
| Map<String, String> payload = new HashMap<String, String>(); |
| payload.put("Hello", "World"); |
| payload.put("CCS", "Dummy Message"); |
| payload.put("EmbeddedMessageId", messageId); |
| String collapseKey = "sample"; |
| Long timeToLive = 10000L; |
| Boolean delayWhileIdle = true; |
| ccsClient.send(createJsonMessage(toRegId, messageId, payload, collapseKey, |
| timeToLive, delayWhileIdle)); |
| } |
| }</pre> |
| <h3 id="python">Python sample</h3> |
| |
| <p>Here is an example of a CCS app server written in Python. This sample echo |
| server sends an initial message, and for every upstream message received, it sends |
| a dummy response back to the application that sent the upstream message. This |
| example illustrates how to connect, send, and receive GCM messages using XMPP. It |
| shouldn't be used as-is on a production deployment.</p> |
| |
| <pre> |
| #!/usr/bin/python |
| import sys, json, xmpp, random, string |
| |
| SERVER = 'gcm.googleapis.com' |
| PORT = 5235 |
| USERNAME = "Your GCM Sender Id" |
| PASSWORD = "API Key" |
| REGISTRATION_ID = "Registration Id of the target device" |
| |
| unacked_messages_quota = 1000 |
| send_queue = [] |
| |
| # Return a random alphanumerical id |
| def random_id(): |
| rid = '' |
| for x in range(8): rid += random.choice(string.ascii_letters + string.digits) |
| return rid |
| |
| def message_callback(session, message): |
| global unacked_messages_quota |
| gcm = message.getTags('gcm') |
| if gcm: |
| gcm_json = gcm[0].getData() |
| msg = json.loads(gcm_json) |
| if not msg.has_key('message_type'): |
| # Acknowledge the incoming message immediately. |
| send({'to': msg['from'], |
| 'message_type': 'ack', |
| 'message_id': msg['message_id']}) |
| # Queue a response back to the server. |
| if msg.has_key('from'): |
| # Send a dummy echo response back to the app that sent the upstream message. |
| send_queue.append({'to': msg['from'], |
| 'message_id': random_id(), |
| 'data': {'pong': 1}}) |
| elif msg['message_type'] == 'ack' or msg['message_type'] == 'nack': |
| unacked_messages_quota += 1 |
| |
| def send(json_dict): |
| template = ("<message><gcm xmlns='google:mobile:data'>{1}</gcm></message>") |
| client.send(xmpp.protocol.Message( |
| node=template.format(client.Bind.bound[0], json.dumps(json_dict)))) |
| |
| def flush_queued_messages(): |
| global unacked_messages_quota |
| while len(send_queue) and unacked_messages_quota > 0: |
| send(send_queue.pop(0)) |
| unacked_messages_quota -= 1 |
| |
| client = xmpp.Client('gcm.googleapis.com', debug=['socket']) |
| client.connect(server=(SERVER,PORT), secure=1, use_srv=False) |
| auth = client.auth(USERNAME, PASSWORD) |
| if not auth: |
| print 'Authentication failed!' |
| sys.exit(1) |
| |
| client.RegisterHandler('message', message_callback) |
| |
| send_queue.append({'to': REGISTRATION_ID, |
| 'message_id': 'reg_id', |
| 'data': {'message_destination': 'RegId', |
| 'message_id': random_id()}}) |
| |
| while True: |
| client.Process(1) |
| flush_queued_messages()</pre> |