Skip to content

Commit

Permalink
Merge pull request #10 from MaxCDN/feature/better_delete
Browse files Browse the repository at this point in the history
Better (faster) delete w/ files.
  • Loading branch information
jmervine committed Feb 10, 2014
2 parents 94dc334 + 8c1eea8 commit 9caf43b
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 71 deletions.
54 changes: 25 additions & 29 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
var OAuth = require('oauth').OAuth;
var path = require('path');
var querystring = require('querystring');
var async = require('async');

function MaxCDN(alias, key, secret) {
if (typeof alias !== 'string') {
Expand Down Expand Up @@ -69,42 +68,39 @@ MaxCDN.prototype.post = function post(url, data, callback) {
this.oauth.post(this._makeUrl(url), '', '', this._makeObject(data), this._parse(callback));
};

MaxCDN.prototype.delete = function del(url, files, limit, callback) {
var default_limit = 25;
if (typeof limit === 'function') {
callback = limit;
limit = default_limit;
}
MaxCDN.prototype.delete = function del(url, files, callback) {
if (typeof files === 'function') {
callback = files;
limit = default_limit;
files = null;
}

var that = this;
function dd(u) {
return function(cb) {
that.oauth.delete(that._makeUrl(u), '', '', that._parse(
function(err, data) { cb(err, data); }
));
};
}
var runs = [];
if (files !== null) {
if (!files.files) {
throw new Error('invalid files object');
/***
* This is a workaround for OAuth not supporting sending
* data (like a post) through the delete method, and not
* querystring.stringify not supporting using index based
* naming of params.
*
* Delete wants "files[0]=foo.css&files[1]=bar.css"
***/
function stringify(arr) {
var f = '';
for (var i = 0; i < arr.length; i++) {
f += 'files[' + i + ']=' + querystring.escape(arr[i])
if (i !== arr.length-1) f += '&';
}
files.files.forEach(function(file) {
runs.push(dd(url + '?' + that._makeQuerystring({files: file})));
});
return f;
}
if (runs == 0) {
that.oauth.delete(that._makeUrl(url), '', '', that._parse(callback));
} else {
async.parallelLimit(runs, limit, function(err, res) {
callback(err, res);
});

if (typeof files === 'string') {
files = "files="+string;
} else if (Array.isArray(files)) {
files = stringify(files);
} else if (files && files.files) {
files = stringify(files.files);
}
if (files) url += '?' + files;

this.oauth.delete(this._makeUrl(url), '', '', this._parse(callback));
};

MaxCDN.prototype._parse = function _parse(callback) {
Expand Down
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"name": "maxcdn",
"version": "0.1.1",
"version": "0.1.2",
"description": "MaxCDN API for Node.js",
"main": "index.js",
"scripts": {
"test": "tape ./test/*_test.js",
"int": "tape ./test/*_int.js"
"int": "tape ./test/*_int.js",
"benchmark": "node ./test/benchmark.js"
},
"repository": {
"type": "git",
Expand All @@ -20,10 +21,10 @@
"url": "https://github.com/MaxCDN/node-maxcdn/issues"
},
"dependencies": {
"oauth": "~0.9.10",
"async": "~0.2.9"
"oauth": "~0.9.11"
},
"devDependencies": {
"tape": "~2.3.0"
"tape": "~2.3.0",
"async": "~0.2.9"
}
}
78 changes: 78 additions & 0 deletions test/benchmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
var MaxCDN = require('../index');
var maxcdn = new MaxCDN(process.env.ALIAS, process.env.KEY, process.env.SECRET);
var async = require('async');
var format = require('util').format;

var post_name = Date.now().toString() + 'timer';
var timers = [];
var zoneid;

// fetch zoneid for timers
maxcdn.get('/zones/pull.json', function (err, res) {
zoneid = res.data.pullzones[0].id;

// timers
timer('GET /reports/popularfiles.json', function (callback) {
maxcdn.get('/reports/popularfiles.json', function (err, res) {
callback(err);
});
});

timer('PUT /reports/account.json/address', function (callback) {
maxcdn.put('account.json/address', "street2="+post_name, function(err, res) {
callback(err);
});
});

timer('POST /zones/pull.json', function (callback) {
var zone = {
name: post_name,
url: 'http://www.example.com'
};

maxcdn.post('/zones/pull.json', zone, function (err, res) {
callback(err);
});
});

timer('DELETE /zones/pull.json/PULL_ZONE', function (callback) {
maxcdn.delete('/zones/pull.json/'+post_name, function (err, res) {
callback(err);
});
});

timer('DELETE /zones/pull.json/'+zoneid+'/cache', function (callback) {
maxcdn.delete('/zones/pull.json/'+zoneid+'/cache', function (err, res) {
callback(err);
});
});

timer('DELETE /zones/pull.json/'+zoneid+'/cache w/ [ files ]', function (callback) {
maxcdn.delete('/zones/pull.json/'+zoneid+'/cache',
[ '/master.css', '/favicon.ico' ],
function (err, res) {
callback(err);
});
});

// run timers
async.series(timers);
});

// timer function
function timer(label, test) {
timers.push(function (callback) {
var start = new Date();
process.stdout.write(label+': ');
test( function (err) {
if (err) {
console.log('FAILED');
console.trace(err);
return;
}
console.log('%s ms', new Date() - start);
callback();
});
});
}

15 changes: 7 additions & 8 deletions test/delete_int.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,19 @@ test('delete', function(t) {
maxcdn.get('reports/popularfiles.json', function(err, res) {
var file1 = res.data.popularfiles.shift().uri;
var file2 = res.data.popularfiles.shift().uri;
maxcdn.delete('zones/pull.json/'+id+'/cache',
{ "files": [file1, file2] },
maxcdn.delete('zones/pull.json/'+id+'/cache', [file1, file2],
function(err, res) {
t.notOk(err, 'delete without error');
t.equal(res[0].code, 200, 'delete file one successful');
t.equal(res[1].code, 200, 'delete file two successful');
t.equal(res.code, 200, 'delete successful');
});
maxcdn.delete('zones/pull.json/'+id+'/cache', { "files": [file1, file2] },
function(err, res) {
t.notOk(err, 'delete without error');
t.equal(res.code, 200, 'delete successful');
});
});
});

maxcdn.get('reports/stats.json/hourly', function(err, res) {
t.notOk(err, 'get report without error');
t.ok(res.data.stats, 'get report with data');
});
t.end();
});

Expand Down
78 changes: 49 additions & 29 deletions test/index_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@ var test = require('tape');
// OAuth stub.
function oaStub() {
var callback = arguments[arguments.length-1];
callback(null, '{ "foo": "bar" }', '');

var res = JSON.stringify({
foo: "bar",
arguments: arguments
});

callback(null, res, '');
}

var OAuth = require('oauth').OAuth;
Expand All @@ -25,66 +31,80 @@ test('maxcdn', function(t) {
});

var m = new MaxCDN('alias', 'key', 'secret');
t.equal(m.alias , 'alias');
t.equal(m.key , 'key');
t.equal(m.secret , 'secret');
t.equal(m.alias , 'alias', 'setup:: alias');
t.equal(m.key , 'key', 'setup:: key');
t.equal(m.secret , 'secret', 'setup:: secret');

// _makeUrl
t.equal(m._makeUrl('foobar'), 'https://rws.netdna.com/alias/foobar');
t.equal(m._makeUrl('foobar'), 'https://rws.netdna.com/alias/foobar', '_makeURl');

// _makeQuerystring
t.equal(m._makeQuerystring({ foo: 'bar' }), 'foo=bar');
t.equal(m._makeQuerystring('{ "foo": "bar" }'), 'foo=bar');
t.equal(m._makeQuerystring('foo=bar'), 'foo=bar');
t.equal(m._makeQuerystring({ foo: 'bar' }), 'foo=bar', '_makeQuerystring:: object');
t.equal(m._makeQuerystring('{ "foo": "bar" }'), 'foo=bar', '_makeQuerystring:: json');
t.equal(m._makeQuerystring('foo=bar'), 'foo=bar', '_makeQuerystring:: querystring');

// _makeObject
t.equal(m._makeObject({ foo: 'bar' }).foo, 'bar');
t.equal(m._makeObject('{ "foo": "bar" }').foo, 'bar');
t.equal(m._makeObject('foo=bar').foo, 'bar');
t.equal(m._makeObject({ foo: 'bar' }).foo, 'bar', '_makeObject:: object');
t.equal(m._makeObject('{ "foo": "bar" }').foo, 'bar', '_makeObject:: json');
t.equal(m._makeObject('foo=bar').foo, 'bar', '_makeQuery:: querystring');

// _parse
m._parse(function(err, data) {
t.ok(err, 'should have err');
t.ok(data, 'should have data');
t.ok(err, '_parse:: string w/ err');
t.ok(data, '_parse:: string w/ data');
})(null, 'foobar', '');

m._parse(function(err, data) {
t.ok(err, 'should have err');
t.ok(data, 'should have data');
t.ok(err, '_parse:: json w/ err');
t.ok(data, '_parse:: json w/ data');
})(new Error(), '{ "foo": "bar" }', '');

m._parse(function(err, data) {
t.notOk(err, 'should not have err');
t.ok(data, 'should have data');
t.notOk(err, '_parse:: json w/o error');
t.ok(data, '_parse:: json w/ data');
})(null, '{ "foo": "bar" }', '');

// get
m.get('path', function(err, data) {
t.equal(data.foo, 'bar');
t.equal(data.foo, 'bar', 'get w/ data');
t.equal(data.arguments[0], 'https://rws.netdna.com/alias/path', 'get w/ path');
});

// put
m.put('path', 'data', function(err, data) {
t.equal(data.foo, 'bar');
m.put('path', { data: 'data' }, function(err, data) {
t.equal(data.foo, 'bar', 'put w/ data');
t.equal(data.arguments[0], 'https://rws.netdna.com/alias/path', 'put w/ path');
t.deepEqual(data.arguments[3], 'data=data', 'put sends data');
});

// post
m.post('path', 'data', function(err, data) {
t.equal(data.foo, 'bar');
m.post('path', { data: 'data' }, function(err, data) {
t.equal(data.foo, 'bar', 'post w/ data');
t.equal(data.arguments[0], 'https://rws.netdna.com/alias/path', 'post w/ path');
t.deepEqual(data.arguments[3], { data: 'data' }, 'post sends data');
});

// delete
m.delete('path', function(err, data) {
t.equal(data.foo, 'bar');
t.equal(data.foo, 'bar', 'delete w/ data');
t.equal(data.arguments[0], 'https://rws.netdna.com/alias/path', 'delete w/ path');
t.notOk(data.arguments[3], 'delete sends data');
});
m.delete('path', { files: ['path1','path2'] }, function(err, data) {
t.equal(data[0].foo, 'bar');
t.equal(data[1].foo, 'bar');

m.delete('path', ['path1','path2'], function(err, data) {
t.equal(data.foo, 'bar', 'delete (via Array) w/ data');
t.equal(data.arguments[0],
'https://rws.netdna.com/alias/path?files[0]=path1&files[1]=path2',
'delete (via Array) w/ path');
t.notOk(data.arguments[3], 'delete (via Array) sends data');
});

m.delete('path', { files: ['path1','path2'] }, 1, function(err, data) {
t.equal(data[0].foo, 'bar');
t.equal(data[1].foo, 'bar');
m.delete('path', { files: ['path1','path2'] }, function(err, data) {
t.equal(data.foo, 'bar', 'delete (via Object) w/ data');
t.equal(data.arguments[0],
'https://rws.netdna.com/alias/path?files[0]=path1&files[1]=path2',
'delete (via Object) w/ path');
t.notOk(data.arguments[3], 'delete (via Object) sends data');
});

t.end();
Expand Down

0 comments on commit 9caf43b

Please sign in to comment.