diff options
Diffstat (limited to 'docs/html/google/gcm/ccs.jd')
-rw-r--r-- | docs/html/google/gcm/ccs.jd | 963 |
1 files changed, 0 insertions, 963 deletions
diff --git a/docs/html/google/gcm/ccs.jd b/docs/html/google/gcm/ccs.jd deleted file mode 100644 index c4d1b1d..0000000 --- a/docs/html/google/gcm/ccs.jd +++ /dev/null @@ -1,963 +0,0 @@ -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="#connecting">Establishing a Connection</a> - <ol class="toc"> - <li><a href="#auth">Authentication</a></li> - </ol> - </li> - <li><a href="#format">Downstream Messages</a> - <ol class="toc"> - <li><a href="#request">Request format</a></li> - <li><a href="#response">Response format</a></li> - <li><a href="#receipts">Receive delivery receipts</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="server-ref.html">Server Reference</a></li> -<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> -</ol> - -</div> -</div> - -<p>The Google Cloud Messaging (GCM) Cloud Connection Server (CCS) is an XMPP endpoint that provides a -persistent, asynchronous, bidirectional connection to Google servers. The -connection can be used to send and receive messages between your server and -your users' GCM-connected devices.</p> - -<p class="note"><strong>Note:</strong> The content in this document -applies to <a href="http://developer.chrome.com/apps/cloudMessaging"> -GCM with Chrome apps</a> as well as Android. - -<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 your server send messages -to the device, but the device can send messages back to your server.</li> - <li>The device 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 the -<a href="server-ref.html">Server Reference</a> for a list of all the message -parameters and which connection server(s) supports them.</p> - -<h2 id="connecting">Establishing a Connection</h2> - -<p>CCS just uses XMPP as an authenticated transport layer, so you can use most -XMPP libraries to manage the connection. For an example, see <a href="#smack"> -Java sample using the Smack library</a>.</p> - -<p>The CCS XMPP endpoint runs at {@code gcm.googleapis.com:5235}. When testing -functionality (with non-production users), you should instead connect to -{@code gcm-preprod.googleapis.com:5236} (note the different port). Regular -testing on preprod (a smaller environment where the latest CCS builds run) is -beneficial both for isolating real users from test code, as well as for early -detection of unexpected behavior changes. Note that a connection receives upstream -messages destined for its GCM sender ID, regardless of which environment (gcm or -gcm-preprod) it is connected to. Therefore, test code connecting to -{@code gcm-preprod.googleapis.com:5236} should use a different GCM sender ID to -avoid upstream messages from production traffic being sent over test connections.</p> - -<p>The connection has two important requirements:</p> - -<ul> - <li>You must initiate a Transport Layer Security (TLS) connection. Note that - CCS doesn't currently support the <a href="http://xmpp.org/rfcs/rfc3920.html" - class="external-link" target="_android">STARTTLS extension</a>.</li> - <li>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>.</li> -</ul> - -<p>If at any point the connection fails, you should immediately reconnect. -There is no need to back off after a disconnect that happens after -authentication.</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">Downstream Messages</h2> -<p>Once the XMPP connection is established, CCS and your server use normal XMPP -<code><message></code> stanzas to send JSON-encoded messages back and -forth. The body of the <code><message></code> must be:</p> -<pre> -<gcm xmlns:google:mobile:data> - <em>JSON payload</em> -</gcm> -</pre> - -<p>The JSON payload for regular GCM messages is similar to -<a href="http.html#request">what the GCM http endpoint uses</a>, 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 (per -sender ID), but always present.</li> -</ul> - -<p>In addition to regular GCM messages, control messages are sent, indicated by -the {@code message_type} field in the JSON object. The value can be either -'ack' or 'nack', or 'control' (see formats below). Any GCM message with an -unknown {@code message_type} can be ignored by your server.</p> - -<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 resends it the next time a new XMPP connection is established, unless the -message expires first. -</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 the -<a href="server-ref.html">Server Reference</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, - "delivery_receipt_requested": 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> - <gcm xmlns="google:mobile:data"> - { - "message_type":"nack", - "message_id":"msgId1", - "from":"SomeInvalidRegistrationId", - "error":"BAD_REGISTRATION", - "error_description":"Invalid token on 'to' field: SomeInvalidRegistrationId" - } - </gcm> -</message></pre> - -<p>Invalid JSON:</p> - -<pre><message> - <gcm xmlns="google:mobile:data"> - { - "message_type":"nack", - "message_id":"msgId1", - "from":"APA91bHFOtaQGSwupt5l1og", - "error":"INVALID_JSON", - "error_description":"InvalidJson: JSON_TYPE_ERROR : Field \"time_to_live\" must be a JSON java.lang.Number: abc" - } - </gcm> -</message> -</pre> - -<p>Device Message Rate Exceeded:</p> - -<pre><message id="..."> - <gcm xmlns="google:mobile:data"> - { - "message_type":"nack", - "message_id":"msgId1", - "from":"REGID", - "error":"DEVICE_MESSAGE_RATE_EXCEEDED", - "error_description":"Downstream message rate exceeded for this registration id" - } - </gcm> -</message> -</pre> - -<p>See the <a href="server-ref.html#table11">Server Reference</a> for a complete list of the -NACK error codes. Unless otherwise -indicated, a NACKed message should not be retried. Unexpected NACK error codes -should be treated the same as {@code INTERNAL_SERVER_ERROR}.</p> - -<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="123456789@gcm.googleapis.com/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> - -<h4 id="control">Control messages</h4> - -<p>Periodically, CCS needs to close down a connection to perform load balancing. Before it -closes the connection, CCS sends a {@code CONNECTION_DRAINING} message to indicate that the connection is being drained -and will be closed soon. "Draining" refers to shutting off the flow of messages coming into a -connection, but allowing whatever is already in the pipeline to continue. When you receive -a {@code CONNECTION_DRAINING} message, you should immediately begin sending messages to another CCS -connection, opening a new connection if necessary. You should, however, keep the original -connection open and continue receiving messages that may come over the connection (and -ACKing them)—CCS handles initiating a connection close when it is ready.</p> - -<p>The {@code CONNECTION_DRAINING} message looks like this:</p> -<pre><message> - <data:gcm xmlns:data="google:mobile:data"> - { - "message_type":"control" - "control_type":"CONNECTION_DRAINING" - } - </data:gcm> -</message></pre> - -<p>{@code CONNECTION_DRAINING} is currently the only {@code control_type} supported.</p> - -<!--Delivery receipts section--> - -<h3 id="receipts">Receive delivery receipts</h3> - -<p>You can get delivery receipts (sent from CCS to -your 3rd party app server) when -a device confirms that it received a message sent by CCS.</p> - -<p>To enable this feature, the message your 3rd-party app server sends to CCS must include -a field called <code>"delivery_receipt_requested"</code>. When this field is set to -<code>true</code>, CCS sends a delivery receipt when a device confirms that it received a particular message.</p> - -<p>Here is an XMPP stanza containing a JSON -message with <code>"delivery_receipt_requested"</code> set to <code>true</code>:</p> - -<pre><message id=""> - <gcm xmlns="google:mobile:data"> - { - "to":"REGISTRATION_ID", - "message_id":"m-1366082849205" - "data": - { - "hello":"world", - } - "time_to_live":"600", - "delay_while_idle": true, - <strong>"delivery_receipt_requested": true</strong> - } - </gcm> -</message> -</pre> - - - -<p>Here is an example of the delivery receipt that CCS sends to tell your 3rd-party -app server that a device received a message that CCS sent it:</p> - -</p> -<pre><message id=""> - <gcm xmlns="google:mobile:data"> - { - "category":"com.example.yourapp", // to know which app sent it - "data": - { - “message_status":"MESSAGE_SENT_TO_DEVICE", - “original_message_id”:”m-1366082849205” - “device_registration_id”: “REGISTRATION_ID” - }, - "message_id":"dr2:m-1366082849205", - "message_type":"receipt", - "from":"gcm.googleapis.com" - } - </gcm> -</message></pre> - -<p>Note the following:</p> - -<ul> - <li>The {@code "message_type"} is set to {@code "receipt"}. - <li>The {@code "message_status"} is set to {@code "MESSAGE_SENT_TO_DEVICE"}, - indicating that the device received the message. Notice that in this case, -{@code "message_status"} is not a field but rather part of the data payload.</li> - <li>The receipt message ID consists of the original message ID, but with a -<code>dr2:</code> prefix. Your 3rd-party app server must send an ACK back with this ID, -which in this example is {@code dr2:m-1366082849205}.</li> - <li>The original message ID, the device registration ID, and the status are inside the -{@code "data"} field.</li> -<li>If the connection between CCS and the device is poor, GCM may send multiple, duplicate delivery - receipts. You can safely ignore such duplicates.</li> -</ul> - -<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 24 hours. -int ttl = [0 seconds, 24 hours] - -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 100, 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 stops 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 100, 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.SmackException; -import org.jivesoftware.smack.SmackException.NotConnectedException; -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.tcp.XMPPTCPConnection; -import org.jivesoftware.smack.util.StringUtils; -import org.json.simple.JSONValue; -import org.json.simple.parser.ParseException; -import org.xmlpull.v1.XmlPullParser; - -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -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. This - * code can be run as a standalone CCS client. - * - * <p>For illustration purposes only. - */ -public class SmackCcsClient { - - private static final Logger logger = Logger.getLogger("SmackCcsClient"); - - private static final String GCM_SERVER = "gcm.googleapis.com"; - private static final int GCM_PORT = 5235; - - private static final String GCM_ELEMENT_NAME = "gcm"; - private static final String GCM_NAMESPACE = "google:mobile:data"; - - static { - - ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, - new PacketExtensionProvider() { - @Override - public PacketExtension parseExtension(XmlPullParser parser) throws - Exception { - String json = parser.nextText(); - return new GcmPacketExtension(json); - } - }); - } - - private XMPPConnection connection; - - /** - * Indicates whether the connection is in draining state, which means that it - * will not accept any new downstream messages. - */ - protected volatile boolean connectionDraining = false; - - /** - * Sends a downstream message to GCM. - * - * @return true if the message has been successfully sent. - */ - public boolean sendDownstreamMessage(String jsonRequest) throws - NotConnectedException { - if (!connectionDraining) { - send(jsonRequest); - return true; - } - logger.info("Dropping downstream message since the connection is draining"); - return false; - } - - /** - * 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 nextMessageId() { - return "m-" + UUID.randomUUID().toString(); - } - - /** - * Sends a packet with contents provided. - */ - protected void send(String jsonRequest) throws NotConnectedException { - 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 properly process upstream messages. - */ - protected void handleUpstreamMessage(Map<String, Object> jsonObject) { - // PackageName of the application that sent this message. - String category = (String) jsonObject.get("category"); - String from = (String) jsonObject.get("from"); - @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, nextMessageId(), payload, - "echo:CollapseKey", null, false); - - try { - sendDownstreamMessage(echo); - } catch (NotConnectedException e) { - logger.log(Level.WARNING, "Not connected anymore, echo message is - not sent", e); - } - } - - /** - * Handles an ACK. - * - * <p>Logs a {@code INFO} message, but subclasses could override it to - * properly handle ACKs. - */ - protected void handleAckReceipt(Map<String, Object> jsonObject) { - String messageId = (String) jsonObject.get("message_id"); - String from = (String) jsonObject.get("from"); - logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", - messageId: " + messageId); - } - - /** - * Handles a NACK. - * - * <p>Logs a {@code INFO} message, but subclasses could override it to - * properly handle NACKs. - */ - protected void handleNackReceipt(Map<String, Object> jsonObject) { - String messageId = (String) jsonObject.get("message_id"); - String from = (String) jsonObject.get("from"); - logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", - messageId: " + messageId); - } - - protected void handleControlMessage(Map<String, Object> jsonObject) { - logger.log(Level.INFO, "handleControlMessage(): " + jsonObject); - String controlType = (String) jsonObject.get("control_type"); - if ("CONNECTION_DRAINING".equals(controlType)) { - connectionDraining = true; - } else { - logger.log(Level.INFO, "Unrecognized control type: %s. This could - happen if new features are " + "added to the CCS protocol.", - controlType); - } - } - - /** - * Creates a JSON encoded GCM message. - * - * @param to RegistrationId of the target device (Required). - * @param messageId Unique messageId for which CCS sends 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. - */ - protected 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 senderId Your GCM project number - * @param apiKey API Key of your project - */ - public void connect(long senderId, String apiKey) - throws XMPPException, IOException, SmackException { - ConnectionConfiguration config = - new ConnectionConfiguration(GCM_SERVER, GCM_PORT); - config.setSecurityMode(SecurityMode.enabled); - config.setReconnectionAllowed(true); - config.setRosterLoadedAtLogin(false); - config.setSendPresence(false); - config.setSocketFactory(SSLSocketFactory.getDefault()); - - connection = new XMPPTCPConnection(config); - connection.connect(); - - connection.addConnectionListener(new LoggingConnectionListener()); - - // 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 - handleUpstreamMessage(jsonObject); - - // Send ACK to CCS - String messageId = (String) jsonObject.get("message_id"); - String from = (String) jsonObject.get("from"); - 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 if ("control".equals(messageType.toString())) { - // Process control message - handleControlMessage(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, "Failed to process packet", 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(senderId + "@gcm.googleapis.com", apiKey); - } - - public static void main(String[] args) throws Exception { - final long senderId = 1234567890L; // your GCM sender id - final String password = "Your API key"; - - SmackCcsClient ccsClient = new SmackCcsClient(); - - ccsClient.connect(senderId, password); - - // Send a sample hello downstream message to a device. - String toRegId = "RegistrationIdOfTheTargetDevice"; - String messageId = ccsClient.nextMessageId(); - 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; - String message = createJsonMessage(toRegId, messageId, payload, - collapseKey, timeToLive, true); - - ccsClient.sendDownstreamMessage(message); - } - - /** - * XMPP Packet Extension for GCM Cloud Connection Server. - */ - private static final class GcmPacketExtension extends DefaultPacketExtension { - - private final 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, - StringUtils.escapeForXML(json), GCM_ELEMENT_NAME); - } - - public Packet toPacket() { - Message message = new Message(); - message.addExtension(this); - return message; - } - } - - private static final class LoggingConnectionListener - implements ConnectionListener { - - @Override - public void connected(XMPPConnection xmppConnection) { - logger.info("Connected."); - } - - @Override - public void authenticated(XMPPConnection xmppConnection) { - logger.info("Authenticated."); - } - - @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.info("Connection closed on error."); - } - - @Override - public void connectionClosed() { - logger.info("Connection closed."); - } - } -}</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 = 100 -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> |