summaryrefslogtreecommitdiffstats
path: root/docs/html/google/gcm/ccs.jd
diff options
context:
space:
mode:
Diffstat (limited to 'docs/html/google/gcm/ccs.jd')
-rw-r--r--docs/html/google/gcm/ccs.jd963
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&mdash;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 &lt;your_GCM_Sender_Id&gt;&#64;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>&lt;stream:stream to=&quot;gcm.googleapis.com&quot;
- version=&quot;1.0&quot; xmlns=&quot;jabber:client&quot;
- xmlns:stream=&quot;http://etherx.jabber.org/streams&quot;/&gt;
-</pre>
-<h4>Server</h4>
-<pre>&lt;str:features xmlns:str=&quot;http://etherx.jabber.org/streams&quot;&gt;
- &lt;mechanisms xmlns=&quot;urn:ietf:params:xml:ns:xmpp-sasl&quot;&gt;
- &lt;mechanism&gt;X-OAUTH2&lt;/mechanism&gt;
- &lt;mechanism&gt;X-GOOGLE-TOKEN&lt;/mechanism&gt;
- &lt;mechanism&gt;PLAIN&lt;/mechanism&gt;
- &lt;/mechanisms&gt;
-&lt;/str:features&gt;
-</pre>
-
-<h4>Client</h4>
-<pre>&lt;auth mechanism=&quot;PLAIN&quot;
-xmlns=&quot;urn:ietf:params:xml:ns:xmpp-sasl&quot;&gt;MTI2MjAwMzQ3OTMzQHByb2plY3RzLmdjbS5hb
-mFTeUIzcmNaTmtmbnFLZEZiOW1oekNCaVlwT1JEQTJKV1d0dw==&lt;/auth&gt;
-</pre>
-
-<h4>Server</h4>
-<pre>&lt;success xmlns=&quot;urn:ietf:params:xml:ns:xmpp-sasl&quot;/&gt;</pre>
-
-<h2 id="format">Downstream Messages</h2>
-<p>Once the XMPP connection is established, CCS and your server use normal XMPP
-<code>&lt;message&gt;</code> stanzas to send JSON-encoded messages back and
-forth. The body of the <code>&lt;message&gt;</code> must be:</p>
-<pre>
-&lt;gcm xmlns:google:mobile:data&gt;
- <em>JSON payload</em>
-&lt;/gcm&gt;
-</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>&lt;message id=&quot;&quot;&gt;
- &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
- {
- &quot;to&quot;:&quot;REGISTRATION_ID&quot;, // &quot;to&quot; replaces &quot;registration_ids&quot;
- &quot;message_id&quot;:&quot;m-1366082849205&quot; // new required field
- &quot;data&quot;:
- {
- &quot;hello&quot;:&quot;world&quot;,
- }
- &quot;time_to_live&quot;:&quot;600&quot;,
- &quot;delay_while_idle&quot;: true/false,
- &quot;delivery_receipt_requested&quot;: true/false
- }
- &lt;/gcm&gt;
-&lt;/message&gt;
-</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>&lt;message id=&quot;&quot;&gt;
- &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
- {
- &quot;from&quot;:&quot;REGID&quot;,
- &quot;message_id&quot;:&quot;m-1366082849205&quot;
- &quot;message_type&quot;:&quot;ack&quot;
- }
- &lt;/gcm&gt;
-&lt;/message&gt;
-</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 &quot;nack&quot;. 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>&lt;message&gt;
- &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
- {
- &quot;message_type&quot;:&quot;nack&quot;,
- &quot;message_id&quot;:&quot;msgId1&quot;,
- &quot;from&quot;:&quot;SomeInvalidRegistrationId&quot;,
- &quot;error&quot;:&quot;BAD_REGISTRATION&quot;,
- &quot;error_description&quot;:&quot;Invalid token on 'to' field: SomeInvalidRegistrationId&quot;
- }
- &lt;/gcm&gt;
-&lt;/message&gt;</pre>
-
-<p>Invalid JSON:</p>
-
-<pre>&lt;message&gt;
- &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
- {
- &quot;message_type&quot;:&quot;nack&quot;,
- &quot;message_id&quot;:&quot;msgId1&quot;,
- &quot;from&quot;:&quot;APA91bHFOtaQGSwupt5l1og&quot;,
- &quot;error&quot;:&quot;INVALID_JSON&quot;,
- &quot;error_description&quot;:&quot;InvalidJson: JSON_TYPE_ERROR : Field \&quot;time_to_live\&quot; must be a JSON java.lang.Number: abc&quot;
- }
- &lt;/gcm&gt;
-&lt;/message&gt;
-</pre>
-
-<p>Device Message Rate Exceeded:</p>
-
-<pre>&lt;message id=&quot;...&quot;&gt;
- &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
- {
- &quot;message_type&quot;:&quot;nack&quot;,
- &quot;message_id&quot;:&quot;msgId1&quot;,
- &quot;from&quot;:&quot;REGID&quot;,
- &quot;error&quot;:&quot;DEVICE_MESSAGE_RATE_EXCEEDED&quot;,
- &quot;error_description&quot;:&quot;Downstream message rate exceeded for this registration id&quot;
- }
- &lt;/gcm&gt;
-&lt;/message&gt;
-</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>&lt;message id=&quot;3&quot; type=&quot;error&quot; to=&quot;123456789@gcm.googleapis.com/ABC&quot;&gt;
- &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
- {&quot;random&quot;: &quot;text&quot;}
- &lt;/gcm&gt;
- &lt;error code=&quot;400&quot; type=&quot;modify&quot;&gt;
- &lt;bad-request xmlns=&quot;urn:ietf:params:xml:ns:xmpp-stanzas&quot;/&gt;
- &lt;text xmlns=&quot;urn:ietf:params:xml:ns:xmpp-stanzas&quot;&gt;
- InvalidJson: JSON_PARSING_ERROR : Missing Required Field: message_id\n
- &lt;/text&gt;
- &lt;/error&gt;
-&lt;/message&gt;
-</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)&mdash;CCS handles initiating a connection close when it is ready.</p>
-
-<p>The {@code CONNECTION_DRAINING} message looks like this:</p>
-<pre>&lt;message&gt;
- &lt;data:gcm xmlns:data=&quot;google:mobile:data&quot;&gt;
- {
- &quot;message_type&quot;:&quot;control&quot;
- &quot;control_type&quot;:&quot;CONNECTION_DRAINING&quot;
- }
- &lt;/data:gcm&gt;
-&lt;/message&gt;</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>&quot;delivery_receipt_requested&quot;</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>&quot;delivery_receipt_requested&quot;</code> set to <code>true</code>:</p>
-
-<pre>&lt;message id=&quot;&quot;&gt;
- &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
- {
- &quot;to&quot;:&quot;REGISTRATION_ID&quot;,
- &quot;message_id&quot;:&quot;m-1366082849205&quot;
- &quot;data&quot;:
- {
- &quot;hello&quot;:&quot;world&quot;,
- }
- &quot;time_to_live&quot;:&quot;600&quot;,
- &quot;delay_while_idle&quot;: true,
- <strong>&quot;delivery_receipt_requested&quot;: true</strong>
- }
- &lt;/gcm&gt;
-&lt;/message&gt;
-</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>&lt;message id=&quot;&quot;&gt;
- &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
- {
- &quot;category&quot;:&quot;com.example.yourapp&quot;, // to know which app sent it
- &quot;data&quot;:
- {
- &#x201c;message_status&quot;:&quot;MESSAGE_SENT_TO_DEVICE&quot;,
- &#x201c;original_message_id&#x201d;:&#x201d;m-1366082849205&#x201d;
- &#x201c;device_registration_id&#x201d;: &#x201c;REGISTRATION_ID&#x201d;
- },
- &quot;message_id&quot;:&quot;dr2:m-1366082849205&quot;,
- &quot;message_type&quot;:&quot;receipt&quot;,
- &quot;from&quot;:&quot;gcm.googleapis.com&quot;
- }
- &lt;/gcm&gt;
-&lt;/message&gt;</pre>
-
-<p>Note the following:</p>
-
-<ul>
- <li>The {@code &quot;message_type&quot;} is set to {@code &quot;receipt&quot;}.
- <li>The {@code &quot;message_status&quot;} is set to {@code &quot;MESSAGE_SENT_TO_DEVICE&quot;},
- indicating that the device received the message. Notice that in this case,
-{@code &quot;message_status&quot;} 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 &quot;data&quot;} 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 + "&#64;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>&lt;message id=&quot;&quot;&gt;
- &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
- {
- &quot;category&quot;:&quot;com.example.yourapp&quot;, // to know which app sent it
- &quot;data&quot;:
- {
- &quot;hello&quot;:&quot;world&quot;,
- },
- &quot;message_id&quot;:&quot;m-123&quot;,
- &quot;from&quot;:&quot;REGID&quot;
- }
- &lt;/gcm&gt;
-&lt;/message&gt;</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>&lt;message id=&quot;&quot;&gt;
- &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
- {
- &quot;to&quot;:&quot;REGID&quot;,
- &quot;message_id&quot;:&quot;m-123&quot;
- &quot;message_type&quot;:&quot;ack&quot;
- }
- &lt;/gcm&gt;
-&lt;/message&gt;</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.
- *
- * &lt;p&gt;For illustration purposes only.
- */
-public class SmackCcsClient {
-
- private static final Logger logger = Logger.getLogger(&quot;SmackCcsClient&quot;);
-
- private static final String GCM_SERVER = &quot;gcm.googleapis.com&quot;;
- private static final int GCM_PORT = 5235;
-
- private static final String GCM_ELEMENT_NAME = &quot;gcm&quot;;
- private static final String GCM_NAMESPACE = &quot;google:mobile:data&quot;;
-
- static {
-
- ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE,
- new PacketExtensionProvider() {
- &#64;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.
- *
- * &#64;return true if the message has been successfully sent.
- */
- public boolean sendDownstreamMessage(String jsonRequest) throws
- NotConnectedException {
- if (!connectionDraining) {
- send(jsonRequest);
- return true;
- }
- logger.info(&quot;Dropping downstream message since the connection is draining&quot;);
- return false;
- }
-
- /**
- * Returns a random message id to uniquely identify a message.
- *
- * &lt;p&gt;Note: This is generated by a pseudo random number generator for
- * illustration purpose, and is not guaranteed to be unique.
- */
- public String nextMessageId() {
- return &quot;m-&quot; + 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.
- *
- * &lt;p&gt;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&lt;String, Object&gt; jsonObject) {
- // PackageName of the application that sent this message.
- String category = (String) jsonObject.get(&quot;category&quot;);
- String from = (String) jsonObject.get(&quot;from&quot;);
- &#64;SuppressWarnings(&quot;unchecked&quot;)
- Map&lt;String, String&gt; payload = (Map&lt;String, String&gt;) jsonObject.get(&quot;data&quot;);
- payload.put(&quot;ECHO&quot;, &quot;Application: &quot; + category);
-
- // Send an ECHO response back
- String echo = createJsonMessage(from, nextMessageId(), payload,
- &quot;echo:CollapseKey&quot;, null, false);
-
- try {
- sendDownstreamMessage(echo);
- } catch (NotConnectedException e) {
- logger.log(Level.WARNING, &quot;Not connected anymore, echo message is
- not sent&quot;, e);
- }
- }
-
- /**
- * Handles an ACK.
- *
- * &lt;p&gt;Logs a {@code INFO} message, but subclasses could override it to
- * properly handle ACKs.
- */
- protected void handleAckReceipt(Map&lt;String, Object&gt; jsonObject) {
- String messageId = (String) jsonObject.get(&quot;message_id&quot;);
- String from = (String) jsonObject.get(&quot;from&quot;);
- logger.log(Level.INFO, &quot;handleAckReceipt() from: &quot; + from + &quot;,
- messageId: &quot; + messageId);
- }
-
- /**
- * Handles a NACK.
- *
- * &lt;p&gt;Logs a {@code INFO} message, but subclasses could override it to
- * properly handle NACKs.
- */
- protected void handleNackReceipt(Map&lt;String, Object&gt; jsonObject) {
- String messageId = (String) jsonObject.get(&quot;message_id&quot;);
- String from = (String) jsonObject.get(&quot;from&quot;);
- logger.log(Level.INFO, &quot;handleNackReceipt() from: &quot; + from + &quot;,
- messageId: &quot; + messageId);
- }
-
- protected void handleControlMessage(Map&lt;String, Object&gt; jsonObject) {
- logger.log(Level.INFO, &quot;handleControlMessage(): &quot; + jsonObject);
- String controlType = (String) jsonObject.get(&quot;control_type&quot;);
- if (&quot;CONNECTION_DRAINING&quot;.equals(controlType)) {
- connectionDraining = true;
- } else {
- logger.log(Level.INFO, &quot;Unrecognized control type: %s. This could
- happen if new features are &quot; + &quot;added to the CCS protocol.&quot;,
- controlType);
- }
- }
-
- /**
- * Creates a JSON encoded GCM message.
- *
- * &#64;param to RegistrationId of the target device (Required).
- * &#64;param messageId Unique messageId for which CCS sends an
- * &quot;ack/nack&quot; (Required).
- * &#64;param payload Message content intended for the application. (Optional).
- * &#64;param collapseKey GCM collapse_key parameter (Optional).
- * &#64;param timeToLive GCM time_to_live parameter (Optional).
- * &#64;param delayWhileIdle GCM delay_while_idle parameter (Optional).
- * &#64;return JSON encoded GCM message.
- */
- public static String createJsonMessage(String to, String messageId,
- Map&lt;String, String&gt; payload, String collapseKey, Long timeToLive,
- Boolean delayWhileIdle) {
- Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
- message.put(&quot;to&quot;, to);
- if (collapseKey != null) {
- message.put(&quot;collapse_key&quot;, collapseKey);
- }
- if (timeToLive != null) {
- message.put(&quot;time_to_live&quot;, timeToLive);
- }
- if (delayWhileIdle != null &amp;&amp; delayWhileIdle) {
- message.put(&quot;delay_while_idle&quot;, true);
- }
- message.put(&quot;message_id&quot;, messageId);
- message.put(&quot;data&quot;, payload);
- return JSONValue.toJSONString(message);
- }
-
- /**
- * Creates a JSON encoded ACK message for an upstream message received
- * from an application.
- *
- * &#64;param to RegistrationId of the device who sent the upstream message.
- * &#64;param messageId messageId of the upstream message to be acknowledged to CCS.
- * &#64;return JSON encoded ack.
- */
- protected static String createJsonAck(String to, String messageId) {
- Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
- message.put(&quot;message_type&quot;, &quot;ack&quot;);
- message.put(&quot;to&quot;, to);
- message.put(&quot;message_id&quot;, messageId);
- return JSONValue.toJSONString(message);
- }
-
- /**
- * Connects to GCM Cloud Connection Server using the supplied credentials.
- *
- * &#64;param senderId Your GCM project number
- * &#64;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() {
-
- &#64;Override
- public void processPacket(Packet packet) {
- logger.log(Level.INFO, &quot;Received: &quot; + packet.toXML());
- Message incomingMessage = (Message) packet;
- GcmPacketExtension gcmPacket =
- (GcmPacketExtension) incomingMessage.
- getExtension(GCM_NAMESPACE);
- String json = gcmPacket.getJson();
- try {
- &#64;SuppressWarnings(&quot;unchecked&quot;)
- Map&lt;String, Object&gt; jsonObject =
- (Map&lt;String, Object&gt;) JSONValue.
- parseWithException(json);
-
- // present for &quot;ack&quot;/&quot;nack&quot;, null otherwise
- Object messageType = jsonObject.get(&quot;message_type&quot;);
-
- if (messageType == null) {
- // Normal upstream data message
- handleUpstreamMessage(jsonObject);
-
- // Send ACK to CCS
- String messageId = (String) jsonObject.get(&quot;message_id&quot;);
- String from = (String) jsonObject.get(&quot;from&quot;);
- String ack = createJsonAck(from, messageId);
- send(ack);
- } else if (&quot;ack&quot;.equals(messageType.toString())) {
- // Process Ack
- handleAckReceipt(jsonObject);
- } else if (&quot;nack&quot;.equals(messageType.toString())) {
- // Process Nack
- handleNackReceipt(jsonObject);
- } else if (&quot;control&quot;.equals(messageType.toString())) {
- // Process control message
- handleControlMessage(jsonObject);
- } else {
- logger.log(Level.WARNING,
- &quot;Unrecognized message type (%s)&quot;,
- messageType.toString());
- }
- } catch (ParseException e) {
- logger.log(Level.SEVERE, &quot;Error parsing JSON &quot; + json, e);
- } catch (Exception e) {
- logger.log(Level.SEVERE, &quot;Failed to process packet&quot;, e);
- }
- }
- }, new PacketTypeFilter(Message.class));
-
- // Log all outgoing packets
- connection.addPacketInterceptor(new PacketInterceptor() {
- &#64;Override
- public void interceptPacket(Packet packet) {
- logger.log(Level.INFO, &quot;Sent: {0}&quot;, packet.toXML());
- }
- }, new PacketTypeFilter(Message.class));
-
- connection.login(senderId + &quot;&#64;gcm.googleapis.com&quot;, apiKey);
- }
-
- public static void main(String[] args) throws Exception {
- final long senderId = 1234567890L; // your GCM sender id
- final String password = &quot;Your API key&quot;;
-
- SmackCcsClient ccsClient = new SmackCcsClient();
-
- ccsClient.connect(senderId, password);
-
- // Send a sample hello downstream message to a device.
- String toRegId = &quot;RegistrationIdOfTheTargetDevice&quot;;
- String messageId = ccsClient.nextMessageId();
- Map&lt;String, String&gt; payload = new HashMap&lt;String, String&gt;();
- payload.put(&quot;Hello&quot;, &quot;World&quot;);
- payload.put(&quot;CCS&quot;, &quot;Dummy Message&quot;);
- payload.put(&quot;EmbeddedMessageId&quot;, messageId);
- String collapseKey = &quot;sample&quot;;
- 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;
- }
-
- &#64;Override
- public String toXML() {
- return String.format(&quot;&lt;%s xmlns=\&quot;%s\&quot;&gt;%s&lt;/%s&gt;&quot;,
- 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 {
-
- &#64;Override
- public void connected(XMPPConnection xmppConnection) {
- logger.info(&quot;Connected.&quot;);
- }
-
- &#64;Override
- public void authenticated(XMPPConnection xmppConnection) {
- logger.info(&quot;Authenticated.&quot;);
- }
-
- &#64;Override
- public void reconnectionSuccessful() {
- logger.info(&quot;Reconnecting..&quot;);
- }
-
- &#64;Override
- public void reconnectionFailed(Exception e) {
- logger.log(Level.INFO, &quot;Reconnection failed.. &quot;, e);
- }
-
- &#64;Override
- public void reconnectingIn(int seconds) {
- logger.log(Level.INFO, &quot;Reconnecting in %d secs&quot;, seconds);
- }
-
- &#64;Override
- public void connectionClosedOnError(Exception e) {
- logger.info(&quot;Connection closed on error.&quot;);
- }
-
- &#64;Override
- public void connectionClosed() {
- logger.info(&quot;Connection closed.&quot;);
- }
- }
-}</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 = (&quot;&lt;message&gt;&lt;gcm xmlns='google:mobile:data'&gt;{1}&lt;/gcm&gt;&lt;/message&gt;&quot;)
- 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 &gt; 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>