pkgcloud
Version:
An infrastructure-as-a-service agnostic cloud library for node.js
252 lines (220 loc) • 6.01 kB
JavaScript
/*
* client.js: Base client from which all pkgcloud clients inherit from
*
* (C) 2011 Nodejitsu Inc.
*
*/
var fs = require('fs'),
events = require('eventemitter2'),
morestreams = require('morestreams'),
request = require('request'),
utile = require('utile'),
qs = require('querystring'),
common = require('../../common'),
errs = require('errs');
var Client = exports.Client = function (options) {
events.EventEmitter2.call(this, { delimiter: '::', wildcard: true });
this.config = options || {};
};
utile.inherits(Client, events.EventEmitter2);
Client.prototype.request = function () {
var self = this,
responded,
callback,
errback,
options = {},
buffer,
method,
piped,
ended,
dest;
if (arguments.length === 3) {
errback = arguments[1];
callback = arguments[2];
options = typeof arguments[0] === 'object' ? arguments[0] : {
method: 'GET',
path: arguments[0],
headers: {}
};
}
else if (arguments.length === 4) {
errback = arguments[2];
callback = arguments[3];
options = {
method: arguments[0],
path: arguments[1],
headers: {}
};
}
else if (arguments.length === 5) {
var encoded = qs.encode(arguments[2]);
errback = arguments[3];
callback = arguments[4];
options = {
method: arguments[0],
path: arguments[1] + (encoded ? '?' + encoded : ''),
headers: {}
};
}
if (!options.path && !options.url) {
return errs.handle(
errs.create({ message: 'No path was provided' }),
errback
);
}
function handleRequest(err, res, body) {
if (err) {
return errback(err);
}
var statusCode = res.statusCode.toString(),
err2;
if (Object.keys(self.failCodes).indexOf(statusCode) !== -1) {
//
// TODO: Support more than JSON errors here
//
err2 = {
provider: self.provider,
failCode: self.failCodes[statusCode],
message: self.provider + ' Error (' +
statusCode + '): ' + self.failCodes[statusCode]
};
try {
err2.result = typeof body === 'string' ? JSON.parse(body) : body;
} catch (e) { err2.result = {err: body}; }
return errback(errs.create(err2));
}
callback(body, res);
}
function sendRequest () {
//
// Setup any specific request options before
// making the request
//
if (self.before) {
var errors = false;
for (var i in self.before) {
var fn = self.before[i];
try {
options = fn.call(self, options) || options;
// on errors do error handling, break.
} catch (exc) { errs.handle(exc, errback); errors = true; break; }
}
if (errors) { return; }
}
//
// Set the url for the request based
// on the `path` supplied.
//
if (typeof options.path === 'string') {
options.path = [options.path];
}
//
// Allow override of URL on parameters, otherwise use the function
//
if (!options.url) {
options.url = self.url.apply(self, options.path);
}
// dont delete the delete. this options path thing was messing
// request up.
delete options.path;
if (!errback || !callback) {
try {
return request(options);
} // if request throws still return an EE
catch (exc1) { return errs.handle(exc1); }
} else {
try {
return request(options, handleRequest);
} catch (exc2) { return errs.handle(exc2, errback); }
}
}
//
// Helper function which sets the appropriate headers
// for Rackspace Cloudfiles depending on the state of the
// buffer.
//
// TODO: Refactor this into the Rackspace Cloudfiles client.
//
function onPiped() {
options.headers = options.headers || {};
if (ended) {
options.headers['content-length'] = buffer.size;
}
else {
// this was crashing all getServer Tests
//options.headers['transfer-encoding'] = 'chunked';
// from teh docs
// The Cloud Server API does not support chunked transfer-encoding.
}
}
//
// Helper function which creates a `BufferedStream` to hold
// any piped data while this instance is authenticating.
//
function createBuffer() {
buffer = new morestreams.BufferedStream();
buffer.emit = function (event) {
if (event === 'end' && !responded) {
ended = true;
return;
}
morestreams.BufferedStream.prototype.emit.apply(buffer, arguments);
};
buffer.pipe = function (target) {
piped = true;
dest = target;
morestreams.BufferedStream.prototype.pipe.apply(buffer, arguments);
};
buffer.on('pipe', function () {
piped = true;
});
}
function pipeUpload(response) {
if (piped) {
buffer.pipe(response);
}
else {
buffer.on('pipe', function () {
buffer.pipe(response);
});
}
}
function pipeDownload(response) {
if (piped) {
response.pipe(dest);
}
else {
//
// Remark: Do we need to do something here?
//
}
}
if (!this.authorized && (this.provider === 'rackspace' || this.provider == 'openstack')) {
//
// If this instance is not yet authorized, then return
// a `BufferedStream` which can be piped to in the current
// tick.
//
createBuffer();
this.auth(function (err) {
onPiped();
var response = sendRequest();
response.on('end', function () {
responded = true;
buffer.emit('end');
buffer.removeAllListeners('pipe');
});
response.on('response', function (res) {
buffer.emit('response', res);
});
if (options.upload) {
pipeUpload(response);
}
else if (options.download) {
pipeDownload(response);
}
});
return buffer;
}
return sendRequest();
};