gl-store-s3.js/node_modules/aws-sdk/lib/discover_endpoint.js

360 lines
13 KiB
JavaScript
Raw Normal View History

2019-05-08 10:43:08 +00:00
var AWS = require('./core');
var util = require('./util');
var endpointDiscoveryEnabledEnvs = ['AWS_ENABLE_ENDPOINT_DISCOVERY', 'AWS_ENDPOINT_DISCOVERY_ENABLED'];
/**
* Generate key (except resources and operation part) to index the endpoints in the cache
* If input shape has endpointdiscoveryid trait then use
* accessKey + operation + resources + region + service as cache key
* If input shape doesn't have endpointdiscoveryid trait then use
* accessKey + region + service as cache key
* @return [map<String,String>] object with keys to index endpoints.
* @api private
*/
function getCacheKey(request) {
var service = request.service;
var api = service.api || {};
var operations = api.operations;
var identifiers = {};
if (service.config.region) {
identifiers.region = service.config.region;
}
if (api.serviceId) {
identifiers.serviceId = api.serviceId;
}
if (service.config.credentials.accessKeyId) {
identifiers.accessKeyId = service.config.credentials.accessKeyId;
}
return identifiers;
}
/**
* Recursive helper for marshallCustomIdentifiers().
* Looks for required string input members that have 'endpointdiscoveryid' trait.
* @api private
*/
function marshallCustomIdentifiersHelper(result, params, shape) {
if (!shape || params === undefined || params === null) return;
if (shape.type === 'structure' && shape.required && shape.required.length > 0) {
util.arrayEach(shape.required, function(name) {
var memberShape = shape.members[name];
if (memberShape.endpointDiscoveryId === true) {
var locationName = memberShape.isLocationName ? memberShape.name : name;
result[locationName] = String(params[name]);
} else {
marshallCustomIdentifiersHelper(result, params[name], memberShape);
}
});
}
}
/**
* Get custom identifiers for cache key.
* Identifies custom identifiers by checking each shape's `endpointDiscoveryId` trait.
* @param [object] request object
* @param [object] input shape of the given operation's api
* @api private
*/
function marshallCustomIdentifiers(request, shape) {
var identifiers = {};
marshallCustomIdentifiersHelper(identifiers, request.params, shape);
return identifiers;
}
/**
* Call endpoint discovery operation when it's optional.
* When endpoint is available in cache then use the cached endpoints. If endpoints
* are unavailable then use regional endpoints and call endpoint discovery operation
* asynchronously. This is turned off by default.
* @param [object] request object
* @api private
*/
function optionalDiscoverEndpoint(request) {
var service = request.service;
var api = service.api;
var operationModel = api.operations ? api.operations[request.operation] : undefined;
var inputShape = operationModel ? operationModel.input : undefined;
var identifiers = marshallCustomIdentifiers(request, inputShape);
var cacheKey = getCacheKey(request);
if (Object.keys(identifiers).length > 0) {
cacheKey = util.update(cacheKey, identifiers);
if (operationModel) cacheKey.operation = operationModel.name;
}
var endpoints = AWS.endpointCache.get(cacheKey);
if (endpoints && endpoints.length === 1 && endpoints[0].Address === '') {
//endpoint operation is being made but response not yet received
//or endpoint operation just failed in 1 minute
return;
} else if (endpoints && endpoints.length > 0) {
//found endpoint record from cache
request.httpRequest.updateEndpoint(endpoints[0].Address);
} else {
//endpoint record not in cache or outdated. make discovery operation
var endpointRequest = service.makeRequest(api.endpointOperation, {
Operation: operationModel.name,
Identifiers: identifiers,
});
addApiVersionHeader(endpointRequest);
endpointRequest.removeListener('validate', AWS.EventListeners.Core.VALIDATE_PARAMETERS);
endpointRequest.removeListener('retry', AWS.EventListeners.Core.RETRY_CHECK);
//put in a placeholder for endpoints already requested, prevent
//too much in-flight calls
AWS.endpointCache.put(cacheKey, [{
Address: '',
CachePeriodInMinutes: 1
}]);
endpointRequest.send(function(err, data) {
if (data && data.Endpoints) {
AWS.endpointCache.put(cacheKey, data.Endpoints);
} else if (err) {
AWS.endpointCache.put(cacheKey, [{
Address: '',
CachePeriodInMinutes: 1 //not to make more endpoint operation in next 1 minute
}]);
}
});
}
}
var requestQueue = {};
/**
* Call endpoint discovery operation when it's required.
* When endpoint is available in cache then use cached ones. If endpoints are
* unavailable then SDK should call endpoint operation then use returned new
* endpoint for the api call. SDK will automatically attempt to do endpoint
* discovery. This is turned off by default
* @param [object] request object
* @api private
*/
function requiredDiscoverEndpoint(request, done) {
var service = request.service;
var api = service.api;
var operationModel = api.operations ? api.operations[request.operation] : undefined;
var inputShape = operationModel ? operationModel.input : undefined;
var identifiers = marshallCustomIdentifiers(request, inputShape);
var cacheKey = getCacheKey(request);
if (Object.keys(identifiers).length > 0) {
cacheKey = util.update(cacheKey, identifiers);
if (operationModel) cacheKey.operation = operationModel.name;
}
var cacheKeyStr = AWS.EndpointCache.getKeyString(cacheKey);
var endpoints = AWS.endpointCache.get(cacheKeyStr); //endpoint cache also accepts string keys
if (endpoints && endpoints.length === 1 && endpoints[0].Address === '') {
//endpoint operation is being made but response not yet received
//push request object to a pending queue
if (!requestQueue[cacheKeyStr]) requestQueue[cacheKeyStr] = [];
requestQueue[cacheKeyStr].push({request: request, callback: done});
return;
} else if (endpoints && endpoints.length > 0) {
request.httpRequest.updateEndpoint(endpoints[0].Address);
done();
} else {
var endpointRequest = service.makeRequest(api.endpointOperation, {
Operation: operationModel.name,
Identifiers: identifiers,
});
endpointRequest.removeListener('validate', AWS.EventListeners.Core.VALIDATE_PARAMETERS);
addApiVersionHeader(endpointRequest);
//put in a placeholder for endpoints already requested, prevent
//too much in-flight calls
AWS.endpointCache.put(cacheKeyStr, [{
Address: '',
CachePeriodInMinutes: 60 //long-live cache
}]);
endpointRequest.send(function(err, data) {
if (err) {
var errorParams = {
code: 'EndpointDiscoveryException',
message: 'Request cannot be fulfilled without specifying an endpoint',
retryable: false
};
request.response.error = util.error(err, errorParams);
AWS.endpointCache.remove(cacheKey);
//fail all the pending requests in batch
if (requestQueue[cacheKeyStr]) {
var pendingRequests = requestQueue[cacheKeyStr];
util.arrayEach(pendingRequests, function(requestContext) {
requestContext.request.response.error = util.error(err, errorParams);
requestContext.callback();
});
delete requestQueue[cacheKeyStr];
}
} else if (data) {
AWS.endpointCache.put(cacheKeyStr, data.Endpoints);
request.httpRequest.updateEndpoint(data.Endpoints[0].Address);
//update the endpoint for all the pending requests in batch
if (requestQueue[cacheKeyStr]) {
var pendingRequests = requestQueue[cacheKeyStr];
util.arrayEach(pendingRequests, function(requestContext) {
requestContext.request.httpRequest.updateEndpoint(data.Endpoints[0].Address);
requestContext.callback();
});
delete requestQueue[cacheKeyStr];
}
}
done();
});
}
}
/**
* add api version header to endpoint operation
* @api private
*/
function addApiVersionHeader(endpointRequest) {
var api = endpointRequest.service.api;
var apiVersion = api.apiVersion;
if (apiVersion && !endpointRequest.httpRequest.headers['x-amz-api-version']) {
endpointRequest.httpRequest.headers['x-amz-api-version'] = apiVersion;
}
}
/**
* If api call gets invalid endpoint exception, SDK should attempt to remove the invalid
* endpoint from cache.
* @api private
*/
function invalidateCachedEndpoints(response) {
var error = response.error;
var httpResponse = response.httpResponse;
if (error &&
(error.code === 'InvalidEndpointException' || httpResponse.statusCode === 421)
) {
var request = response.request;
var operations = request.service.api.operations || {};
var inputShape = operations[request.operation] ? operations[request.operation].input : undefined;
var identifiers = marshallCustomIdentifiers(request, inputShape);
var cacheKey = getCacheKey(request);
if (Object.keys(identifiers).length > 0) {
cacheKey = util.update(cacheKey, identifiers);
if (operations[request.operation]) cacheKey.operation = operations[request.operation].name;
}
AWS.endpointCache.remove(cacheKey);
}
}
/**
* If endpoint is explicitly configured, SDK should not do endpoint discovery in anytime.
* @param [object] client Service client object.
* @api private
*/
function hasCustomEndpoint(client) {
//if set endpoint is set for specific client, enable endpoint discovery will raise an error.
if (client._originalConfig && client._originalConfig.endpoint && client._originalConfig.endpointDiscoveryEnabled === true) {
throw util.error(new Error(), {
code: 'ConfigurationException',
message: 'Custom endpoint is supplied; endpointDiscoveryEnabled must not be true.'
});
};
var svcConfig = AWS.config[client.serviceIdentifier] || {};
return Boolean(AWS.config.endpoint || svcConfig.endpoint || (client._originalConfig && client._originalConfig.endpoint));
}
/**
* @api private
*/
function isFalsy(value) {
return ['false', '0'].indexOf(value) >= 0;
}
/**
* If endpoint discovery should perform for this request when endpoint discovery is optional.
* SDK performs config resolution in order like below:
* 1. If turned on client configuration(default to off) then turn on endpoint discovery.
* 2. If turned on in env AWS_ENABLE_ENDPOINT_DISCOVERY then turn on endpoint discovery.
* 3. If turned on in shared ini config file with key 'endpoint_discovery_enabled', then
* turn on endpoint discovery.
* @param [object] request request object.
* @api private
*/
function isEndpointDiscoveryApplicable(request) {
var service = request.service || {};
if (service.config.endpointDiscoveryEnabled === true) return true;
//shared ini file is only available in Node
//not to check env in browser
if (util.isBrowser()) return false;
for (var i = 0; i < endpointDiscoveryEnabledEnvs.length; i++) {
var env = endpointDiscoveryEnabledEnvs[i];
if (Object.prototype.hasOwnProperty.call(process.env, env)) {
if (process.env[env] === '' || process.env[env] === undefined) {
throw util.error(new Error(), {
code: 'ConfigurationException',
message: 'environmental variable ' + env + ' cannot be set to nothing'
});
}
if (!isFalsy(process.env[env])) return true;
}
}
var configFile = {};
try {
configFile = AWS.util.iniLoader ? AWS.util.iniLoader.loadFrom({
isConfig: true,
filename: process.env[AWS.util.sharedConfigFileEnv]
}) : {};
} catch (e) {}
var sharedFileConfig = configFile[
process.env.AWS_PROFILE || AWS.util.defaultProfile
] || {};
if (Object.prototype.hasOwnProperty.call(sharedFileConfig, 'endpoint_discovery_enabled')) {
if (sharedFileConfig.endpoint_discovery_enabled === undefined) {
throw util.error(new Error(), {
code: 'ConfigurationException',
message: 'config file entry \'endpoint_discovery_enabled\' cannot be set to nothing'
});
}
if (!isFalsy(sharedFileConfig.endpoint_discovery_enabled)) return true;
}
return false;
}
/**
* attach endpoint discovery logic to request object
* @param [object] request
* @api private
*/
function discoverEndpoint(request, done) {
var service = request.service || {};
if (hasCustomEndpoint(service) || request.isPresigned()) return done();
if (!isEndpointDiscoveryApplicable(request)) return done();
request.httpRequest.appendToUserAgent('endpoint-discovery');
var operations = service.api.operations || {};
var operationModel = operations[request.operation];
var isEndpointDiscoveryRequired = operationModel ? operationModel.endpointDiscoveryRequired : 'NULL';
switch (isEndpointDiscoveryRequired) {
case 'OPTIONAL':
optionalDiscoverEndpoint(request);
request.addNamedListener('INVALIDATE_CACHED_ENDPOINTS', 'extractError', invalidateCachedEndpoints);
done();
break;
case 'REQUIRED':
request.addNamedListener('INVALIDATE_CACHED_ENDPOINTS', 'extractError', invalidateCachedEndpoints);
requiredDiscoverEndpoint(request, done);
break;
case 'NULL':
default:
done();
break;
}
}
module.exports = {
discoverEndpoint: discoverEndpoint,
requiredDiscoverEndpoint: requiredDiscoverEndpoint,
optionalDiscoverEndpoint: optionalDiscoverEndpoint,
marshallCustomIdentifiers: marshallCustomIdentifiers,
getCacheKey: getCacheKey,
invalidateCachedEndpoint: invalidateCachedEndpoints,
};