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—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 <your_GCM_Sender_Id>@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><stream:stream to="gcm.googleapis.com" 114 version="1.0" xmlns="jabber:client" 115 xmlns:stream="http://etherx.jabber.org/streams"/> 116 </pre> 117 <h4>Server</h4> 118 <pre><str:features xmlns:str="http://etherx.jabber.org/streams"> 119 <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"> 120 <mechanism>X-OAUTH2</mechanism> 121 <mechanism>X-GOOGLE-TOKEN</mechanism> 122 <mechanism>PLAIN</mechanism> 123 </mechanisms> 124 </str:features> 125 </pre> 126 127 <h4>Client</h4> 128 <pre><auth mechanism="PLAIN" 129 xmlns="urn:ietf:params:xml:ns:xmpp-sasl">MTI2MjAwMzQ3OTMzQHByb2plY3RzLmdjbS5hb 130 mFTeUIzcmNaTmtmbnFLZEZiOW1oekNCaVlwT1JEQTJKV1d0dw==</auth> 131 </pre> 132 133 <h4>Server</h4> 134 <pre><success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/></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><message></code> stanzas to send JSON-encoded messages back and 139 forth. The body of the <code><message></code> must be:</p> 140 <pre> 141 <gcm xmlns:google:mobile:data> 142 <em>JSON payload</em> 143 </gcm> 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><message id=""> 185 <gcm xmlns="google:mobile:data"> 186 { 187 "to":"REGISTRATION_ID", // "to" replaces "registration_ids" 188 "message_id":"m-1366082849205" // new required field 189 "data": 190 { 191 "hello":"world", 192 } 193 "time_to_live":"600", 194 "delay_while_idle": true/false, 195 "delivery_receipt_requested": true/false 196 } 197 </gcm> 198 </message> 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><message id=""> 212 <gcm xmlns="google:mobile:data"> 213 { 214 "from":"REGID", 215 "message_id":"m-1366082849205" 216 "message_type":"ack" 217 } 218 </gcm> 219 </message> 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 "nack". 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><message> 236 <gcm xmlns="google:mobile:data"> 237 { 238 "message_type":"nack", 239 "message_id":"msgId1", 240 "from":"SomeInvalidRegistrationId", 241 "error":"BAD_REGISTRATION", 242 "error_description":"Invalid token on 'to' field: SomeInvalidRegistrationId" 243 } 244 </gcm> 245 </message></pre> 246 247 <p>Invalid JSON:</p> 248 249 <pre><message> 250 <gcm xmlns="google:mobile:data"> 251 { 252 "message_type":"nack", 253 "message_id":"msgId1", 254 "from":"APA91bHFOtaQGSwupt5l1og", 255 "error":"INVALID_JSON", 256 "error_description":"InvalidJson: JSON_TYPE_ERROR : Field \"time_to_live\" must be a JSON java.lang.Number: abc" 257 } 258 </gcm> 259 </message> 260 </pre> 261 262 <p>Quota exceeded:</p> 263 264 <pre><message> 265 <gcm xmlns="google:mobile:data"> 266 { 267 "message_type":"nack", 268 "message_id":"msgId1", 269 "from":"APA91bHFOtaQGSwupt5l1og", 270 "error":"QUOTA_EXCEEDED", 271 "error_description":"Short-term downstream quota exceeded for this registration id" 272 } 273 </gcm> 274 </message> 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><message id="3" type="error" to="123456789 (a] gcm.googleapis.com/ABC"> 340 <gcm xmlns="google:mobile:data"> 341 {"random": "text"} 342 </gcm> 343 <error code="400" type="modify"> 344 <bad-request xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"/> 345 <text xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"> 346 InvalidJson: JSON_PARSING_ERROR : Missing Required Field: message_id\n 347 </text> 348 </error> 349 </message> 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)—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><message> 365 <data:gcm xmlns:data="google:mobile:data"> 366 { 367 "message_type":"control" 368 "control_type":"CONNECTION_DRAINING" 369 } 370 </data:gcm> 371 </message></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 + "@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><message id=""> 406 <gcm xmlns="google:mobile:data"> 407 { 408 "category":"com.example.yourapp", // to know which app sent it 409 "data": 410 { 411 "hello":"world", 412 }, 413 "message_id":"m-123", 414 "from":"REGID" 415 } 416 </gcm> 417 </message></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><message id=""> 423 <gcm xmlns="google:mobile:data"> 424 { 425 "to":"REGID", 426 "message_id":"m-123" 427 "message_type":"ack" 428 } 429 </gcm> 430 </message></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>"delivery_receipt_requested"</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>"delivery_receipt_requested"</code> set to <code>true</code>:</p> 442 443 <pre><message id=""> 444 <gcm xmlns="google:mobile:data"> 445 { 446 "to":"REGISTRATION_ID", 447 "message_id":"m-1366082849205" 448 "data": 449 { 450 "hello":"world", 451 } 452 "time_to_live":"600", 453 "delay_while_idle": true, 454 <strong>"delivery_receipt_requested": true</strong> 455 } 456 </gcm> 457 </message> 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><message id=""> 465 <gcm xmlns="google:mobile:data"> 466 { 467 "category":"com.example.yourapp", // to know which app sent it 468 "data": 469 { 470 “message_status":"MESSAGE_SENT_TO_DEVICE", 471 “original_message_id”:”m-1366082849205” 472 “device_registration_id”: “REGISTRATION_ID” 473 }, 474 "message_id":"dr2:m-1366082849205", 475 "message_type":"receipt", 476 "from":"gcm.googleapis.com" 477 } 478 </gcm> 479 </message></pre> 480 481 <p>Note the following:</p> 482 483 <ul> 484 <li>The {@code "message_type"} is set to {@code "receipt"}. 485 <li>The {@code "message_status"} is set to {@code "MESSAGE_SENT_TO_DEVICE"}, 486 indicating that the message was delivered. Notice that in this case, 487 {@code "message_status"} 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 "data"} 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 * <p>For illustration purposes only. 571 */ 572 public class SmackCcsClient { 573 574 private static final Logger logger = Logger.getLogger("SmackCcsClient"); 575 576 private static final String GCM_SERVER = "gcm.googleapis.com"; 577 private static final int GCM_PORT = 5235; 578 579 private static final String GCM_ELEMENT_NAME = "gcm"; 580 private static final String GCM_NAMESPACE = "google:mobile:data"; 581 582 static { 583 584 ProviderManager.addExtensionProvider(GCM_ELEMENT_NAME, GCM_NAMESPACE, 585 new PacketExtensionProvider() { 586 @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 * @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("Dropping downstream message since the connection is draining"); 615 return false; 616 } 617 618 /** 619 * Returns a random message id to uniquely identify a message. 620 * 621 * <p>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 "m-" + 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 * <p>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<String, Object> jsonObject) { 643 // PackageName of the application that sent this message. 644 String category = (String) jsonObject.get("category"); 645 String from = (String) jsonObject.get("from"); 646 @SuppressWarnings("unchecked") 647 Map<String, String> payload = (Map<String, String>) jsonObject.get("data"); 648 payload.put("ECHO", "Application: " + category); 649 650 // Send an ECHO response back 651 String echo = createJsonMessage(from, nextMessageId(), payload, 652 "echo:CollapseKey", null, false); 653 654 try { 655 sendDownstreamMessage(echo); 656 } catch (NotConnectedException e) { 657 logger.log(Level.WARNING, "Not connected anymore, echo message is 658 not sent", e); 659 } 660 } 661 662 /** 663 * Handles an ACK. 664 * 665 * <p>Logs a {@code INFO} message, but subclasses could override it to 666 * properly handle ACKs. 667 */ 668 protected void handleAckReceipt(Map<String, Object> jsonObject) { 669 String messageId = (String) jsonObject.get("message_id"); 670 String from = (String) jsonObject.get("from"); 671 logger.log(Level.INFO, "handleAckReceipt() from: " + from + ", 672 messageId: " + messageId); 673 } 674 675 /** 676 * Handles a NACK. 677 * 678 * <p>Logs a {@code INFO} message, but subclasses could override it to 679 * properly handle NACKs. 680 */ 681 protected void handleNackReceipt(Map<String, Object> jsonObject) { 682 String messageId = (String) jsonObject.get("message_id"); 683 String from = (String) jsonObject.get("from"); 684 logger.log(Level.INFO, "handleNackReceipt() from: " + from + ", 685 messageId: " + messageId); 686 } 687 688 protected void handleControlMessage(Map<String, Object> jsonObject) { 689 logger.log(Level.INFO, "handleControlMessage(): " + jsonObject); 690 String controlType = (String) jsonObject.get("control_type"); 691 if ("CONNECTION_DRAINING".equals(controlType)) { 692 connectionDraining = true; 693 } else { 694 logger.log(Level.INFO, "Unrecognized control type: %s. This could 695 happen if new features are " + "added to the CCS protocol.", 696 controlType); 697 } 698 } 699 700 /** 701 * Creates a JSON encoded GCM message. 702 * 703 * @param to RegistrationId of the target device (Required). 704 * @param messageId Unique messageId for which CCS will send an 705 * "ack/nack" (Required). 706 * @param payload Message content intended for the application. (Optional). 707 * @param collapseKey GCM collapse_key parameter (Optional). 708 * @param timeToLive GCM time_to_live parameter (Optional). 709 * @param delayWhileIdle GCM delay_while_idle parameter (Optional). 710 * @return JSON encoded GCM message. 711 */ 712 public static String createJsonMessage(String to, String messageId, 713 Map<String, String> payload, String collapseKey, Long timeToLive, 714 Boolean delayWhileIdle) { 715 Map<String, Object> message = new HashMap<String, Object>(); 716 message.put("to", to); 717 if (collapseKey != null) { 718 message.put("collapse_key", collapseKey); 719 } 720 if (timeToLive != null) { 721 message.put("time_to_live", timeToLive); 722 } 723 if (delayWhileIdle != null && delayWhileIdle) { 724 message.put("delay_while_idle", true); 725 } 726 message.put("message_id", messageId); 727 message.put("data", 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 * @param to RegistrationId of the device who sent the upstream message. 736 * @param messageId messageId of the upstream message to be acknowledged to CCS. 737 * @return JSON encoded ack. 738 */ 739 protected static String createJsonAck(String to, String messageId) { 740 Map<String, Object> message = new HashMap<String, Object>(); 741 message.put("message_type", "ack"); 742 message.put("to", to); 743 message.put("message_id", messageId); 744 return JSONValue.toJSONString(message); 745 } 746 747 /** 748 * Connects to GCM Cloud Connection Server using the supplied credentials. 749 * 750 * @param senderId Your GCM project number 751 * @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 @Override 772 public void processPacket(Packet packet) { 773 logger.log(Level.INFO, "Received: " + 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 @SuppressWarnings("unchecked") 781 Map<String, Object> jsonObject = 782 (Map<String, Object>) JSONValue. 783 parseWithException(json); 784 785 // present for "ack"/"nack", null otherwise 786 Object messageType = jsonObject.get("message_type"); 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("message_id"); 794 String from = (String) jsonObject.get("from"); 795 String ack = createJsonAck(from, messageId); 796 send(ack); 797 } else if ("ack".equals(messageType.toString())) { 798 // Process Ack 799 handleAckReceipt(jsonObject); 800 } else if ("nack".equals(messageType.toString())) { 801 // Process Nack 802 handleNackReceipt(jsonObject); 803 } else if ("control".equals(messageType.toString())) { 804 // Process control message 805 handleControlMessage(jsonObject); 806 } else { 807 logger.log(Level.WARNING, 808 "Unrecognized message type (%s)", 809 messageType.toString()); 810 } 811 } catch (ParseException e) { 812 logger.log(Level.SEVERE, "Error parsing JSON " + json, e); 813 } catch (Exception e) { 814 logger.log(Level.SEVERE, "Failed to process packet", e); 815 } 816 } 817 }, new PacketTypeFilter(Message.class)); 818 819 // Log all outgoing packets 820 connection.addPacketInterceptor(new PacketInterceptor() { 821 @Override 822 public void interceptPacket(Packet packet) { 823 logger.log(Level.INFO, "Sent: {0}", packet.toXML()); 824 } 825 }, new PacketTypeFilter(Message.class)); 826 827 connection.login(senderId + "@gcm.googleapis.com", 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 = "Your API key"; 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 = "RegistrationIdOfTheTargetDevice"; 840 String messageId = ccsClient.nextMessageId(); 841 Map<String, String> payload = new HashMap<String, String>(); 842 payload.put("Hello", "World"); 843 payload.put("CCS", "Dummy Message"); 844 payload.put("EmbeddedMessageId", messageId); 845 String collapseKey = "sample"; 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 @Override 870 public String toXML() { 871 return String.format("<%s xmlns=\"%s\">%s</%s>", 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 @Override 887 public void connected(XMPPConnection xmppConnection) { 888 logger.info("Connected."); 889 } 890 891 @Override 892 public void authenticated(XMPPConnection xmppConnection) { 893 logger.info("Authenticated."); 894 } 895 896 @Override 897 public void reconnectionSuccessful() { 898 logger.info("Reconnecting.."); 899 } 900 901 @Override 902 public void reconnectionFailed(Exception e) { 903 logger.log(Level.INFO, "Reconnection failed.. ", e); 904 } 905 906 @Override 907 public void reconnectingIn(int seconds) { 908 logger.log(Level.INFO, "Reconnecting in %d secs", seconds); 909 } 910 911 @Override 912 public void connectionClosedOnError(Exception e) { 913 logger.info("Connection closed on error."); 914 } 915 916 @Override 917 public void connectionClosed() { 918 logger.info("Connection closed."); 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 = ("<message><gcm xmlns='google:mobile:data'>{1}</gcm></message>") 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 > 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