Home | History | Annotate | Download | only in gcm
      1 page.title=GCM Cloud Connection Server (XMPP)
      2 @jd:body
      3 
      4 <div id="qv-wrapper">
      5 <div id="qv">
      6 
      7 
      8 <h2>In this document</h2>
      9 
     10 <ol class="toc">
     11   <li><a href="#connecting">Establishing a Connection</a>
     12     <ol class="toc">
     13       <li><a href="#auth">Authentication</a></li>
     14       </ol>
     15       </li>
     16     <li><a href="#format">Message Format</a>
     17       <ol class="toc">
     18         <li><a href="#request">Request format</a></li>
     19         <li><a href="#response">Response format</a></li>
     20       </ol>
     21       </li>
     22   <li><a href="#upstream">Upstream Messages</a>
     23     <ol>
     24       <li><a href="#receipts">Receive return receipts</a></li>
     25     </ol>
     26   </li>
     27   <li><a href="#flow">Flow Control</a> </li>
     28   <li><a href="#implement">Implementing an XMPP-based App Server</a>
     29     <ol class="toc">
     30       <li><a href="#smack">Java sample using the Smack library</a></li>
     31       <li><a href="#python">Python sample</a></li>
     32     </ol>
     33   </li>
     34 </ol>
     35 
     36 <h2>See Also</h2>
     37 
     38 <ol class="toc">
     39 <li><a href="{@docRoot}google/gcm/http.html">HTTP</a></li>
     40 <li><a href="{@docRoot}google/gcm/gs.html">Getting Started</a></li>
     41 <li><a href="{@docRoot}google/gcm/server.html">Implementing GCM Server</a></li>
     42 <li><a href="{@docRoot}google/gcm/client.html">Implementing GCM Client</a></li>
     43 </ol>
     44 
     45 </div>
     46 </div>
     47 
     48 <p>The GCM Cloud Connection Server (CCS) is an XMPP endpoint that provides a
     49 persistent, asynchronous, bidirectional connection to Google servers. The
     50 connection can be used to send and receive messages between your server and
     51 your users' GCM-connected devices.</p>
     52 
     53 <p>You can continue to use the HTTP request mechanism to send messages to GCM
     54 servers, side-by-side with CCS which uses XMPP. Some of the benefits of CCS include:</p>
     55 
     56 <ul>
     57   <li>The asynchronous nature of XMPP allows you to send more messages with fewer
     58 resources.</li>
     59   <li>Communication is bidirectional&mdash;not only can your server send messages
     60 to the device, but the device can send messages back to your server.</li>
     61   <li>The device can send messages back using the same connection used for receiving,
     62 thereby improving battery life.</li>
     63 </ul>
     64 
     65 <p>The upstream messaging (device-to-cloud) feature of CCS is part of the Google
     66 Play services platform. Upstream messaging is available through the
     67 <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
     68 {@code GoogleCloudMessaging}</a>
     69 APIs. For examples, see
     70 <a href="#implement">Implementing an XMPP-based App Server</a>.</p>
     71 
     72 <p class="note"><strong>Note:</strong> See
     73 <a href="server.html#params">Implementing GCM Server</a> for a list of all the message
     74 parameters and which connection server(s) supports them.</p>
     75 
     76 <h2 id="connecting">Establishing a Connection</h2>
     77 
     78 <p>CCS just uses XMPP as an authenticated transport layer, so you can use most
     79 XMPP libraries to manage the connection. For an example, see <a href="#smack">
     80 Java sample using the Smack library</a>.</p>
     81 
     82 <p>The CCS XMPP endpoint runs at {@code gcm.googleapis.com:5235}. When testing
     83 functionality (with non-production users), you should instead connect to
     84 {@code gcm-preprod.googleapis.com:5236} (note the different port). Regular
     85 testing on preprod (a smaller environment where the latest CCS builds run) is
     86 beneficial both for isolating real users from test code, as well as for early
     87 detection of unexpected behavior changes. Note that a connection receives upstream
     88 messages destined for its GCM sender ID, regardless of which environment (gcm or
     89 gcm-preprod) it is connected to. Therefore, test code connecting to
     90 {@code gcm-preprod.googleapis.com:5236} should use a different GCM sender ID to
     91 avoid upstream messages from production traffic being sent over test connections.</p>
     92 
     93 <p>The connection has two important requirements:</p>
     94 
     95 <ul>
     96   <li>You must initiate a Transport Layer Security (TLS) connection. Note that
     97   CCS doesn't currently support the <a href="http://xmpp.org/rfcs/rfc3920.html"
     98   class="external-link" target="_android">STARTTLS extension</a>.</li>
     99   <li>CCS requires a SASL PLAIN authentication mechanism using
    100   {@code &lt;your_GCM_Sender_Id&gt;&#64;gcm.googleapis.com} (GCM sender ID)
    101   and the API key as the password, where the sender ID and API key are the same
    102   as described in <a href="gs.html">Getting Started</a>.</li>
    103 </ul>
    104 
    105 <p>If at any point the connection fails, you should immediately reconnect.
    106 There is no need to back off after a disconnect that happens after
    107 authentication.</p>
    108 
    109 <h3 id="auth">Authentication</h3>
    110 
    111 <p>The following snippets illustrate how to perform authentication in CCS.</p>
    112 <h4>Client</h4>
    113 <pre>&lt;stream:stream to=&quot;gcm.googleapis.com&quot; 
    114         version=&quot;1.0&quot; xmlns=&quot;jabber:client&quot; 
    115         xmlns:stream=&quot;http://etherx.jabber.org/streams"/>;
    116 </pre>
    117 <h4>Server</h4>
    118 <pre>&lt;str:features xmlns:str=&quot;http://etherx.jabber.org/streams">;
    119  &lt;mechanisms xmlns=&quot;urn:ietf:params:xml:ns:xmpp-sasl&quot;&gt;
    120    &lt;mechanism&gt;X-OAUTH2&lt;/mechanism&gt;
    121    &lt;mechanism&gt;X-GOOGLE-TOKEN&lt;/mechanism&gt;
    122    &lt;mechanism&gt;PLAIN&lt;/mechanism&gt;
    123  &lt;/mechanisms&gt;
    124 &lt;/str:features&gt;
    125 </pre>
    126 
    127 <h4>Client</h4>
    128 <pre>&lt;auth mechanism=&quot;PLAIN&quot;
    129 xmlns=&quot;urn:ietf:params:xml:ns:xmpp-sasl&quot;&gt;MTI2MjAwMzQ3OTMzQHByb2plY3RzLmdjbS5hb
    130 mFTeUIzcmNaTmtmbnFLZEZiOW1oekNCaVlwT1JEQTJKV1d0dw==&lt;/auth&gt;
    131 </pre>
    132 
    133 <h4>Server</h4>
    134 <pre>&lt;success xmlns=&quot;urn:ietf:params:xml:ns:xmpp-sasl&quot;/&gt;</pre>
    135 
    136 <h2 id="format">Message Format</h2>
    137 <p>Once the XMPP connection is established, CCS and your server use normal XMPP
    138 <code>&lt;message&gt;</code> stanzas to send JSON-encoded messages back and
    139 forth. The body of the <code>&lt;message&gt;</code> must be:</p>
    140 <pre>
    141 &lt;gcm xmlns:google:mobile:data&gt;
    142     <em>JSON payload</em>
    143 &lt;/gcm&gt;
    144 </pre>
    145 
    146 <p>The JSON payload for regular GCM messages is similar to
    147 <a href="http.html#request">what the GCM http endpoint uses</a>, with these
    148 exceptions:</p>
    149 <ul>
    150   <li>There is no support for multiple recipients.</li>
    151   <li>{@code to} is used instead of {@code registration_ids}.</li>
    152   <li>CCS adds the field {@code message_id}, which is required. This ID uniquely
    153 identifies the message in an XMPP connection. The ACK or NACK from CCS uses the
    154 {@code message_id} to identify a message sent from 3rd-party app servers to CCS.
    155 Therefore, it's important that this {@code message_id} not only be unique (per
    156 sender ID), but always present.</li>
    157 </ul>
    158 
    159 <p>In addition to regular GCM messages, control messages are sent, indicated by
    160 the {@code message_type} field in the JSON object. The value can be either
    161 'ack' or 'nack', or 'control' (see formats below). Any GCM message with an
    162 unknown {@code message_type} can be ignored by your server.</p>
    163 
    164 <p>For each device message your app server receives from CCS, it needs to send
    165 an ACK message.
    166 It never needs to send a NACK message. If you don't send an ACK for a message,
    167 CCS will just resend it.
    168 </p>
    169 <p>CCS also sends an ACK or NACK for each server-to-device message. If you do not
    170 receive either, it means that the TCP connection was closed in the middle of the
    171 operation and your server needs to resend the messages. See
    172 <a href="#flow">Flow Control</a> for details.
    173 </p>
    174 
    175 <p class="note"><strong>Note:</strong> See
    176 <a href="server.html#params">Implementing GCM Server</a> for a list of all the message
    177 parameters and which connection server(s) supports them.</p>
    178 
    179 <h3 id="request">Request format</h3>
    180 
    181 <p>Here is an XMPP stanza containing the JSON message from a 3rd-party app server to CCS:
    182 
    183 </p>
    184 <pre>&lt;message id=&quot;&quot;&gt;
    185   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
    186   {
    187       &quot;to&quot;:&quot;REGISTRATION_ID&quot;,  // &quot;to&quot; replaces &quot;registration_ids&quot;
    188       &quot;message_id&quot;:&quot;m-1366082849205&quot; // new required field
    189       &quot;data&quot;:
    190       {
    191           &quot;hello&quot;:&quot;world&quot;,
    192       }
    193       &quot;time_to_live&quot;:&quot;600&quot;,
    194       &quot;delay_while_idle&quot;: true/false,
    195       &quot;delivery_receipt_requested&quot;: true/false
    196   }
    197   &lt;/gcm&gt;
    198 &lt;/message&gt;
    199 </pre>
    200 
    201 <h3 id="response">Response format</h3>
    202 
    203 <p>A CCS response can have 3 possible forms. The first one is a regular 'ack'
    204 message. But when the response contains an error, there are 2
    205 different forms the message can take, described below.</p>
    206 
    207 <h4 id="ack">ACK message</h4>
    208 
    209 <p>Here is an XMPP stanza containing the ACK/NACK message from CCS to 3rd-party app server:
    210 </p>
    211 <pre>&lt;message id=&quot;&quot;&gt;
    212   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
    213   {
    214       &quot;from&quot;:&quot;REGID&quot;,
    215       &quot;message_id&quot;:&quot;m-1366082849205&quot;
    216       &quot;message_type&quot;:&quot;ack&quot;
    217   }
    218   &lt;/gcm&gt;
    219 &lt;/message&gt;
    220 </pre>
    221 
    222 <h4 id="nack">NACK message</h4>
    223 
    224 <p>A NACK error is a regular XMPP message in which the {@code message_type} status
    225 message is &quot;nack&quot;. A NACK message contains:</p>
    226 <ul>
    227 <li>Nack error code.</li>
    228 <li>Nack error description.</li>
    229 </ul>
    230 
    231 <p>Below are some examples.</p>
    232 
    233 <p>Bad registration:</p>
    234 
    235 <pre>&lt;message&gt;
    236   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
    237   {
    238     &quot;message_type&quot;:&quot;nack&quot;,
    239     &quot;message_id&quot;:&quot;msgId1&quot;,
    240     &quot;from&quot;:&quot;SomeInvalidRegistrationId&quot;,
    241     &quot;error&quot;:&quot;BAD_REGISTRATION&quot;,
    242     &quot;error_description&quot;:&quot;Invalid token on 'to' field: SomeInvalidRegistrationId&quot;
    243   }
    244   &lt;/gcm&gt;
    245 &lt;/message&gt;</pre>
    246 
    247 <p>Invalid JSON:</p>
    248 
    249 <pre>&lt;message&gt;
    250  &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
    251  {
    252    &quot;message_type&quot;:&quot;nack&quot;,
    253    &quot;message_id&quot;:&quot;msgId1&quot;,
    254    &quot;from&quot;:&quot;APA91bHFOtaQGSwupt5l1og&quot;,
    255    &quot;error&quot;:&quot;INVALID_JSON&quot;,
    256    &quot;error_description&quot;:&quot;InvalidJson: JSON_TYPE_ERROR : Field \&quot;time_to_live\&quot; must be a JSON java.lang.Number: abc&quot;
    257  }
    258  &lt;/gcm&gt;
    259 &lt;/message&gt;
    260 </pre>
    261 
    262 <p>Quota exceeded:</p>
    263 
    264 <pre>&lt;message&gt;
    265  &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
    266  {
    267    &quot;message_type&quot;:&quot;nack&quot;,
    268    &quot;message_id&quot;:&quot;msgId1&quot;,
    269    &quot;from&quot;:&quot;APA91bHFOtaQGSwupt5l1og&quot;,
    270    &quot;error&quot;:&quot;QUOTA_EXCEEDED&quot;,
    271    &quot;error_description&quot;:&quot;Short-term downstream quota exceeded for this registration id&quot;
    272  }
    273  &lt;/gcm&gt;
    274 &lt;/message&gt;
    275 </pre>
    276 
    277 
    278 <p>The following table lists NACK error codes. Unless otherwise
    279 indicated, a NACKed message should not be retried. Unexpected NACK error codes
    280 should be treated the same as {@code INTERNAL_SERVER_ERROR}.</p>
    281 
    282 <p class="table-caption" id="table1">
    283   <strong>Table 1.</strong> NACK error codes.</p>
    284 
    285 <table border="1">
    286 <tr>
    287 <th>Error Code</th>
    288 <th>Description</th>
    289 </tr>
    290 <tr>
    291 <td>{@code BAD_ACK}</td>
    292 <td>The ACK message is improperly formed.</td>
    293 </tr>
    294 <tr>
    295 <td>{@code BAD_REGISTRATION}</td>
    296 <td>The device has a registration ID, but it's invalid or expired.</td>
    297 </tr>
    298 <tr>
    299 <td>{@code CONNECTION_DRAINING}</td>
    300 <td>The message couldn't be processed because the connection is draining. The
    301 message should be immediately retried over another connection.</td>
    302 </tr>
    303 <tr>
    304 <td>{@code DEVICE_UNREGISTERED}</td>
    305 <td>The device is not registered.</td>
    306 </tr>
    307 <tr>
    308 <td>{@code INTERNAL_SERVER_ERROR}</td>
    309 <td>The server encountered an error while trying to process the request.</td>
    310 </tr>
    311 <tr>
    312 <td>{@code INVALID_JSON}</td>
    313 <td>The JSON message payload is not valid.</td>
    314 </tr>
    315 <tr>
    316 <td>{@code QUOTA_EXCEEDED}</td>
    317 <td>The rate of messages to a particular registration ID (in other words, to a
    318 sender/device pair) is too high. If you want to retry the message, try using a slower
    319 rate.</td>
    320 </tr>
    321 <tr>
    322   <td>{@code SERVICE_UNAVAILABLE}</td>
    323   <td>CCS is not currently able to process the message. The
    324     message should be retried over the same connection using exponential backoff
    325     with an initial delay of 1 second.</td>
    326 </tr>
    327 </table>
    328 
    329 <h4 id="stanza">Stanza error</h4>
    330 
    331 <p>You can also get a stanza error in certain cases.
    332 A stanza error contains:</p>
    333 <ul>
    334 <li>Stanza error code.</li>
    335 <li>Stanza error description (free text).</li>
    336 </ul>
    337 <p>For example:</p>
    338 
    339 <pre>&lt;message id=&quot;3&quot; type=&quot;error&quot; to=&quot;123456789 (a] gcm.googleapis.com/ABC&quot;&gt;
    340   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
    341      {&quot;random&quot;: &quot;text&quot;}
    342   &lt;/gcm&gt;
    343   &lt;error code=&quot;400&quot; type=&quot;modify&quot;&gt;
    344     &lt;bad-request xmlns=&quot;urn:ietf:params:xml:ns:xmpp-stanzas&quot;/&gt;
    345     &lt;text xmlns=&quot;urn:ietf:params:xml:ns:xmpp-stanzas&quot;&gt;
    346       InvalidJson: JSON_PARSING_ERROR : Missing Required Field: message_id\n
    347     &lt;/text&gt;
    348   &lt;/error&gt;
    349 &lt;/message&gt;
    350 </pre>
    351 
    352 <h4 id="control">Control messages</h4>
    353 
    354 <p>Periodically, CCS needs to close down a connection to perform load balancing. Before it
    355 closes the connection, CCS sends a {@code CONNECTION_DRAINING} message to indicate that the connection is being drained
    356 and will be closed soon. "Draining" refers to shutting off the flow of messages coming into a
    357 connection, but allowing whatever is already in the pipeline to continue. When you receive
    358 a {@code CONNECTION_DRAINING} message, you should immediately begin sending messages to another CCS
    359 connection, opening a new connection if necessary. You should, however, keep the original
    360 connection open and continue receiving messages that may come over the connection (and
    361 ACKing them)&mdash;CCS will handle initiating a connection close when it is ready.</p>
    362 
    363 <p>The {@code CONNECTION_DRAINING} message looks like this:</p>
    364 <pre>&lt;message&gt;
    365   &lt;data:gcm xmlns:data=&quot;google:mobile:data&quot;&gt;
    366   {
    367     &quot;message_type&quot;:&quot;control&quot;
    368     &quot;control_type&quot;:&quot;CONNECTION_DRAINING&quot;
    369   }
    370   &lt;/data:gcm&gt;
    371 &lt;/message&gt;</pre>
    372 
    373 <p>{@code CONNECTION_DRAINING} is currently the only {@code control_type} supported.</p>
    374 
    375 <h2 id="upstream">Upstream Messages</h2>
    376 
    377 <p>Using CCS and the
    378 <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
    379 {@code GoogleCloudMessaging}</a>
    380 API, you can send messages from a user's device to the cloud.</p>
    381 
    382 <p>Here is how you send an upstream message using the
    383 <a href="{@docRoot}reference/com/google/android/gms/gcm/GoogleCloudMessaging.html">
    384 {@code GoogleCloudMessaging}</a>
    385 API. For a complete example, see <a href="client.html">Implementing GCM Client</a>:</p>
    386 
    387 <pre>GoogleCloudMessaging gcm = GoogleCloudMessaging.get(context);
    388 String GCM_SENDER_ID = "Your-Sender-ID";
    389 AtomicInteger msgId = new AtomicInteger();
    390 String id = Integer.toString(msgId.incrementAndGet());
    391 Bundle data = new Bundle();
    392 // Bundle data consists of a key-value pair
    393 data.putString("hello", "world");
    394 // "time to live" parameter
    395 // This is optional. It specifies a value in seconds up to 24 hours.
    396 int ttl = [0 seconds, 24 hours]
    397 
    398 gcm.send(GCM_SENDER_ID + "&#64;gcm.googleapis.com", id, ttl, data);
    399 </pre>
    400 
    401 <p>This call generates the necessary XMPP stanza for sending the upstream message.
    402 The message goes from the app on the device to CCS to the 3rd-party app server.
    403 The stanza has the following format:</p>
    404 
    405 <pre>&lt;message id=&quot;&quot;&gt;
    406   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
    407   {
    408       &quot;category&quot;:&quot;com.example.yourapp&quot;, // to know which app sent it
    409       &quot;data&quot;:
    410       {
    411           &quot;hello&quot;:&quot;world&quot;,
    412       },
    413       &quot;message_id&quot;:&quot;m-123&quot;,
    414       &quot;from&quot;:&quot;REGID&quot;
    415   }
    416   &lt;/gcm&gt;
    417 &lt;/message&gt;</pre>
    418 
    419 <p>Here is the format of the ACK expected by CCS from 3rd-party app servers in
    420 response to the above message:</p>
    421 
    422 <pre>&lt;message id=&quot;&quot;&gt;
    423   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
    424   {
    425       &quot;to&quot;:&quot;REGID&quot;,
    426       &quot;message_id&quot;:&quot;m-123&quot;
    427       &quot;message_type&quot;:&quot;ack&quot;
    428   }
    429   &lt;/gcm&gt;
    430 &lt;/message&gt;</pre>
    431 
    432 <h3 id="receipts">Receive return receipts</h3>
    433 
    434 <p>You can use upstream messaging to get receipt notifications, confirming
    435 that a given message was sent to a device. Your 3rd-party app server receives the receipt
    436 notification from CCS once the message has been sent to the device.</p>
    437 
    438 <p>To enable this feature, the message your 3rd-party app server sends to CCS must include
    439 a field called <code>&quot;delivery_receipt_requested&quot;</code>. When this field is set to
    440 <code>true</code>, CCS sends a return receipt. Here is an XMPP stanza containing a JSON
    441 message with <code>&quot;delivery_receipt_requested&quot;</code> set to <code>true</code>:</p>
    442 
    443 <pre>&lt;message id=&quot;&quot;&gt;
    444   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
    445   {
    446       &quot;to&quot;:&quot;REGISTRATION_ID&quot;,
    447       &quot;message_id&quot;:&quot;m-1366082849205&quot;
    448       &quot;data&quot;:
    449       {
    450           &quot;hello&quot;:&quot;world&quot;,
    451       }
    452       &quot;time_to_live&quot;:&quot;600&quot;,
    453       &quot;delay_while_idle&quot;: true,
    454       <strong>&quot;delivery_receipt_requested&quot;: true</strong>
    455   }
    456   &lt;/gcm&gt;
    457 &lt;/message&gt;
    458 </pre>
    459 
    460 <p>Here is an example of a receipt notification message that CCS sends back to your 3rd-party
    461 app server:</p>
    462 
    463 </p>
    464 <pre>&lt;message id=&quot;&quot;&gt;
    465   &lt;gcm xmlns=&quot;google:mobile:data&quot;&gt;
    466   {
    467       &quot;category&quot;:&quot;com.example.yourapp&quot;, // to know which app sent it
    468       &quot;data&quot;:
    469       {
    470          &#x201c;message_status&quot;:&quot;MESSAGE_SENT_TO_DEVICE&quot;,
    471          &#x201c;original_message_id&#x201d;:&#x201d;m-1366082849205&#x201d;
    472          &#x201c;device_registration_id&#x201d;: &#x201c;REGISTRATION_ID&#x201d;
    473       },
    474       &quot;message_id&quot;:&quot;dr2:m-1366082849205&quot;,
    475       &quot;message_type&quot;:&quot;receipt&quot;,
    476       &quot;from&quot;:&quot;gcm.googleapis.com&quot;
    477   }
    478   &lt;/gcm&gt;
    479 &lt;/message&gt;</pre>
    480 
    481 <p>Note the following:</p>
    482 
    483 <ul>
    484   <li>The {@code &quot;message_type&quot;} is set to {@code &quot;receipt&quot;}.
    485   <li>The {@code &quot;message_status&quot;} is set to {@code &quot;MESSAGE_SENT_TO_DEVICE&quot;},
    486   indicating that the message was delivered. Notice that in this case,
    487 {@code &quot;message_status&quot;} is not a field but rather part of the data payload.</li>
    488   <li>The receipt message ID consists of the original message ID, but with a
    489 <code>dr:</code> prefix. Your 3rd-party app server must send an ACK back with this ID,
    490 which in this example is {@code dr2:m-1366082849205}.</li>
    491   <li>The original message ID and status are inside the
    492 {@code &quot;data&quot;} field.</li>
    493 </ul>
    494 
    495 <h2 id="flow">Flow Control</h2>
    496 
    497 <p>Every message sent to CCS receives either an ACK or a NACK response. Messages
    498 that haven't received one of these responses are considered pending. If the pending
    499 message count reaches 100, the 3rd-party app server should stop sending new messages
    500 and wait for CCS to acknowledge some of the existing pending messages as illustrated in
    501 figure 1:</p>
    502 
    503 <img src="{@docRoot}images/gcm/CCS-ack.png">
    504 
    505 <p class="img-caption">
    506   <strong>Figure 1.</strong> Message/ack flow.
    507 </p>
    508 
    509 <p>Conversely, to avoid overloading the 3rd-party app server, CCS will stop sending
    510 if there are too many unacknowledged messages. Therefore, the 3rd-party app server
    511 should "ACK" upstream messages, received from the client application via CCS, as soon as possible
    512 to maintain a constant flow of incoming messages. The aforementioned pending message limit doesn't
    513 apply to these ACKs. Even if the pending message count reaches 100, the 3rd-party app server
    514 should continue sending ACKs for messages received from CCS to avoid blocking delivery of new
    515 upstream messages.</p>
    516 
    517 <p>ACKs are only valid within the context of one connection. If the connection is
    518 closed before a message can be ACKed, the 3rd-party app server should wait for CCS
    519 to resend the upstream message before ACKing it again. Similarly, all pending messages for which an
    520 ACK/NACK was not received from CCS before the connection was closed should be sent again.
    521 </p>
    522 
    523 <h2 id="implement">Implementing an XMPP-based App Server</h2>
    524 
    525 <p>This section gives examples of implementing an app server that works with CCS.
    526 Note that a full GCM implementation requires a client-side implementation, in
    527 addition to the server. For more information, see <a href="client.html">
    528 Implementing GCM Client</a>.</a>
    529 
    530 <h3 id="smack">Java sample using the Smack library</h3>
    531 
    532 <p>Here is a sample app server written in Java, using the
    533 <a href="http://www.igniterealtime.org/projects/smack/">Smack</a> library.</p>
    534 
    535 <pre>import org.jivesoftware.smack.ConnectionConfiguration;
    536 import org.jivesoftware.smack.ConnectionConfiguration.SecurityMode;
    537 import org.jivesoftware.smack.ConnectionListener;
    538 import org.jivesoftware.smack.PacketInterceptor;
    539 import org.jivesoftware.smack.PacketListener;
    540 import org.jivesoftware.smack.SmackException;
    541 import org.jivesoftware.smack.SmackException.NotConnectedException;
    542 import org.jivesoftware.smack.XMPPConnection;
    543 import org.jivesoftware.smack.XMPPException;
    544 import org.jivesoftware.smack.filter.PacketTypeFilter;
    545 import org.jivesoftware.smack.packet.DefaultPacketExtension;
    546 import org.jivesoftware.smack.packet.Message;
    547 import org.jivesoftware.smack.packet.Packet;
    548 import org.jivesoftware.smack.packet.PacketExtension;
    549 import org.jivesoftware.smack.provider.PacketExtensionProvider;
    550 import org.jivesoftware.smack.provider.ProviderManager;
    551 import org.jivesoftware.smack.tcp.XMPPTCPConnection;
    552 import org.jivesoftware.smack.util.StringUtils;
    553 import org.json.simple.JSONValue;
    554 import org.json.simple.parser.ParseException;
    555 import org.xmlpull.v1.XmlPullParser;
    556 
    557 import java.io.IOException;
    558 import java.util.HashMap;
    559 import java.util.Map;
    560 import java.util.UUID;
    561 import java.util.logging.Level;
    562 import java.util.logging.Logger;
    563 
    564 import javax.net.ssl.SSLSocketFactory;
    565 
    566 /**
    567  * Sample Smack implementation of a client for GCM Cloud Connection Server. This
    568  * code can be run as a standalone CCS client.
    569  *
    570  * &lt;p&gt;For illustration purposes only.
    571  */
    572 public class SmackCcsClient {
    573 
    574     private static final Logger logger = Logger.getLogger(&quot;SmackCcsClient&quot;);
    575 
    576     private static final String GCM_SERVER = &quot;gcm.googleapis.com&quot;;
    577     private static final int GCM_PORT = 5235;
    578 
    579     private static final String GCM_ELEMENT_NAME = &quot;gcm&quot;;
    580     private static final String GCM_NAMESPACE = &quot;google:mobile:data&quot;;
    581 
    582     static {
    583 
    584         ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE,
    585             new PacketExtensionProvider() {
    586                 &#64;Override
    587                 public PacketExtension parseExtension(XmlPullParser parser) throws
    588                         Exception {
    589                     String json = parser.nextText();
    590                     return new GcmPacketExtension(json);
    591                 }
    592             });
    593     }
    594 
    595     private XMPPConnection connection;
    596 
    597     /**
    598      * Indicates whether the connection is in draining state, which means that it
    599      * will not accept any new downstream messages.
    600      */
    601     protected volatile boolean connectionDraining = false;
    602 
    603     /**
    604      * Sends a downstream message to GCM.
    605      *
    606      * &#64;return true if the message has been successfully sent.
    607      */
    608     public boolean sendDownstreamMessage(String jsonRequest) throws
    609             NotConnectedException {
    610         if (!connectionDraining) {
    611             send(jsonRequest);
    612             return true;
    613         }
    614         logger.info(&quot;Dropping downstream message since the connection is draining&quot;);
    615         return false;
    616     }
    617 
    618     /**
    619      * Returns a random message id to uniquely identify a message.
    620      *
    621      * &lt;p&gt;Note: This is generated by a pseudo random number generator for
    622      * illustration purpose, and is not guaranteed to be unique.
    623      */
    624     public String nextMessageId() {
    625         return &quot;m-&quot; + UUID.randomUUID().toString();
    626     }
    627 
    628     /**
    629      * Sends a packet with contents provided.
    630      */
    631     protected void send(String jsonRequest) throws NotConnectedException {
    632         Packet request = new GcmPacketExtension(jsonRequest).toPacket();
    633         connection.sendPacket(request);
    634     }
    635 
    636     /**
    637      * Handles an upstream data message from a device application.
    638      *
    639      * &lt;p&gt;This sample echo server sends an echo message back to the device.
    640      * Subclasses should override this method to properly process upstream messages.
    641      */
    642     protected void handleUpstreamMessage(Map&lt;String, Object&gt; jsonObject) {
    643         // PackageName of the application that sent this message.
    644         String category = (String) jsonObject.get(&quot;category&quot;);
    645         String from = (String) jsonObject.get(&quot;from&quot;);
    646         &#64;SuppressWarnings(&quot;unchecked&quot;)
    647         Map&lt;String, String&gt; payload = (Map&lt;String, String&gt;) jsonObject.get(&quot;data&quot;);
    648         payload.put(&quot;ECHO&quot;, &quot;Application: &quot; + category);
    649 
    650         // Send an ECHO response back
    651         String echo = createJsonMessage(from, nextMessageId(), payload,
    652                 &quot;echo:CollapseKey&quot;, null, false);
    653 
    654         try {
    655             sendDownstreamMessage(echo);
    656         } catch (NotConnectedException e) {
    657             logger.log(Level.WARNING, &quot;Not connected anymore, echo message is
    658                     not sent&quot;, e);
    659         }
    660     }
    661 
    662     /**
    663      * Handles an ACK.
    664      *
    665      * &lt;p&gt;Logs a {@code INFO} message, but subclasses could override it to
    666      * properly handle ACKs.
    667      */
    668     protected void handleAckReceipt(Map&lt;String, Object&gt; jsonObject) {
    669         String messageId = (String) jsonObject.get(&quot;message_id&quot;);
    670         String from = (String) jsonObject.get(&quot;from&quot;);
    671         logger.log(Level.INFO, &quot;handleAckReceipt() from: &quot; + from + &quot;,
    672                 messageId: &quot; + messageId);
    673     }
    674 
    675     /**
    676      * Handles a NACK.
    677      *
    678      * &lt;p&gt;Logs a {@code INFO} message, but subclasses could override it to
    679      * properly handle NACKs.
    680      */
    681     protected void handleNackReceipt(Map&lt;String, Object&gt; jsonObject) {
    682         String messageId = (String) jsonObject.get(&quot;message_id&quot;);
    683         String from = (String) jsonObject.get(&quot;from&quot;);
    684         logger.log(Level.INFO, &quot;handleNackReceipt() from: &quot; + from + &quot;,
    685                 messageId: &quot; + messageId);
    686     }
    687 
    688     protected void handleControlMessage(Map&lt;String, Object&gt; jsonObject) {
    689         logger.log(Level.INFO, &quot;handleControlMessage(): &quot; + jsonObject);
    690         String controlType = (String) jsonObject.get(&quot;control_type&quot;);
    691         if (&quot;CONNECTION_DRAINING&quot;.equals(controlType)) {
    692             connectionDraining = true;
    693         } else {
    694             logger.log(Level.INFO, &quot;Unrecognized control type: %s. This could
    695                     happen if new features are &quot; + &quot;added to the CCS protocol.&quot;,
    696                     controlType);
    697         }
    698     }
    699 
    700     /**
    701      * Creates a JSON encoded GCM message.
    702      *
    703      * &#64;param to RegistrationId of the target device (Required).
    704      * &#64;param messageId Unique messageId for which CCS will send an
    705      *         &quot;ack/nack&quot; (Required).
    706      * &#64;param payload Message content intended for the application. (Optional).
    707      * &#64;param collapseKey GCM collapse_key parameter (Optional).
    708      * &#64;param timeToLive GCM time_to_live parameter (Optional).
    709      * &#64;param delayWhileIdle GCM delay_while_idle parameter (Optional).
    710      * &#64;return JSON encoded GCM message.
    711      */
    712     public static String createJsonMessage(String to, String messageId,
    713             Map&lt;String, String&gt; payload, String collapseKey, Long timeToLive,
    714             Boolean delayWhileIdle) {
    715         Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
    716         message.put(&quot;to&quot;, to);
    717         if (collapseKey != null) {
    718             message.put(&quot;collapse_key&quot;, collapseKey);
    719         }
    720         if (timeToLive != null) {
    721             message.put(&quot;time_to_live&quot;, timeToLive);
    722         }
    723         if (delayWhileIdle != null &amp;&amp; delayWhileIdle) {
    724             message.put(&quot;delay_while_idle&quot;, true);
    725         }
    726       message.put(&quot;message_id&quot;, messageId);
    727       message.put(&quot;data&quot;, payload);
    728       return JSONValue.toJSONString(message);
    729     }
    730 
    731     /**
    732      * Creates a JSON encoded ACK message for an upstream message received
    733      * from an application.
    734      *
    735      * &#64;param to RegistrationId of the device who sent the upstream message.
    736      * &#64;param messageId messageId of the upstream message to be acknowledged to CCS.
    737      * &#64;return JSON encoded ack.
    738      */
    739         protected static String createJsonAck(String to, String messageId) {
    740         Map&lt;String, Object&gt; message = new HashMap&lt;String, Object&gt;();
    741         message.put(&quot;message_type&quot;, &quot;ack&quot;);
    742         message.put(&quot;to&quot;, to);
    743         message.put(&quot;message_id&quot;, messageId);
    744         return JSONValue.toJSONString(message);
    745     }
    746 
    747     /**
    748      * Connects to GCM Cloud Connection Server using the supplied credentials.
    749      *
    750      * &#64;param senderId Your GCM project number
    751      * &#64;param apiKey API Key of your project
    752      */
    753     public void connect(long senderId, String apiKey)
    754             throws XMPPException, IOException, SmackException {
    755         ConnectionConfiguration config =
    756                 new ConnectionConfiguration(GCM_SERVER, GCM_PORT);
    757         config.setSecurityMode(SecurityMode.enabled);
    758         config.setReconnectionAllowed(true);
    759         config.setRosterLoadedAtLogin(false);
    760         config.setSendPresence(false);
    761         config.setSocketFactory(SSLSocketFactory.getDefault());
    762 
    763         connection = new XMPPTCPConnection(config);
    764         connection.connect();
    765 
    766         connection.addConnectionListener(new LoggingConnectionListener());
    767 
    768         // Handle incoming packets
    769         connection.addPacketListener(new PacketListener() {
    770 
    771             &#64;Override
    772             public void processPacket(Packet packet) {
    773                 logger.log(Level.INFO, &quot;Received: &quot; + packet.toXML());
    774                 Message incomingMessage = (Message) packet;
    775                 GcmPacketExtension gcmPacket =
    776                         (GcmPacketExtension) incomingMessage.
    777                         getExtension(GCM_NAMESPACE);
    778                 String json = gcmPacket.getJson();
    779                 try {
    780                     &#64;SuppressWarnings(&quot;unchecked&quot;)
    781                     Map&lt;String, Object&gt; jsonObject =
    782                             (Map&lt;String, Object&gt;) JSONValue.
    783                             parseWithException(json);
    784 
    785                     // present for &quot;ack&quot;/&quot;nack&quot;, null otherwise
    786                     Object messageType = jsonObject.get(&quot;message_type&quot;);
    787 
    788                     if (messageType == null) {
    789                         // Normal upstream data message
    790                         handleUpstreamMessage(jsonObject);
    791 
    792                         // Send ACK to CCS
    793                         String messageId = (String) jsonObject.get(&quot;message_id&quot;);
    794                         String from = (String) jsonObject.get(&quot;from&quot;);
    795                         String ack = createJsonAck(from, messageId);
    796                         send(ack);
    797                     } else if (&quot;ack&quot;.equals(messageType.toString())) {
    798                           // Process Ack
    799                           handleAckReceipt(jsonObject);
    800                     } else if (&quot;nack&quot;.equals(messageType.toString())) {
    801                           // Process Nack
    802                           handleNackReceipt(jsonObject);
    803                     } else if (&quot;control&quot;.equals(messageType.toString())) {
    804                           // Process control message
    805                           handleControlMessage(jsonObject);
    806                     } else {
    807                           logger.log(Level.WARNING,
    808                                   &quot;Unrecognized message type (%s)&quot;,
    809                                   messageType.toString());
    810                     }
    811                 } catch (ParseException e) {
    812                     logger.log(Level.SEVERE, &quot;Error parsing JSON &quot; + json, e);
    813                 } catch (Exception e) {
    814                     logger.log(Level.SEVERE, &quot;Failed to process packet&quot;, e);
    815                 }
    816             }
    817         }, new PacketTypeFilter(Message.class));
    818 
    819         // Log all outgoing packets
    820         connection.addPacketInterceptor(new PacketInterceptor() {
    821             &#64;Override
    822                 public void interceptPacket(Packet packet) {
    823                     logger.log(Level.INFO, &quot;Sent: {0}&quot;, packet.toXML());
    824                 }
    825             }, new PacketTypeFilter(Message.class));
    826 
    827         connection.login(senderId + &quot;&#64;gcm.googleapis.com&quot;, apiKey);
    828     }
    829 
    830     public static void main(String[] args) throws Exception {
    831         final long senderId = 1234567890L; // your GCM sender id
    832         final String password = &quot;Your API key&quot;;
    833 
    834         SmackCcsClient ccsClient = new SmackCcsClient();
    835 
    836         ccsClient.connect(senderId, password);
    837 
    838         // Send a sample hello downstream message to a device.
    839         String toRegId = &quot;RegistrationIdOfTheTargetDevice&quot;;
    840         String messageId = ccsClient.nextMessageId();
    841         Map&lt;String, String&gt; payload = new HashMap&lt;String, String&gt;();
    842         payload.put(&quot;Hello&quot;, &quot;World&quot;);
    843         payload.put(&quot;CCS&quot;, &quot;Dummy Message&quot;);
    844         payload.put(&quot;EmbeddedMessageId&quot;, messageId);
    845         String collapseKey = &quot;sample&quot;;
    846         Long timeToLive = 10000L;
    847         String message = createJsonMessage(toRegId, messageId, payload,
    848                 collapseKey, timeToLive, true);
    849 
    850         ccsClient.sendDownstreamMessage(message);
    851     }
    852 
    853     /**
    854      * XMPP Packet Extension for GCM Cloud Connection Server.
    855      */
    856     private static final class GcmPacketExtension extends DefaultPacketExtension {
    857 
    858         private final String json;
    859 
    860         public GcmPacketExtension(String json) {
    861             super(GCM_ELEMENT_NAME, GCM_NAMESPACE);
    862             this.json = json;
    863         }
    864 
    865         public String getJson() {
    866             return json;
    867         }
    868 
    869         &#64;Override
    870         public String toXML() {
    871             return String.format(&quot;&lt;%s xmlns=\&quot;%s\&quot;&gt;%s&lt;/%s&gt;&quot;,
    872                     GCM_ELEMENT_NAME, GCM_NAMESPACE,
    873                     StringUtils.escapeForXML(json), GCM_ELEMENT_NAME);
    874         }
    875 
    876         public Packet toPacket() {
    877             Message message = new Message();
    878             message.addExtension(this);
    879             return message;
    880         }
    881     }
    882 
    883     private static final class LoggingConnectionListener
    884             implements ConnectionListener {
    885 
    886         &#64;Override
    887         public void connected(XMPPConnection xmppConnection) {
    888             logger.info(&quot;Connected.&quot;);
    889         }
    890 
    891         &#64;Override
    892         public void authenticated(XMPPConnection xmppConnection) {
    893             logger.info(&quot;Authenticated.&quot;);
    894         }
    895 
    896         &#64;Override
    897         public void reconnectionSuccessful() {
    898             logger.info(&quot;Reconnecting..&quot;);
    899         }
    900 
    901         &#64;Override
    902         public void reconnectionFailed(Exception e) {
    903             logger.log(Level.INFO, &quot;Reconnection failed.. &quot;, e);
    904         }
    905 
    906         &#64;Override
    907         public void reconnectingIn(int seconds) {
    908             logger.log(Level.INFO, &quot;Reconnecting in %d secs&quot;, seconds);
    909         }
    910 
    911         &#64;Override
    912         public void connectionClosedOnError(Exception e) {
    913             logger.info(&quot;Connection closed on error.&quot;);
    914         }
    915 
    916         &#64;Override
    917         public void connectionClosed() {
    918             logger.info(&quot;Connection closed.&quot;);
    919         }
    920     }
    921 }</pre>
    922 
    923 <h3 id="python">Python sample</h3>
    924 
    925 <p>Here is an example of a CCS app server written in Python. This sample echo
    926 server sends an initial message, and for every upstream message received, it sends
    927 a dummy response back to the application that sent the upstream message. This
    928 example illustrates how to connect, send, and receive GCM messages using XMPP. It
    929 shouldn't be used as-is on a production deployment.</p>
    930 
    931 <pre>
    932 #!/usr/bin/python
    933 import sys, json, xmpp, random, string
    934 
    935 SERVER = 'gcm.googleapis.com'
    936 PORT = 5235
    937 USERNAME = "Your GCM Sender Id"
    938 PASSWORD = "API Key"
    939 REGISTRATION_ID = "Registration Id of the target device"
    940 
    941 unacked_messages_quota = 100
    942 send_queue = []
    943 
    944 # Return a random alphanumerical id
    945 def random_id():
    946   rid = ''
    947   for x in range(8): rid += random.choice(string.ascii_letters + string.digits)
    948   return rid
    949 
    950 def message_callback(session, message):
    951   global unacked_messages_quota
    952   gcm = message.getTags('gcm')
    953   if gcm:
    954     gcm_json = gcm[0].getData()
    955     msg = json.loads(gcm_json)
    956     if not msg.has_key('message_type'):
    957       # Acknowledge the incoming message immediately.
    958       send({'to': msg['from'],
    959             'message_type': 'ack',
    960             'message_id': msg['message_id']})
    961       # Queue a response back to the server.
    962       if msg.has_key('from'):
    963         # Send a dummy echo response back to the app that sent the upstream message.
    964         send_queue.append({'to': msg['from'],
    965                            'message_id': random_id(),
    966                            'data': {'pong': 1}})
    967     elif msg['message_type'] == 'ack' or msg['message_type'] == 'nack':
    968       unacked_messages_quota += 1
    969 
    970 def send(json_dict):
    971   template = (&quot;&lt;message&gt;&lt;gcm xmlns='google:mobile:data'&gt;{1}&lt;/gcm&gt;&lt;/message&gt;&quot;)
    972   client.send(xmpp.protocol.Message(
    973       node=template.format(client.Bind.bound[0], json.dumps(json_dict))))
    974 
    975 def flush_queued_messages():
    976   global unacked_messages_quota
    977   while len(send_queue) and unacked_messages_quota &gt; 0:
    978     send(send_queue.pop(0))
    979     unacked_messages_quota -= 1
    980 
    981 client = xmpp.Client('gcm.googleapis.com', debug=['socket'])
    982 client.connect(server=(SERVER,PORT), secure=1, use_srv=False)
    983 auth = client.auth(USERNAME, PASSWORD)
    984 if not auth:
    985   print 'Authentication failed!'
    986   sys.exit(1)
    987 
    988 client.RegisterHandler('message', message_callback)
    989 
    990 send_queue.append({'to': REGISTRATION_ID,
    991                    'message_id': 'reg_id',
    992                    'data': {'message_destination': 'RegId',
    993                             'message_id': random_id()}})
    994 
    995 while True:
    996   client.Process(1)
    997   flush_queued_messages()</pre>
    998