Super-simple asynchronous Google Cloud Messaging (GCM) notification send service written in Go
The world already has pyapns for asynchronous sending of Apple Push Notifications. We needed similar async functionality for Google Cloud Messages that we use for push notifications on Android.
Since we're (slowly) moving most of our backend codebase to Go, any new code is written in Go.
pyapns is so nice because Apple Push Notification Services require a long-open socket through which all messages are sent. This minimizes the number of handshakes that need to take place, and gives the app an open pipe through which to dump messages.
GCM, however, is a RESTful service (as of the writing of this README, the "send" URL is https://android.googleapis.com/gcm/send), that requires one HTTP connection per message sent. It does support multicasting to up to 1000 devices at once, but the message must be identical to all devices.
In our testing, from API servers running on AWS EC2 c1.medium cloud servers, sending a single APNS push notification takes < 10ms on an open socket, while sending a single GCM notification can take between 150-250ms. Since we send hundreds of notifications per second, this became unacceptable.
In order to not reinvent the wheel, we started with Alex Lockwood's open source GCM package for Go, called simply gcm. Surrounding that, we instantiate a standard web server that takes an -apikey
argument (and also configurable -ipaddress
and -port
arguments if the defaults are not desired) and listens for incoming POST requests.
The POST request should include two key-value pairs, tokens
(an array of GCM device tokens) and payload
(the JSON packet to send to GCM).
The server returns immediately, while pushing the main bulk of the work on to a new goroutine.
Functions:
- Send GCM messages (retries any message twice (TODO, make configurable?))
- Keep a run report for the process
- Report canonical IDs from GCM
- Report NotRegistered tokens
Start the server
./GoCM --apikey <GCM_API_KEY>
Send a message...
...via curl
:
curl -d "tokens=<GCM_DEVICE_TOKEN>&tokens=<GCM_DEVICE_TOKEN2>&payload={\"title\": \"This is the title\", \"subtitle\": \"This is the subtitle\", \"tickerText\": \"This is the ticker text\", \"datestamp\": \"2014-03-07T18:01:04.702100\"}" localhost:5601/gcm/send
...via python
import requests
GCM_SEND_ENDPOINT = 'https://localhost:5601/gcm/send'
token = "<GCM_DEVICE_TOKEN>"
message = {
'title': 'This is the title',
'subtitle': 'This is the subtitle',
'tickerText': 'This is the ticker text',
'datestamp': '2014-03-07T18:01:04.702100'
}
data = {
'tokens': [token],
'payload': json.dumps(message)
}
response = requests.post(GCM_SEND_ENDPOINT, data=data)
Get back a run report of attempts, failures, and required changes:
curl localhost:5601/gcm/report
Result like: {"attempts":0,"failures":0,"pending":0,"canonicals":0,"notregistered":0}
("attempts," "failures," "notregistered", and "canonicals" is a running total for the running process, while "pending" is the number of messages waiting to finish transmitting successfully. Great for watching via Graphite graphs or the like.)
Get back a list of push tokens that require updating (call if canonicals
in above results > 0):
curl localhost:5601/gcm/report/canonical
Results like: {"canonical_replacements":null}
Or: {"canonical_replacements": [{"original": "<token>", "canonical": "<new_token>"}]}
Read more on Canonical IDs in GCM via the offical documentation
Get back a list of push tokens that should be purged from your database (app was uninstalled, etc.)
curl localhost:5601/gcm/report/notregistered
Results like {"tokens": ["token1", "token2", ...]}
- Perhaps make runnable on a UNIX socket