So far, we’ve seen two mechanisms an app can use to communicate with the outside world. The first and most prominent of these is HTTP: an app can receive and respond to HTTP requests, and can send HTTP requests to other hosts and receive responses with the URL Fetch service. The second is email: an app can send email messages by using the Mail service, and can receive messages via a proxy that calls a request handler for each incoming email message.
In this chapter, we introduce a third method of communication: XMPP, also known as “instant messages,” or simply “chat.” An app can participate in a chat dialogue with a user of any XMPP-compatible chat service, such as Google Talk or any Jabber server. The XMPP service is useful for chat interfaces, such as a chat-based query engine, or a customer service proxy. App Engine does not act as an XMPP service itself. Instead, it connects to Google Talk’s infrastructure to participate as a chat user.
Sending and receiving XMPP messages works similarly to email messages. To send a message, an app calls the XMPP service API. To receive a message, the app declares that it accepts such messages in its configuration, and then handles HTTP requests sent by the XMPP service to special-purpose URLs. Figure 15-1 illustrates the flow of incoming XMPP messages.
Figure 15-1. Architecture of incoming XMPP messages, calling web hooks in response to incoming message events
Each participant in an XMPP communication has an address similar to an
email address, known as a JID. (JID is short for “Jabber ID,” named
after the Jabber project, where XMPP originated.) A JID consists of a
username, an “at” symbol (@
), and the domain name of the XMPP
server. A JID can also have an optional “resource” string, which is used to
identify specific clients connected to the service with the username. A
message sent to the ID without the resource goes to all connected
clients:
username @ domain / resource
To send a message, a chat participant sends an XMPP message to its own XMPP server. The participant’s chat service contacts the recipient service’s host by using the domain name of the JID and a standard port, then delivers the message. If the remote service accepts messages for the JID and someone is connected to the service with a chat client for that JID, the service delivers the message to the client.
As with email, each app has its own set of JIDs, based on its application ID. For XMPP chat, the app can receive messages at addresses of these forms:
app-id@appspot.com anything@app-id.appspotchat.com
(Notice the differences in the domain names from the options available for incoming email.)
App Engine does not support XMPP addresses on a custom (Google Apps) domain. This is one of only a few cases where exposing your application ID to users cannot be avoided.
If your users have Google accounts, you can use the Google Talk API to build custom clients to interact with your app over XMPP. See the Google Talk API documentation for more information.
Let’s take a look at the features and API of the XMPP service.
Before a user of an XMPP-compatible instant messaging service will see any messages your app sends, the service needs to know that the user is expecting your messages. This can happen in two ways: either the user explicitly adds your app’s JID to her contact list, or she accepts an invitation to chat sent by the app.
An app can send an invitation to chat by calling the XMPP service API. For apps, it’s polite to get the user’s permission to do this first, so the complete workflow looks something like this:
The user visits the website, and activates the chat-based feature of the service, providing a JID.
The app sends an invitation to chat to the user’s JID.
The user accepts the invitation in her chat client.
The user and app exchange chat messages.
The alternative where the user adds the app’s JID to her contact list is usually equivalent to sending an invitation to the app. App Engine accepts all such invitations automatically, even if the app does not accept chat messages.
An accepted invitation entitles both parties to know the other party’s presence status, whether the party is connected and accepting messages. This includes the ability to know when an invitation is accepted. See Managing Presence.
In the development server, inviting a user to chat emits a log message, but otherwise does nothing.
To invite a user to chat in Python, you call the
send_invite()
function in the google.appengine.api.xmpp
module. It takes
the recipient JID as its first argument, and an optional sender JID
(from_jid
) as its second argument. By default, it uses
as the sender
JID:app-id@
appspot.com
from google.appengine.api import xmpp jid = '[email protected]' xmpp.send_invite(jid) # fromapp-id
@appspot.com xmpp.send_invite(jid, from_jid='support@app-id
.appspotchat.com') # from a custom JID
In Java, each JID is represented by an instance of the
JID
class, in the package com.google.appengine.api.xmpp
. You create
this by passing the address as a string to the JID
constructor. To send an invitation, you
call the sendInvitation()
method with
either one or two arguments:
import com.google.appengine.api.xmpp.JID; import com.google.appengine.api.xmpp.XMPPService; import com.google.appengine.api.xmpp.XMPPServiceFactory; // ... XMPPService xmpp = XMPPServiceFactory.getXMPPService(); // Fromapp-id
@appspot.com: xmpp.sendInvitation(new JID("[email protected]")); // From a custom JID: xmpp.sendInvitation(new JID("[email protected]"), new JID("support@app-id
.appspotchat.com"));
An XMPP message includes a sender address, one or more recipient addresses, a message type, and a message body.
The sender address must be one of the app’s incoming XMPP addresses.
These are of the form
app-id
@appspot.com
or
anything
@
app-id
.appspotchat.com
,
where app-id
is your application ID and
anything
can be any string that’s valid on the
left side of a JID (it can’t contain an @
symbol). Unlike incoming email addresses,
it’s not as convenient to use the “anything” form for creating IDs on the
fly, since the recipient needs to accept an invitation from that ID before
receiving messages. But it can still be useful for sessions that begin
with an invitation, or addresses that represent specific purposes or users
of the app
(support@
app-id
.appspotchat.com
).
If the version of the app that is sending an XMPP message is not the
default version, App Engine modifies the sender address to a
version-specific address, so replies go directly to the correct version:
either
anything
@
version
.
app-id
.appspotchat.com
or
app-id
@
version
.
app-id.
appspotchat.com
.
App Engine adds a “resource” to the end of the sender JID
(after the domain name) that looks like this: /bot
. This is
mostly just to comply with the best practice of sending messages using
JIDs with resources. It isn’t noticed by chat users, and is not needed
when a user wishes to send a message to the app. You’ll see it in log
messages.
The message type can be any of the types in the XMPP standard, including chat, error, groupchat, headline, and normal. An app can only receive messages of the types chat, normal, and error, and so cannot participate in group chats. For straightforward communication between an app and a chat user, you usually want to send chat messages. For an app and a custom client, you can do what you like.
Messages are sent asynchronously. The service call returns immediately, and reports success only if the XMPP service enqueued the message successfully. You can configure the app to receive error messages, such as to be notified if a sent message was not received because the user went offline. See Handling Error Messages.
When an app is running in the development server, sending an XMPP chat message or invitation causes the server to print the message to the console. The development server does not contact the XMPP service or send messages.
To send a chat message in Python, you call the
send_message()
function in the google.appengine.api.xmpp
module. The
function takes a JID or list of JIDs, the body of the message, and an
optional sender JID (from_jid
). It returns a success code,
or a list of success codes, one for each recipient JID:
result = xmpp.send_message( '[email protected]', 'Your dog has reached level 12!') if result != xmpp.NO_ERROR: # ...
By default, this sends a message of the “chat” type. You can send
a message of a different type by setting the message_type
parameter. Acceptable values include xmpp.MESSAGE_TYPE_CHAT
(the default),
xmpp.MESSAGE_TYPE_ERROR
, xmpp.MESSAGE_TYPE
_GROUPCHAT
,
xmpp.MESSAGE_TYPE_HEADLINE
, and xmpp.MESSAGE_TYPE_NORMAL
.
Complete XMPP messages are sent over the network as XML data. By
default, send_message()
treats the
text of the message as plain text, and knows to escape XML characters.
Instead of a text message, you can send an XML stanza. This is included
verbatim (assuming the stanza is well formed) in the XMPP message, so
you can send structured data to XMPP clients. To tell
send_message()
that the content is an XML stanza so it
doesn’t escape XML characters, provide the raw_xml=True
parameter.
The send_message()
function returns a status code for
each recipient JID, as a single value if called with a single JID, or as
a list of codes if called with a list of JIDs. The possible status
values are xmpp.NO_ERROR
, xmpp.INVALID_JID
,
and xmpp.OTHER_ERROR
.
In Java, each action is a method of an XMPPService
object, which you get from
XMPPServiceFactory.getXMPPService()
. You send a message by
calling the sendMessage()
method. The method takes a
Message
object, which you build with a
MessageBuilder
object. sendMessage()
returns a
SendResponse
object, which contains status
codes for each intended recipient of the message:
import com.google.appengine.api.xmpp.JID; import com.google.appengine.api.xmpp.Message; import com.google.appengine.api.xmpp.MessageBuilder; import com.google.appengine.api.xmpp.SendResponse; import com.google.appengine.api.xmpp.XMPPService; import com.google.appengine.api.xmpp.XMPPServiceFactory; // ... XMPPService xmpp = XMPPServiceFactory.getXMPPService(); JID recipient = new JID("[email protected]"); Message message = new MessageBuilder() .withRecipientJids(recipient) .withBody("Your dog has reached level 12!") .build(); SendResponse success = xmpp.sendMessage(message); if (success.getStatusMap().get(recipient) != SendResponse.Status.SUCCESS) { // ... }
You use the MessageBuilder
class to assemble the
(immutable) Message
object. You can chain its methods to
construct a complete message in a single statement. Relevant methods
include:
withBody(String body)
Sets the message body.
asXml(boolean asXml)
Declares that the body contains a well-formed XML stanza (and not plain text).
withFromJid(JID jid)
Sets the sender JID.
withRecipientJids(JID jid1,
...)
Adds one or more recipient JIDs.
withMessageType(MessageType
type)
Sets the message type.
build()
Returns the finished Message
.
Message types are represented by the MessageType
enum: MessageType.CHAT
, MessageType.ERROR
,
MessageType.GROUPCHAT
, MessageType.HEADLINE
,
and MessageType.NORMAL
.
The sendMessage()
method returns a SendResponse
object. Calling this object’s
getStatusMap()
method returns a
Map<JID, SendResponseStatus>
, a map of recipient JIDs
to status codes. The possible status codes are
SendResponse.Status.SUCCESS
, SendResponse.Status.INVALID_ID
, and
SendResponse.Status.OTHER_ERROR
.
As with email, to receive incoming XMPP messages, you must first enable the feature by adding the XMPP inbound services to your app’s configuration. In Python, you add a section similar to the following in the app.yaml file:
inbound_services: - xmpp_message
In Java, you add a similar section to the appengine-web.xml file, anywhere inside the root element:
<inbound-services> <service>xmpp_message</service> </inbound-services>
This is the same configuration list as the mail
inbound
service. If you’re enabling both email and XMPP, you provide one list of
inbound services with all the items.
Deploy your app, and confirm that incoming XMPP is enabled using the Administration Console, under Application Settings. If your app does not appear to be receiving HTTP requests for incoming XMPP messages, check the Console and update the configuration if necessary.
The xmpp_message
inbound service routes incoming XMPP
messages of the types chat and normal to your app.
An app receives XMPP messages at several addresses. Messages sent to addresses of these forms are routed to the default version of the app:
app-id
@appspot.comanything
@app-id
.appspotchat.com
Messages sent to addresses of this form are routed to the specified version of the app, useful for testing:
anything
@version
.app-id
.appspotmail.com
Each message is delivered to the app as an HTTP POST request to a fixed URL path. Chat messages (both chat and normal) become POST requests to this URL path:
/_ah/xmpp/message/chat/
(Unlike incoming email, the sender JID is not included in these URL paths.)
The body content of the HTTP POST request is a MIME multipart message, with a part for each field of the message:
from
The sender’s JID.
to
The app JID to which this message was sent.
body
The message body content (with characters as they were originally typed).
stanza
The full XML stanza of the message, including the previous fields (with XML special characters escaped); useful for communicating with a custom client using XML.
The Python and Java SDKs include classes for parsing the request data into objects. (See the sections that follow.)
The development server console (http://localhost:8080/_ah/admin/) includes a feature for simulating incoming XMPP messages by submitting a web form. The development server cannot receive actual XMPP messages.
When using the development server console to simulate an incoming XMPP message, you must use a valid JID for the app in the “To:” field, with the application ID that appears in the app’s configuration. Using any other “To:” address in the development server is an error.
In Python, you map the URL path to a script handler in the app.yaml file, as usual:
handlers: - url: /_ah/xmpp/message/chat/ script: handle_xmpp.py login: admin
As with all web hook URLs in App Engine, this URL handler can be
restricted to admin
to prevent anything other than the XMPP
service from activating the request handler.
The Python library provides a Message
class that can contain an incoming
chat message. You can parse the incoming message into a
Message
object by passing a mapping of the POST parameters to its constructor. With the webapp2
framework, this is a simple matter of passing the parsed POST data (a
mapping of the POST parameter names to values) in directly:
from google.appengine.api import xmpp from google.appengine.ext import webapp2 class IncomingXMPPHandler(webapp2.RequestHandler): def post(self): message = xmpp.Message(self.request.POST) message.reply('I got your message! ' 'It had %d characters.' % len(message.body)) application = webapp2.WSGIApplication([('/_ah/xmpp/message/chat/', IncomingXMPPHandler)], debug=True)
The Message
object has attributes for each message
field: sender
, to
, and body
. (The
attribute for the “from” field is named sender
because
from
is a Python keyword.) It also includes a convenience
method for replying to the message, reply()
, which takes
the body of the reply as its first argument.
The Message
class includes methods for parsing
chat-style commands of this form:
/commandname args
If the chat message body is of this form, the
command
attribute of the Message
is the
command name (without the slash), and the arg
attribute
is everything that follows. If the message is not of this form,
command
is None
.
webapp includes a request handler base class that makes it easy
to implement chat interfaces that perform user-issued commands. Here’s
an example that responds to the commands /stats
and
/score username
, and ignores all other messages:
from google.appengine.api import xmpp from google.appengine.ext import webapp2 from google.appengine.ext.webapp import xmpp_handlers def get_stats(): # ... def get_score_for_user(username): # ... class UnknownUserError(Exception): pass class ScoreBotHandler(xmpp_handlers.CommandHandler): def stats_command(self, message): stats = get_stats() if stats: message.reply('The latest stats: %s' % stats) else: message.reply('Stats are not available right now.') def score_command(self, message): try: score = get_score_for_user(message.arg) message.reply('Score for user %s: %d' % (message.arg, score)) except UnknownUserError, e: message.reply('Unknown user %s' % message.arg) application = webapp2.WSGIApplication([('/_ah/xmpp/message/chat/', ScoreBotHandler)], debug=True)
The CommandHandler
base class, provided by the
google.appengine.ext.webapp
.xmpp_handlers
package, parses the
incoming message for a command. If the message contains such
a command, the handler attempts to call a method named after the
command. For example, when the app receives this chat message:
/score druidjane
The handler would call the score_command()
method
with a message where message.arg
is
'druidjane'
.
If there is no method for the parsed command, the handler calls
the unhandled_command()
method, whose default
implementation replies to the message with “Unknown command.” You can
override this method to customize its behavior.
The base handler calls the command method with the
Message
object as an argument. The method can use the
command
and arg
properties to read the
parsed command.
If the incoming message does not start with a
/commandname
-style command, the base handler calls the
text_message()
method with the Message
as
its argument. The default implementation does nothing, and you can
override it to specify behavior in this case.
This package also contains a simpler handler class named
BaseHandler
, with several useful features. It parses
incoming messages, and logs and ignores malformed messages. If a
message is valid, it calls its message_received()
method,
which you override with the
intended behavior. The class also overrides
webapp.RequestHandler
’s handle_exception()
method to send an XMPP
reply with a generic error message when an uncaught exception occurs,
so the user isn’t left to wonder whether the message was received.
(CommandHandler
extends BaseHandler
, and so
also has these features.)
In Java, you process incoming XMPP messages by mapping a
servlet to the URL path called by the XMPP service, in the deployment
descriptor. You can restrict the URL path by using a
<security-constraint>
to ensure only the XMPP service
can access it:
<servlet> <servlet-name>xmppreceiver</servlet-name> <servlet-class>myapp.XMPPReceiverServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>xmppreceiver</servlet-name> <url-pattern>/_ah/xmpp/message/chat/</url-pattern> </servlet-mapping> <security-constraint> <web-resource-collection> <url-pattern>/_ah/xmpp/message/chat/</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint>
The XMPPService
object includes a
parseMessage()
method that knows how to parse the incoming
request data into a Message
object. You access the data by
using the Message
’s methods:
import java.io.IOException; import javax.servlet.http.*; import com.google.appengine.api.xmpp.Message; import com.google.appengine.api.xmpp.XMPPService; import com.google.appengine.api.xmpp.XMPPServiceFactory; public class XMPPReceiverServlet extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException { XMPPService xmpp = XMPPServiceFactory.getXMPPService(); Message message = xmpp.parseMessage(req); // ... } }
You access the fields of a Message
using
methods:
When an app calls the XMPP service to send a message, the message is queued for delivery and sent asynchronously with the call. The call will only return with an error if the message the app is sending is malformed. If the app wants to know about an error during delivery of the message (such as the inability to connect to a remote server), or an error returned by the remote XMPP server (such as a nonexistent user), it can listen for error messages.
Error messages are just another type of chat message, but the XMPP
service separates incoming error messages into a separate inbound service.
To enable this service, add the xmpp_error
inbound service to
the app’s configuration.
inbound_services: - xmpp_message - xmpp_error
<inbound-services> <service>xmpp_message</service> <service>xmpp_error</service> </inbound-services>
Error messages arrive as POST requests at this URL path:
/_ah/xmpp/error/
You handle an error message just as you would a chat message: create a request handler, map it to the URL path, and parse the POST request for more information.
Neither the Python nor Java APIs provide any assistance parsing
incoming error messages. While XMPP error messages are similar in
structure to chat messages (with a type of error
), minor
differences are not recognized by the message parsers provided. You can
examine the XML data structure in the POST message body, which conforms to
the XMPP message standard. See the XMPP
specification for details.
After the user accepts an app’s invitation to chat, both parties are able to see whether the other party is available to receive chat messages. In XMPP RFC 3921, this is known as presence. The process of asking for and granting permission to see presence is called subscription. For privacy reasons, one user must be successfully subscribed to the other before she can send messages, see presence information, or otherwise know the user exists.
When a user accepts an app’s invitation to chat (subscription request), the user’s client sends a “subscribed” message to the app, to confirm that the app is now subscribed. If, later, the user revokes this permission, the client sends an “unsubscribed” message. While the app is subscribed to a user, the user’s client will send all changes in presence to the app as another kind of message.
Conversely, a user can also send “subscribe” and “unsubscribe” messages to the app. It’s the app’s responsibility to maintain a list of subscribed users to use when sending presence updates.
If you’d like to receive these new message types (and be billed for
the bandwidth), you must enable these as separate inbound services.
Subscription information (invitation responses and subscription requests)
use the xmpp_subscribe
service, and presence updates
use the xmpp_presence
service.
Here’s the Python configuration for app.yaml that enables all four XMPP inbound message types:
inbound_services: - xmpp_message - xmpp_error - xmpp_subscribe - xmpp_presence
And here’s Java configuration for appengine-web.xml that does the same:
<inbound-services> <service>xmpp_message</service> <service>xmpp_error</service> <service>xmpp_subscribe</service> <service>xmpp_presence</service> </inbound-services>
If you want to know when a user accepts or revokes your app’s chat
invitation, but otherwise do not need to see the user’s presence
updates, enable the xmpp_subscribe
service without the xmpp_presence
service. This can save on costs
associated with the incoming bandwidth of changes in the user’s
presence, which can be frequent.
As with chat messages, you can simulate incoming subscription and presence messages in the development server by using the development console. Outgoing subscription and presence messages in the development server are logged to the console, but not actually sent.
An app subscribes to a user when it sends an invitation to
chat, with the send_invite()
function (Python) or the
sendInvitation()
method (Java). An app cannot send an
explicit “unsubscribe” message, only “subscribe.”
When the user accepts the invitation, her chat client sends a
subscribed
message to the app. If the user later revokes
the invitation, the client sends an unsubscribed
message.
These messages arrive via the xmpp_subscribe
inbound
service as POST requests on the following URL paths:
/_ah/xmpp/subscription/subscribed/ /_ah/xmpp/subscription/unsubscribed/
A user can send an explicit subscription request (invitation to
chat) to the app by sending a subscribe
message. Similarly,
the user can explicitly unsubscribe from presence updates by sending an
unsubscribe
message. These arrive at the following URL
paths:
/_ah/xmpp/subscription/subscribe/ /_ah/xmpp/subscription/unsubscribe/
The subscription process typically happens just once in the
lifetime of the relationship between two chat users. After the users are
successfully subscribed, they remain subscribed until one party
explicitly unsubscribes from the other (unsubscribe
), or
one party revokes the other party’s invitation
(unsubscribed
).
If you intend for your app to have visible changes in presence,
the app must maintain a roster of subscribers based on
subscribe
and unsubscribe
messages, and send
updates only to subscribed users.
Incoming subscription-related requests include form-style fields in the POST data, with the following fields:
Because the POST data for these requests does not contain a
body
field, you cannot use the SDK’s Message
class to parse the data. You access
these fields simply as POST form fields in the request.
The app gets the subscription command from the URL path.
Here is an outline for a request handler that processes subscription-related messages, using Python and webapp2:
import webapp2 from google.appengine.api import xmpp def truncate_jid(jid): # Remove the "resource" portion of a JID. if jid: i = jid.find('/') if i != -1: jid = jid[:i] return jid class SubscriptionHandler(webapp2.RequestHandler): def post(self, command): user_jid = truncate_jid(self.request.POST.get('from')) if command == 'subscribed': # User accepted a chat invitation. # ... if command == 'unsubscribed': # User revoked a chat invitation. # ... if command == 'subscribe': # User wants presence updates from the app. # ... if command == 'unsubscribed': # User no longer wants presence updates from the app. # ... application = webapp2.WSGIApplication( [('/_ah/xmpp/subscription/(.*)/', SubscriptionHandler)], debug=True)
As mentioned earlier, an app sends a subscription request
('subscribe'
) to a user by calling the
xmpp.send_invite(jid)
function. There is no way to send
an 'unsubscribe'
message from an app. If the app no
longer cares about a user’s presence messages, the only choice is to
ignore the incoming presence updates from that user.
As with chat messages, the Java API’s XMPPService
includes a facility for parsing
subscription messages out of the incoming HTTP request. The
parseSubscription()
method takes the HttpServletRequest
and returns a
Subscription
object:
import com.google.appengine.api.xmpp.JID; import com.google.appengine.api.xmpp.Subscription; import com.google.appengine.api.xmpp.SubscriptionType; import com.google.appengine.api.xmpp.XMPPService; import com.google.appengine.api.xmpp.XMPPServiceFactory; // ... public class SubscriptionServlet extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { XMPPService xmpp = XMPPServiceFactory.getXMPPService(); Subscription sub = xmpp.parseSubscription(req); JID userJID = sub.getFromJid(); if (sub.getSubscriptionType() == SubscriptionType.SUBSCRIBED) { // User accepted a chat invitation. // ... } else if (sub.getSubscriptionType() == SubscriptionType.UNSUBSCRIBED) { // User revoked a chat invitation. // ... } else if (sub.getSubscriptionType() == SubscriptionType.SUBSCRIBE) { // User wants presence updates from the app. // ... } else if (sub.getSubscriptionType() == SubscriptionType.UNSUBSCRIBE) { // User no longer wants presence updates from the app. // ... } } }
You can access the fields of a Subscription
message, using methods:
getFromJid()
The sender JID
.
getToJid()
The app’s recipient JID
used in the
message.
getSubscriptionType()
The type of the subscription message:
SubscriptionType.SUBSCRIBED
,
SubscriptionType.UNSUBSCRIBED
,
SubscriptionType.SUBSCRIBE
, or
SubscriptionType.UNSUBSCRIBE
.
getStanza()
The String
raw XML of the message.
An app sends a subscription request ('subscribe'
)
to a user by calling the sendInvitation(jid)
method of
the XMPPService
. There is no way to send an
'unsubscribe'
message from an app. If the app no longer
cares about a user’s presence messages, the only choice is to ignore
the incoming presence updates from that user.
While an app is subscribed to a user, the user sends
changes in presence to the app. If the app is configured to receive
inbound presence messages via the xmpp_presence
service,
these messages arrive as POST requests on one of these URL paths:
/_ah/xmpp/presence/available/ /_ah/xmpp/presence/unavailable/
Chat clients typically send an available
message when connecting, and an
unavailable
message when disconnecting (or
going “invisible”).
A presence message can also contain additional status information: the presence show (“show me as”) value and a status message. Most chat clients represent the show value as a colored dot or icon, and may display the status message as well. And of course, most chat clients allow the user to change the show value and the message. The possible show values, along with how they typically appear in chat clients, are as follows:
The user is available to chat. Green, “available.”
The user is away from her computer temporarily and not available to chat. Yellow, “away.” A typical chat client switches to this presence show value automatically when the user is away from the keyboard.
“Do not disturb”: the user may be at her computer, but does not want to receive chat messages. Red, “busy.”
“Extended away”: the user is not available to chat and is away for an extended period. Red.
In XMPP, availability and the presence show value are distinct concepts. For a user to appear as “busy,” the user must be available. For example, a red-colored “busy” user is available, with a show value of “dnd.” Chat clients represent unavailable users either with a grey icon or by showing the user in another list.
An incoming presence update request includes form-style fields in the POST data, with the following fields:
from
The sender’s JID.
to
The app JID to which this message was sent.
show
One of several standard presence show values. If omitted, this implies the “chat” presence.
status
A custom status message. Only present if the user has a custom status message set, or is changing her status message.
The full XML stanza of the subscription message, including the previous fields.
An app can notify users of its own presence by sending a presence message. If the app’s presence changes, it should attempt to send a presence message to every user known to be subscribed to the app. To support this, the app should listen for “subscribe” and “unsubscribe” messages, and keep a list of subscribed users, as described in Managing Subscriptions.
An app should also send a presence message to a user if it receives a presence probe message from that user. See Probing for Presence.
As with chat and subscription messages, the development server can simulate incoming presence messages. However, it cannot include presence show and status strings in these updates.
Here is an outline for a request handler for processing incoming presence updates, using Python and webapp2:
import webapp2 from google.appengine.api import xmpp def truncate_jid(jid): # Remove the "resource" portion of a JID. if jid: i = jid.find('/') if i != -1: jid = jid[:i] return jid class PresenceHandler(webapp2.RequestHandler): def post(self, command): user_jid = truncate_jid(self.request.POST.get('from')) if command == 'available': # The user is available. show = self.request.POST.get('show') status_message = self.request.POST.get('status') # ... elif command == 'unavailable': # The user is unavailable (disconnected). # ... application = webapp2.WSGIApplication( [('/_ah/xmpp/presence/(.*)/', PresenceHandler)], debug=True)
To send a presence update to a single user in Python, you call
the xmpp.send_presence()
method:
xmpp.send_presence(user_jid, status="Doing fine.", presence_type=xmpp.PRESENCE_TYPE_AVAILABLE, presence_show=xmpp.PRESENCE_SHOW_CHAT)
The send_presence()
function takes the
jid
, a status
message (up to 1 kilobyte),
the presence_type
, and the presence_show
as
arguments. presence_type
is either
xmpp.PRESENCE_TYPE_AVAILABLE
or
xmpp.PRESENCE_TYPE_UNAVAILABLE
.
presence_show
is one of the standard presence show
values, which are also available as library constants:
xmpp.PRESENCE_SHOW_CHAT
,
xmpp.PRESENCE_SHOW_AWAY
,
xmpp.PRESENCE_SHOW_DND
, and
xmpp.PRESENCE_SHOW_XA
.
When the app wishes to broadcast a change in presence, it must
call send_presence()
once for each user currently
subscribed to the app. Unlike send_message()
, you can’t
pass a list of JIDs to send_presence()
to send many
updates in one API call. A best practice is to use task queues to
query your data for subscribed users and send presence updates in
batches. See Chapter 16 for
more information on task queues.
The Java API’s XMPPService
can also parse incoming
presence update messages. The parsePresence()
method
takes the HttpServletRequest
and returns a
Presence
object:
import com.google.appengine.api.xmpp.JID; import com.google.appengine.api.xmpp.Presence; import com.google.appengine.api.xmpp.PresenceShow; import com.google.appengine.api.xmpp.PresenceType; import com.google.appengine.api.xmpp.XMPPService; import com.google.appengine.api.xmpp.XMPPServiceFactory; // ... public class PresenceServlet extends HttpServlet { public void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { XMPPService xmpp = XMPPServiceFactory.getXMPPService(); Presence presence = xmpp.parsePresence(req); JID userJID = presence.getFromJid(); if (presence.getPresenceType() == PresenceType.AVAILABLE) { // The user is available. PresenceShow show = presence.getPresenceShow(); String status = presence.getStatus(); // ... } else if (presence.getPresenceType() == PresenceType.AVAILABLE) { // The user is not available (disconnected). // ... } } }
You can access the fields of a Presence
message,
using methods:
getFromJid()
The sender JID
.
getToJid()
The app’s recipient JID
used in the
message.
getPresenceType()
The type of the presence message:
PresenceType.AVAILABLE
,
PresenceType.UNAVAILABLE
, or
PresenceType.PROBE
(see later).
isAvailable()
A convenience method for testing whether the presence type
is “available.” Returns true
or
false
.
getPresenceShow()
The presence show value: PresenceShow.NONE
,
PresenceShow.AWAY
, PresenceShow.CHAT
,
PresenceShow.DND
, or PresenceShow.XA
.
This returns null
if no value was present in the
message.
getStatus()
A custom status message, if the user has one set.
getStanza()
The String
raw XML of the message.
To send a presence update to a single user, call the
sendPresence()
method of the
XMPPService
:
xmpp.sendPresence(new JID("[email protected]"), PresenceType.AVAILABLE, PresenceShow.CHAT, null);
This takes as arguments a JID
, a
PresenceType
, a PresenceShow
, and a
String
custom status message. You can set the show value
or status message to null
if no value is
appropriate.
When the app wishes to broadcast a change in presence, it must
call sendPresence()
once for each user currently
subscribed to the app. Unlike sendMessage()
, you can’t
pass a list of JIDs to sendPresence()
to send many
updates in one API call. A best practice is to use task queues to
query your data for subscribed users and send presence updates in
batches. See Chapter 16 for
more information on task queues.
Chat services broadcast presence updates to subscribed users as a user’s presence changes. But this is only useful while the subscribed users are online. When a user comes online after a period of being disconnected (such as if her computer was turned off or not on the Internet), the user’s client must probe the users in her contact list to get updated presence information.
When a user sends a probe to an app, it comes in via the
xmpp_presence
inbound service, as a POST
request to this URL path:
/_ah/xmpp/presence/probe/
The POST data contains the following fields:
If your app receives this message, it should respond immediately by sending a presence update just to that user.
An app can send a presence probe message to a user. If the app is subscribed to the user, the user will send a presence message to the app in the usual way.
In the development server, outgoing probe messages are logged to the console, and not actually sent. There is currently no way to simulate an incoming probe message in the development console.
Here’s how you would extend the
PresenceHandler
in the previous Python example to respond
to presence probes:
class PresenceHandler(webapp2.RequestHandler): def post(self, command): user_jid = truncate_jid(self.request.POST.get('from')) if command == 'available': # ... elif command == 'unavailable': # ... elif command == 'probe': # The user is requesting the app's presence information. xmpp.send_presence( user_jid, presence_type=xmpp.PRESENCE_TYPE_AVAILABLE, presence_show=xmpp.PRESENCE_SHOW_CHAT)
To send a presence probe to a user, you call
xmpp.send_presence()
with a presence_type
of
xmpp.PRESENCE_TYPE_PROBE
:
xmpp.send_presence(jid, presence_type=xmpp.PRESENCE_TYPE_PROBE)
In Java, an incoming presence probe message is just
another type of Presence
message. You can map a separate
servlet to the /_ah/xmpp/presence/probe/
URL path, or
just test the PresenceType
of the parsed message in a servlet that handles all /_ah/xmpp/presence/*
requests:
XMPPService xmpp = XMPPServiceFactory.getXMPPService(); Presence presence = xmpp.parsePresence(req); JID userJID = presence.getFromJid(); if (presence.getPresenceType() == PresenceType.PROBE) { xmpp.sendPresence(userJID, PresenceType.AVAILABLE, PresenceShow.CHAT, null); }
Similarly, to send a presence probe to a user, you call the
sendPresence()
method with a presence type of
PresenceType.PROBE
:
xmpp.sendPresence(jid, PresenceType.PROBE, null, null);
By virtue of the fact that the XMPP service is using Google Talk’s infrastructure, an app can check the presence of a Google Talk user with an immediate API call, without using a probe and waiting for a response.
For privacy reasons, a Google Talk user will only appear as available to an App Engine app if the app has an active subscription to the user (the user accepted an invitation from the app and has not revoked it). All other users appear as “not present.”
To get the presence of a Google Talk user in Python, you call the
get_presence()
function with the user’s JID and an optional
custom sender JID (from_jid
). The function returns
True
if the user is connected and can receive chat messages
from the app:
if xmpp.get_presence('[email protected]'): # User can receive chat messages from the app. # ...
If you specify the argument get_show=True
, instead of
a Boolean value, get_presence()
returns a tuple. The first
value is the availability Boolean. The second value is one of the four
presence show values: 'chat'
, 'away'
, 'dnd'
, or 'xa'
. There is no way to get the custom
status message with get_presence()
.
In Java, you call the getPresence()
method of the
XMPPService
instance with the user’s JID and
an optional sender JID. The method returns a Presence
object, which is populated as if parsed from a presence update
message:
import com.google.appengine.api.xmpp.Presence; import com.google.appengine.api.xmpp.PresenceShow; // ... JID jid = new JID("[email protected]"); Presence presence = xmpp.getPresence(jid); if (presence.isAvailable()) { PresenceShow presenceShow = presence.getPresenceShow(); String status = presence.getStatus(); // ... }
When running in the development server, all user addresses are
present, with a presence show value of chat
. There is no
way to change the return value of this API in the development server.
(Simulating XMPP presence messages will not change it.)
3.12.166.61