UNPKG

aliyun-sdk

Version:
378 lines (334 loc) 10.5 kB
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; } });