Super-simple async GCM notification send service written in Go
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
Godeps
.gitignore
.travis.yml
LICENSE
README.md
handlers.go
handlers_test.go
main.go
main_test.go
utilities.go
utilities_test.go

README.md

GoCM

Super-simple asynchronous Google Cloud Messaging (GCM) notification send service written in Go

Build Status

Motivation

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.

The service

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:

Example

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 = 'http://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", ...]}

TODO

  • Perhaps make runnable on a UNIX socket