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