aliyun-sdk
Version:
Aliyun SDK for JavaScript
378 lines (334 loc) • 10.5 kB
JavaScript
var ALY = require('./core');
var inherit = ALY.util.inherit;
/**
* The service class representing an ALY service.
*
* @abstract
*
* @!attribute apiVersions
* @return [Array<String>] the list of API versions supported by this service.
* @readonly
*/
ALY.Service = inherit({
/**
* Create a new service object with a configuration object
*
* @param config [map] a map of configuration options
*/
constructor: function Service(config) {
if (!this.loadServiceClass) {
throw ALY.util.error(new Error(),
'Service must be constructed with `new\' operator');
}
var ServiceClass = this.loadServiceClass(config || {});
if (ServiceClass) return new ServiceClass(config);
this.initialize(config);
},
/**
* @api private
*/
initialize: function initialize(config) {
this.config = new ALY.Config(config);
},
/**
* @api private
*/
loadServiceClass: function loadServiceClass(serviceConfig) {
if (!ALY.util.isEmpty(this.api)) {
return;
} else if (!this.constructor.services) {
return;
} else {
return this.getLatestServiceClass(serviceConfig.apiVersion);
}
},
/**
* @api private
*/
getLatestServiceClass: function getLatestServiceClass(version) {
if (this.constructor.services[version] === null) {
ALY.Service.defineServiceApi(this.constructor, version);
}
return this.constructor.services[version];
},
/**
* @api private
*/
api: {},
/**
* @api private
*/
defaultRetryCount: 3,
/**
* Calls an operation on a service with the given input parameters.
*
* @param operation [String] the name of the operation to call on the service.
* @param params [map] a map of input options for the operation
* @callback callback function(err, data)
* If a callback is supplied, it is called when a response is returned
* from the service.
* @param err [Error] the error object returned from the request.
* Set to `null` if the request is successful.
* @param data [Object] the de-serialized data returned from
* the request. Set to `null` if a request error occurs.
*/
makeRequest: function makeRequest(operation, params, callback) {
if (typeof params === 'function') {
callback = params;
params = null;
}
params = params || {};
if (this.config.params) { // copy only toplevel bound params
var rules = this.api.operations[operation];
if (rules) {
params = ALY.util.copy(params);
ALY.util.each(this.config.params, function(key, value) {
if (rules.input.members[key]) {
if (params[key] === undefined || params[key] === null) {
params[key] = value;
}
}
});
}
}
var request = new ALY.Request(this, operation, params);
this.addAllRequestListeners(request);
if (callback) request.send(callback);
return request;
},
/**
* Calls an operation on a service with the given input parameters, without
* any authentication data. This method is useful for "public" API operations.
*
* @param operation [String] the name of the operation to call on the service.
* @param params [map] a map of input options for the operation
* @callback callback function(err, data)
* If a callback is supplied, it is called when a response is returned
* from the service.
* @param err [Error] the error object returned from the request.
* Set to `null` if the request is successful.
* @param data [Object] the de-serialized data returned from
* the request. Set to `null` if a request error occurs.
*/
makeUnauthenticatedRequest: function makeUnauthenticatedRequest(operation, params, callback) {
if (typeof params === 'function') {
callback = params;
params = {};
}
var request = this.makeRequest(operation, params);
request.removeListener('sign', ALY.EventListeners.Core.SIGN);
if (this.api.format === 'query') { // query services turn into GET requests
request.addListener('build', function convertToGET(request) {
request.httpRequest.method = 'GET';
request.httpRequest.path = '/?' + request.httpRequest.body;
request.httpRequest.body = '';
// don't need these headers on a GET request
delete request.httpRequest.headers['Content-Length'];
delete request.httpRequest.headers['Content-Type'];
});
}
return callback ? request.send(callback) : request;
},
/**
* @api private
*/
addAllRequestListeners: function addAllRequestListeners(request) {
var list = [ALY.events, ALY.EventListeners.Core,
this.serviceInterface()];
for (var i = 0; i < list.length; i++) {
if (list[i]) request.addListeners(list[i]);
}
// disable parameter validation
if (!this.config.paramValidation) {
request.removeListener('validate',
ALY.EventListeners.Core.VALIDATE_PARAMETERS);
}
if (this.config.logger) { // add logging events
request.addListeners(ALY.EventListeners.Logger);
}
this.setupRequestListeners(request);
},
/**
* Override this method to setup any custom request listeners for each
* new request to the service.
*
* @abstract
*/
setupRequestListeners: function setupRequestListeners() {
},
/**
* Gets the signer class for a given request
* @api private
*/
getSignerClass: function getSignerClass() {
var version = this.api.signatureVersion;
return ALY.Signers.RequestSigner.getVersion(version);
},
/**
* @api private
*/
serviceInterface: function serviceInterface() {
switch (this.api.format) {
case 'query': return ALY.EventListeners.Query;
case 'json': return ALY.EventListeners.Json;
case 'rest': return ALY.EventListeners.Rest;
case 'rest-json': return ALY.EventListeners.RestJson;
case 'pop': return ALY.EventListeners.Pop;
case 'top': return ALY.EventListeners.Top;
case 'rest-xml': return ALY.EventListeners.RestXml;
}
if (this.api.format) {
throw new Error('Invalid service `format\' ' +
this.api.format + ' in API config');
}
},
/**
* @api private
*/
successfulResponse: function successfulResponse(resp) {
return resp.httpResponse.statusCode < 300;
},
/**
* How many times a failed request should be retried before giving up.
* the defaultRetryCount can be overriden by service classes.
*
* @api private
*/
numRetries: function numRetries() {
if (this.config.maxRetries !== undefined) {
return this.config.maxRetries;
} else {
return this.defaultRetryCount;
}
},
/**
* @api private
*/
retryDelays: function retryDelays() {
var retryCount = this.numRetries();
var delays = [];
for (var i = 0; i < retryCount; ++i) {
delays[i] = Math.pow(2, i) * 30;
}
return delays;
},
/**
* @api private
*/
retryableError: function retryableError(error) {
if (this.networkingError(error)) return true;
if (this.throttledError(error)) return true;
if (error.statusCode >= 500) return true;
return false;
},
/**
* @api private
*/
networkingError: function networkingError(error) {
return error.code == 'NetworkingError';
},
/**
* @api private
*/
throttledError: function throttledError(error) {
// this logic varies between services
return (error.code == 'ProvisionedThroughputExceededException');
},
/**
* @api private
*/
isRegionCN: function isRegionCN() {
if (!this.config.region) return false;
return this.config.region.match(/^cn-/) ? true : false;
},
/**
* @api private
*/
isRegionV4: function isRegionV4() {
return this.isRegionCN();
},
/**
* @api private
*/
paginationConfig: function paginationConfig(operation, throwException) {
function fail(name) {
if (throwException) {
var e = new Error();
throw ALY.util.error(e, 'No pagination configuration for ' + name);
}
return null;
}
if (!this.api.pagination) return fail('service');
if (!this.api.pagination[operation]) return fail(operation);
return this.api.pagination[operation];
}
});
ALY.util.update(ALY.Service, {
/**
* Adds one method for each operation described in the api configuration
*
* @api private
*/
defineMethods: function defineMethods(svc) {
ALY.util.each(svc.prototype.api.operations, function iterator(method) {
if (svc.prototype[method]) return;
svc.prototype[method] = function (params, callback) {
return this.makeRequest(method, params, callback);
};
});
},
defineService: function defineService(serviceIdentifier, versions, features) {
if (!Array.isArray(versions)) {
features = versions;
versions = [];
}
var svc = inherit(ALY.Service, features || {});
if (typeof serviceIdentifier === 'string') {
// create versions hash
var services = {};
for (var i = 0; i < versions.length; i++) {
services[versions[i]] = null;
}
svc.services = svc.services || services;
svc.apiVersions = Object.keys(svc.services).sort();
svc.serviceIdentifier = svc.serviceIdentifier || serviceIdentifier;
} else { // defineService called with an API
svc.prototype.api = serviceIdentifier;
ALY.Service.defineMethods(svc);
}
return svc;
},
/**
* @api private
*/
defineServiceApi: function defineServiceApi(superclass, version) {
var svc = inherit(superclass, {
serviceIdentifier: superclass.serviceIdentifier
});
if (typeof version === 'string') {
var file = superclass.serviceIdentifier + '-' + version;
var path = __dirname + '/../apis/' + file + '.json';
try {
if(ALY.util.isBrowser()) {
svc.prototype.api = require(file + '.json');
}
else {
var fs = require('fs');
svc.prototype.api = JSON.parse(fs.readFileSync(path));
}
} catch (err) {
throw ALY.util.error(err, {
message: 'Could not find API configuration ' + file
});
}
if (!superclass.services.hasOwnProperty(version)) {
superclass.apiVersions.push(version);
}
superclass.services[version] = svc;
} else {
setApi(version);
}
ALY.Service.defineMethods(svc);
return svc;
}
});