core
Version:
Core Libs
393 lines (345 loc) • 10.8 kB
JavaScript
var Response = require("../http/Response.js").Response;
var knox = require('knox');
var xml2js = require('xml2js');
/**
* @class S3Client
* @classdesc S3 client
* @param {Object} configuration S3 connection configuration
* @param {Object} configuration.key access key ID
* @param {Object} configuration.secret secret access key
* @param {Object} configuration.bucket bucket
* @param {Object} options connection options
* @param {Object} options.timeout timeout before aborting request
* @param {Object} options.retry number of retries when connection problem occurs
*/
var S3Client = function (configuration, options) {
this.retry = options.retry || 0;
this.timeout = options.timeout || 10000;
/* odwolanie przez bucket */
configuration.style = 'path';
this._client = knox.createClient(configuration);
};
/**
* Callback fired after receiving response from server.
*
* When connection error occured {error} object will be passed as first parameter.
*
* @callback S3Client~callback
* @param {error} error
* @param {response} response Response object
*/
/**
* Callback fired after receiving response from server.
*
* When connection error occured {error} object will be passed as first parameter.
*
* @callback S3Client~callbackAcl
* @param {error} error
* @param {object} acl requested object's ACLs
*/
/**
* Adds object to S3
*
* @param {string} key key name of the object
* @param {object} params object properties
* @param {buffer|string} params.body object content
* @param {object} params.headers headers
* @param {string} params.acl ACL applied to object
* @param {S3Client~callback} callback Called when response from the server is returned.
*/
S3Client.prototype.set = function (key, params, callback) {
var headers = params.headers;
var body = params.body;
if (!body) {
return callback(this._getError(S3Client.Error.ENOBODY), null);
}
if (!key) {
return callback(this._getError(S3Client.Error.ENOKEY), null);
}
if (!headers) {
return callback(this._getError(S3Client.Error.ENOHEADERS), null);
}
if (params.acl) {
headers['x-amz-acl'] = params.acl;
}
this._spawnFunction(this._set.bind(this, key, headers, body), callback);
};
/**
* Retrieves object from S3.
*
* When retry count specified, callback is called once after all tries.
*
* @param {string} key
* @param {S3Client~callback} callback Called when response from the server is returned.
*/
S3Client.prototype.get = function (key, params, callback) {
params = params || {};
var headers = {};
if (params.range) {
headers.range = params.range;
}
this._spawnFunction(this._get.bind(this, key, headers), callback);
};
/**
* Retrieves headers of object from S3.
*
* When retry count specified, callback is called once after all tries.
*
* @param {string} key
* @param {S3Client~callback} callback Called when response from the server is returned.
*/
S3Client.prototype.head = function (key, headers, callback) {
this._spawnFunction(this._head.bind(this, key, headers), callback);
};
/**
* Removes object from S3.
*
* When retry count specified, callback is called once after all tries.
*
* @param {string} key
* @param {S3Client~callback} callback Called when response from the server is returned.
*/
S3Client.prototype.del = function (key, callback) {
this._spawnFunction(this._del.bind(this, key), callback);
};
/**
* Copy object in S3 around the same bucket.
*
* When retry count specified, callback is called once after all tries.
*
* @param {string} keySrc
* @param {string} keyDst
* @param {string} headers
* @param {S3Client~callback} callback Called when response from the server is returned.
*/
S3Client.prototype.copy = function (keySrc, keyDst, headers, callback) {
this._spawnFunction(this._copy.bind(this, keySrc, keyDst, headers), callback);
};
/**
* List object in S3 bucket.
*
* When retry count specified, callback is called once after all tries.
*
* @param {string} params
* @param {string} headers
* @param {S3Client~callback} callback Called when response from the server is returned.
*/
S3Client.prototype.list = function (params, headers, callback) {
this._client.list(params, headers, callback);
};
/**
* Retrieves object's ACL.
*
* When retry count specified, callback is called once after all tries.
*
* @param {type} key
* @param {S3Client~callbackAcl} callback Called when response from the server is returned.
*/
S3Client.prototype.getAcl = function (key, callback) {
key = key + '?acl';
this._spawnFunction(this._get.bind(this, key, null), function (err, response) {
if (err) {
return callback(err, response);
}
if (response.getStatusCode() != 200) {
var error = new Error('Error fetching ACLs - status code: ' + response.getStatusCode());
return callback(error, null);
}
var xml = response.getBody().toBuffer().toString();
xml2js.parseString(xml, {explicitArray: false}, function (err, acl) {
if (err) {
var error = new Error('Error parsing XML response');
return callback(error, null);
}
callback(null, acl.AccessControlPolicy);
});
});
};
/**
* Wraps knox request and manages request events
*
* @private
* @param {string} key
* @param {Object} headers
* @param {Buffer} body
* @param {function} callback
*/
S3Client.prototype._set = function (key, headers, body, callback) {
var request = this._client.put(key, headers, body);
if (this.timeout) {
request.setTimeout(this.timeout);
}
request.on('response', this._handleResponse.bind(this, callback));
request.on('error', function (error) {
callback(error, null);
});
request.once('timeout', function () {
request.abort();
});
request.end(body);
};
/**
* Wraps knox request and manages request events
*
* @private
* @param {string} keySrc
* @param {Object} keyDst
* @param {Buffer} headers
* @param {function} callback
*/
S3Client.prototype._copy = function (keySrc, keyDst, headers, callback) {
var request = this._client.copy(keySrc, keyDst, headers);
if (this.timeout) {
request.setTimeout(this.timeout);
}
request.on('response', this._handleResponse.bind(this, callback));
request.on('error', function (error) {
callback(error, null);
});
request.once('timeout', function () {
request.abort();
});
request.end();
};
/**
* Wraps knox request and manages request events
*
* @private
* @param {string} key
* @param {function} callback
*/
S3Client.prototype._get = function (key, headers, callback) {
var request = this._client.get(key, headers);
if (this.timeout) {
request.setTimeout(this.timeout);
}
request.on('response', this._handleResponse.bind(this, callback));
request.on('error', function (error) {
callback(error, null);
});
request.once('timeout', function () {
request.abort();
});
request.end();
};
/**
* Wraps knox request and manages request events
*
* @private
* @param {string} key
* @param {function} callback
*/
S3Client.prototype._head = function (key, headers, callback) {
var request = this._client.head(key, headers);
if (this.timeout) {
request.setTimeout(this.timeout);
}
request.on('response', this._handleResponse.bind(this, callback));
request.on('error', function (error) {
callback(error, null);
});
request.once('timeout', function () {
request.abort();
});
request.end();
};
/**
* Wraps knox request and manages request events
*
* @private
* @param {string} key
* @param {function} callback
*/
S3Client.prototype._del = function (key, callback) {
var request = this._client.del(key);
if (this.timeout) {
request.setTimeout(this.timeout);
}
request.on('response', this._handleResponse.bind(this, callback));
request.on('error', function (error) {
callback(error, null);
});
request.once('timeout', function () {
request.abort();
});
request.end();
};
/**
* @private
* @param {function} callback
* @param {http.response} res
*/
S3Client.prototype._handleResponse = function (callback, res) {
var dataLength = 0;
var buffers = [];
res.on('data', function (data) {
buffers.push(data);
dataLength += data.length;
});
res.on('end', function () {
var resultBuffer = new Buffer(dataLength);
var bufferPosition = 0;
for (var i = 0, l = buffers.length; i < l; i++) {
buffers[i].copy(resultBuffer, bufferPosition);
bufferPosition += buffers[i].length;
}
var response = new Response();
response.setStatusCode(res.statusCode);
response.setHeaders(res.headers);
response._setBody(resultBuffer);
callback(null, response);
});
};
/**
* Function for handling retries
*
* @private
* @param {function} fnc function to call
* @param {function} callback callback fired when specified number
* of retries reached or response recieved
*/
S3Client.prototype._spawnFunction = function (fnc, callback) {
var maxRetry = this.retry;
fnc(function (err, data) {
if (err && maxRetry--) {
console.log('S3Client retrying operation...', maxRetry);
fnc(arguments.callee);
} else {
callback(err, data);
}
});
};
S3Client.prototype._getError = function (err) {
var error = new Error(err.msg);
error.code = err.code;
return error;
};
/**
* Checks if passed ACL allows for public read.
*
* @param {Object} acl ACL
* @returns {Boolean} True or false depending on ACL configuration
*/
S3Client.isPublicRead = function (acl) {
try {
var grants = acl.AccessControlList.Grant;
var grant;
for (var i = 0, len = grants.length; i < len; i++) {
grant = grants[i];
if (grant.Grantee.URI == 'http://acs.amazonaws.com/groups/global/AllUsers') {
if (['READ', 'FULL_CONTROL'].indexOf(grant.Permission) != -1) {
return true;
}
}
}
return false;
} catch (ex) {
console.warn('S3Client error processing ACLs', ex);
return false;
}
};
S3Client.Error = {};
S3Client.Error.ENOBODY = {code: 'ENOBODY', msg: 'No body given'};
S3Client.Error.ENOKEY = {code: 'ENOKEY', msg: 'No key give'};
S3Client.Error.ENOHEADERS = {code: 'ENOHEADERS', msg: 'No headers given'};
exports.S3Client = S3Client;