Skip to content

Commit

Permalink
Add automatic exponential backoff retry on quota and temporary errors (
Browse files Browse the repository at this point in the history
…fix #2)
  • Loading branch information
Olivier Poitrey committed Mar 29, 2012
1 parent b34c09a commit 6d0f2b9
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 25 deletions.
89 changes: 65 additions & 24 deletions lib/c2dm.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ var util = require('util');
var https = require('https');
var querystring = require('querystring');
var emitter = require('events').EventEmitter;
var retry = require('retry');

function C2DM(config) {
if (config) {
Expand Down Expand Up @@ -76,33 +77,73 @@ C2DM.prototype.send = function(packet, cb) {
var self = this;
if (cb) this.once('sent', cb);

var postData = querystring.stringify(packet);
var headers = {
//'Connection': 'keep-alive',
'Host': 'android.apis.google.com',
'Authorization': 'GoogleLogin ' + this.token,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-length': postData.length
};
this.c2dmOptions.headers = headers;
if (this.keepAlive)
headers.Connection = 'keep-alive';
var operation = retry.operation();

var request = https.request(this.c2dmOptions, function(res) {
var data = '';
function respond() {
var idx = data.indexOf('id=');
if (idx < 0) {
self.emit('sent', data, null);
} else {
self.emit('sent', null, data.substring(idx));
operation.attempt(function(currentAttempt) {
var postData = querystring.stringify(packet);
var headers = {
//'Connection': 'keep-alive',
'Host': 'android.apis.google.com',
'Authorization': 'GoogleLogin ' + self.token,
'Content-Type': 'application/x-www-form-urlencoded',
'Content-length': postData.length
};
self.c2dmOptions.headers = headers;
if (self.keepAlive)
headers.Connection = 'keep-alive';

var request = https.request(self.c2dmOptions, function(res) {
var data = '';

if (res.statusCode == 503) {
// If the server is temporary unavailable, the C2DM spec requires that we implement exponential backoff
// and respect any Retry-After header
if (res.headers['retry-after']) {
var retrySeconds = res.headers['retry-after'] * 1; // force number
if (isNaN(retrySeconds)) {
// The Retry-After header is a HTTP-date, try to parse it
retrySeconds = new Date(res.headers['retry-after']).getTime() - new Date().getTime();
}
if (!isNaN(retrySeconds) && retrySeconds > 0) {
operation._timeouts['minTimeout'] = retrySeconds;
}
}
if (!operation.retry('TemporaryUnavailable')) {
self.emit('sent', operation.mainError(), null);
}
// Ignore all subsequent events for this request
return;
}
}
res.on('data', function(chunk) {

function respond() {
var error = null, id = null;

if (data.indexOf('Error=') === 0) {
error = data.substring(6).trim();
}
else if (data.indexOf('id=') === 0) {
id = data.substring(3).trim();
}
else {
// No id nor error?
error = 'InvalidServerResponse';
}

// Only retry if error is QuotaExceeded or DeviceQuotaExceeded
if (operation.retry(['QuotaExceeded', 'DeviceQuotaExceeded', 'InvalidServerResponse'].indexOf(error) >= 0 ? error : null)) {
return;
}

// Success, return message id (without id=)
self.emit('sent', error, id);
}

res.on('data', function(chunk) {
data += chunk;
});
res.on('end', respond);
res.on('close', respond);
});
res.on('end', respond);
res.on('close', respond);
request.end(postData);
});
request.end(postData);
};
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,8 @@
"url": "http://github.com/SpeCT/node-c2dm.git",
"web" : "http://github.com/SpeCT/node-c2dm"
},
"engines": { "node": ">= 0.2.0" }
"engines": { "node": ">= 0.2.0" },
"dependencies": {
"retry": ">=0.6.0"
}
}

0 comments on commit 6d0f2b9

Please # to comment.