ClientResolver.php 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. <?php
  2. namespace Aws;
  3. use Aws\Api\Validator;
  4. use Aws\Api\ApiProvider;
  5. use Aws\Api\Service;
  6. use Aws\ClientSideMonitoring\ApiCallAttemptMonitoringMiddleware;
  7. use Aws\ClientSideMonitoring\ApiCallMonitoringMiddleware;
  8. use Aws\ClientSideMonitoring\Configuration;
  9. use Aws\Credentials\Credentials;
  10. use Aws\Credentials\CredentialsInterface;
  11. use Aws\Endpoint\PartitionEndpointProvider;
  12. use Aws\EndpointDiscovery\ConfigurationInterface;
  13. use Aws\EndpointDiscovery\ConfigurationProvider;
  14. use Aws\EndpointDiscovery\EndpointDiscoveryMiddleware;
  15. use Aws\Exception\InvalidRegionException;
  16. use Aws\Retry\ConfigurationInterface as RetryConfigInterface;
  17. use Aws\Retry\ConfigurationProvider as RetryConfigProvider;
  18. use Aws\Signature\SignatureProvider;
  19. use Aws\Endpoint\EndpointProvider;
  20. use Aws\Credentials\CredentialProvider;
  21. use InvalidArgumentException as IAE;
  22. use Psr\Http\Message\RequestInterface;
  23. /**
  24. * @internal Resolves a hash of client arguments to construct a client.
  25. */
  26. class ClientResolver
  27. {
  28. /** @var array */
  29. private $argDefinitions;
  30. /** @var array Map of types to a corresponding function */
  31. private static $typeMap = [
  32. 'resource' => 'is_resource',
  33. 'callable' => 'is_callable',
  34. 'int' => 'is_int',
  35. 'bool' => 'is_bool',
  36. 'string' => 'is_string',
  37. 'object' => 'is_object',
  38. 'array' => 'is_array',
  39. ];
  40. private static $defaultArgs = [
  41. 'service' => [
  42. 'type' => 'value',
  43. 'valid' => ['string'],
  44. 'doc' => 'Name of the service to utilize. This value will be supplied by default when using one of the SDK clients (e.g., Aws\\S3\\S3Client).',
  45. 'required' => true,
  46. 'internal' => true
  47. ],
  48. 'exception_class' => [
  49. 'type' => 'value',
  50. 'valid' => ['string'],
  51. 'doc' => 'Exception class to create when an error occurs.',
  52. 'default' => 'Aws\Exception\AwsException',
  53. 'internal' => true
  54. ],
  55. 'scheme' => [
  56. 'type' => 'value',
  57. 'valid' => ['string'],
  58. 'default' => 'https',
  59. 'doc' => 'URI scheme to use when connecting connect. The SDK will utilize "https" endpoints (i.e., utilize SSL/TLS connections) by default. You can attempt to connect to a service over an unencrypted "http" endpoint by setting ``scheme`` to "http".',
  60. ],
  61. 'disable_host_prefix_injection' => [
  62. 'type' => 'value',
  63. 'valid' => ['bool'],
  64. 'doc' => 'Set to true to disable host prefix injection logic for services that use it. This disables the entire prefix injection, including the portions supplied by user-defined parameters. Setting this flag will have no effect on services that do not use host prefix injection.',
  65. 'default' => false,
  66. ],
  67. 'endpoint' => [
  68. 'type' => 'value',
  69. 'valid' => ['string'],
  70. 'doc' => 'The full URI of the webservice. This is only required when connecting to a custom endpoint (e.g., a local version of S3).',
  71. 'fn' => [__CLASS__, '_apply_endpoint'],
  72. ],
  73. 'region' => [
  74. 'type' => 'value',
  75. 'valid' => ['string'],
  76. 'required' => [__CLASS__, '_missing_region'],
  77. 'doc' => 'Region to connect to. See http://docs.aws.amazon.com/general/latest/gr/rande.html for a list of available regions.',
  78. ],
  79. 'version' => [
  80. 'type' => 'value',
  81. 'valid' => ['string'],
  82. 'required' => [__CLASS__, '_missing_version'],
  83. 'doc' => 'The version of the webservice to utilize (e.g., 2006-03-01).',
  84. ],
  85. 'signature_provider' => [
  86. 'type' => 'value',
  87. 'valid' => ['callable'],
  88. 'doc' => 'A callable that accepts a signature version name (e.g., "v4"), a service name, and region, and returns a SignatureInterface object or null. This provider is used to create signers utilized by the client. See Aws\\Signature\\SignatureProvider for a list of built-in providers',
  89. 'default' => [__CLASS__, '_default_signature_provider'],
  90. ],
  91. 'api_provider' => [
  92. 'type' => 'value',
  93. 'valid' => ['callable'],
  94. 'doc' => 'An optional PHP callable that accepts a type, service, and version argument, and returns an array of corresponding configuration data. The type value can be one of api, waiter, or paginator.',
  95. 'fn' => [__CLASS__, '_apply_api_provider'],
  96. 'default' => [ApiProvider::class, 'defaultProvider'],
  97. ],
  98. 'endpoint_provider' => [
  99. 'type' => 'value',
  100. 'valid' => ['callable'],
  101. 'fn' => [__CLASS__, '_apply_endpoint_provider'],
  102. 'doc' => 'An optional PHP callable that accepts a hash of options including a "service" and "region" key and returns NULL or a hash of endpoint data, of which the "endpoint" key is required. See Aws\\Endpoint\\EndpointProvider for a list of built-in providers.',
  103. 'default' => [__CLASS__, '_default_endpoint_provider'],
  104. ],
  105. 'serializer' => [
  106. 'default' => [__CLASS__, '_default_serializer'],
  107. 'fn' => [__CLASS__, '_apply_serializer'],
  108. 'internal' => true,
  109. 'type' => 'value',
  110. 'valid' => ['callable'],
  111. ],
  112. 'signature_version' => [
  113. 'type' => 'config',
  114. 'valid' => ['string'],
  115. 'doc' => 'A string representing a custom signature version to use with a service (e.g., v4). Note that per/operation signature version MAY override this requested signature version.',
  116. 'default' => [__CLASS__, '_default_signature_version'],
  117. ],
  118. 'signing_name' => [
  119. 'type' => 'config',
  120. 'valid' => ['string'],
  121. 'doc' => 'A string representing a custom service name to be used when calculating a request signature.',
  122. 'default' => [__CLASS__, '_default_signing_name'],
  123. ],
  124. 'signing_region' => [
  125. 'type' => 'config',
  126. 'valid' => ['string'],
  127. 'doc' => 'A string representing a custom region name to be used when calculating a request signature.',
  128. 'default' => [__CLASS__, '_default_signing_region'],
  129. ],
  130. 'profile' => [
  131. 'type' => 'config',
  132. 'valid' => ['string'],
  133. 'doc' => 'Allows you to specify which profile to use when credentials are created from the AWS credentials file in your HOME directory. This setting overrides the AWS_PROFILE environment variable. Note: Specifying "profile" will cause the "credentials" key to be ignored.',
  134. 'fn' => [__CLASS__, '_apply_profile'],
  135. ],
  136. 'credentials' => [
  137. 'type' => 'value',
  138. 'valid' => [CredentialsInterface::class, CacheInterface::class, 'array', 'bool', 'callable'],
  139. 'doc' => 'Specifies the credentials used to sign requests. Provide an Aws\Credentials\CredentialsInterface object, an associative array of "key", "secret", and an optional "token" key, `false` to use null credentials, or a callable credentials provider used to create credentials or return null. See Aws\\Credentials\\CredentialProvider for a list of built-in credentials providers. If no credentials are provided, the SDK will attempt to load them from the environment.',
  140. 'fn' => [__CLASS__, '_apply_credentials'],
  141. 'default' => [__CLASS__, '_default_credential_provider'],
  142. ],
  143. 'endpoint_discovery' => [
  144. 'type' => 'value',
  145. 'valid' => [ConfigurationInterface::class, CacheInterface::class, 'array', 'callable'],
  146. 'doc' => 'Specifies settings for endpoint discovery. Provide an instance of Aws\EndpointDiscovery\ConfigurationInterface, an instance Aws\CacheInterface, a callable that provides a promise for a Configuration object, or an associative array with the following keys: enabled: (bool) Set to true to enable endpoint discovery, false to explicitly disable it. Defaults to false; cache_limit: (int) The maximum number of keys in the endpoints cache. Defaults to 1000.',
  147. 'fn' => [__CLASS__, '_apply_endpoint_discovery'],
  148. 'default' => [__CLASS__, '_default_endpoint_discovery_provider']
  149. ],
  150. 'stats' => [
  151. 'type' => 'value',
  152. 'valid' => ['bool', 'array'],
  153. 'default' => false,
  154. 'doc' => 'Set to true to gather transfer statistics on requests sent. Alternatively, you can provide an associative array with the following keys: retries: (bool) Set to false to disable reporting on retries attempted; http: (bool) Set to true to enable collecting statistics from lower level HTTP adapters (e.g., values returned in GuzzleHttp\TransferStats). HTTP handlers must support an http_stats_receiver option for this to have an effect; timer: (bool) Set to true to enable a command timer that reports the total wall clock time spent on an operation in seconds.',
  155. 'fn' => [__CLASS__, '_apply_stats'],
  156. ],
  157. 'retries' => [
  158. 'type' => 'value',
  159. 'valid' => ['int', RetryConfigInterface::class, CacheInterface::class, 'callable', 'array'],
  160. 'doc' => "Configures the retry mode and maximum number of allowed retries for a client (pass 0 to disable retries). Provide an integer for 'legacy' mode with the specified number of retries. Otherwise provide an instance of Aws\Retry\ConfigurationInterface, an instance of Aws\CacheInterface, a callable function, or an array with the following keys: mode: (string) Set to 'legacy', 'standard' (uses retry quota management), or 'adapative' (an experimental mode that adds client-side rate limiting to standard mode); max_attempts: (int) The maximum number of attempts for a given request. ",
  161. 'fn' => [__CLASS__, '_apply_retries'],
  162. 'default' => [RetryConfigProvider::class, 'defaultProvider']
  163. ],
  164. 'validate' => [
  165. 'type' => 'value',
  166. 'valid' => ['bool', 'array'],
  167. 'default' => true,
  168. 'doc' => 'Set to false to disable client-side parameter validation. Set to true to utilize default validation constraints. Set to an associative array of validation options to enable specific validation constraints.',
  169. 'fn' => [__CLASS__, '_apply_validate'],
  170. ],
  171. 'debug' => [
  172. 'type' => 'value',
  173. 'valid' => ['bool', 'array'],
  174. 'doc' => 'Set to true to display debug information when sending requests. Alternatively, you can provide an associative array with the following keys: logfn: (callable) Function that is invoked with log messages; stream_size: (int) When the size of a stream is greater than this number, the stream data will not be logged (set to "0" to not log any stream data); scrub_auth: (bool) Set to false to disable the scrubbing of auth data from the logged messages; http: (bool) Set to false to disable the "debug" feature of lower level HTTP adapters (e.g., verbose curl output).',
  175. 'fn' => [__CLASS__, '_apply_debug'],
  176. ],
  177. 'csm' => [
  178. 'type' => 'value',
  179. 'valid' => [\Aws\ClientSideMonitoring\ConfigurationInterface::class, 'callable', 'array', 'bool'],
  180. 'doc' => 'CSM options for the client. Provides a callable wrapping a promise, a boolean "false", an instance of ConfigurationInterface, or an associative array of "enabled", "host", "port", and "client_id".',
  181. 'fn' => [__CLASS__, '_apply_csm'],
  182. 'default' => [\Aws\ClientSideMonitoring\ConfigurationProvider::class, 'defaultProvider']
  183. ],
  184. 'http' => [
  185. 'type' => 'value',
  186. 'valid' => ['array'],
  187. 'default' => [],
  188. 'doc' => 'Set to an array of SDK request options to apply to each request (e.g., proxy, verify, etc.).',
  189. ],
  190. 'http_handler' => [
  191. 'type' => 'value',
  192. 'valid' => ['callable'],
  193. 'doc' => 'An HTTP handler is a function that accepts a PSR-7 request object and returns a promise that is fulfilled with a PSR-7 response object or rejected with an array of exception data. NOTE: This option supersedes any provided "handler" option.',
  194. 'fn' => [__CLASS__, '_apply_http_handler']
  195. ],
  196. 'handler' => [
  197. 'type' => 'value',
  198. 'valid' => ['callable'],
  199. 'doc' => 'A handler that accepts a command object, request object and returns a promise that is fulfilled with an Aws\ResultInterface object or rejected with an Aws\Exception\AwsException. A handler does not accept a next handler as it is terminal and expected to fulfill a command. If no handler is provided, a default Guzzle handler will be utilized.',
  200. 'fn' => [__CLASS__, '_apply_handler'],
  201. 'default' => [__CLASS__, '_default_handler']
  202. ],
  203. 'ua_append' => [
  204. 'type' => 'value',
  205. 'valid' => ['string', 'array'],
  206. 'doc' => 'Provide a string or array of strings to send in the User-Agent header.',
  207. 'fn' => [__CLASS__, '_apply_user_agent'],
  208. 'default' => [],
  209. ],
  210. 'idempotency_auto_fill' => [
  211. 'type' => 'value',
  212. 'valid' => ['bool', 'callable'],
  213. 'doc' => 'Set to false to disable SDK to populate parameters that enabled \'idempotencyToken\' trait with a random UUID v4 value on your behalf. Using default value \'true\' still allows parameter value to be overwritten when provided. Note: auto-fill only works when cryptographically secure random bytes generator functions(random_bytes, openssl_random_pseudo_bytes or mcrypt_create_iv) can be found. You may also provide a callable source of random bytes.',
  214. 'default' => true,
  215. 'fn' => [__CLASS__, '_apply_idempotency_auto_fill']
  216. ],
  217. 'use_aws_shared_config_files' => [
  218. 'type' => 'value',
  219. 'valid' => ['bool'],
  220. 'doc' => 'Set to false to disable checking for shared aws config files usually located in \'~/.aws/config\' and \'~/.aws/credentials\'.',
  221. 'default' => true,
  222. ],
  223. ];
  224. /**
  225. * Gets an array of default client arguments, each argument containing a
  226. * hash of the following:
  227. *
  228. * - type: (string, required) option type described as follows:
  229. * - value: The default option type.
  230. * - config: The provided value is made available in the client's
  231. * getConfig() method.
  232. * - valid: (array, required) Valid PHP types or class names. Note: null
  233. * is not an allowed type.
  234. * - required: (bool, callable) Whether or not the argument is required.
  235. * Provide a function that accepts an array of arguments and returns a
  236. * string to provide a custom error message.
  237. * - default: (mixed) The default value of the argument if not provided. If
  238. * a function is provided, then it will be invoked to provide a default
  239. * value. The function is provided the array of options and is expected
  240. * to return the default value of the option. The default value can be a
  241. * closure and can not be a callable string that is not part of the
  242. * defaultArgs array.
  243. * - doc: (string) The argument documentation string.
  244. * - fn: (callable) Function used to apply the argument. The function
  245. * accepts the provided value, array of arguments by reference, and an
  246. * event emitter.
  247. *
  248. * Note: Order is honored and important when applying arguments.
  249. *
  250. * @return array
  251. */
  252. public static function getDefaultArguments()
  253. {
  254. return self::$defaultArgs;
  255. }
  256. /**
  257. * @param array $argDefinitions Client arguments.
  258. */
  259. public function __construct(array $argDefinitions)
  260. {
  261. $this->argDefinitions = $argDefinitions;
  262. }
  263. /**
  264. * Resolves client configuration options and attached event listeners.
  265. * Check for missing keys in passed arguments
  266. *
  267. * @param array $args Provided constructor arguments.
  268. * @param HandlerList $list Handler list to augment.
  269. *
  270. * @return array Returns the array of provided options.
  271. * @throws \InvalidArgumentException
  272. * @see Aws\AwsClient::__construct for a list of available options.
  273. */
  274. public function resolve(array $args, HandlerList $list)
  275. {
  276. $args['config'] = [];
  277. foreach ($this->argDefinitions as $key => $a) {
  278. // Add defaults, validate required values, and skip if not set.
  279. if (!isset($args[$key])) {
  280. if (isset($a['default'])) {
  281. // Merge defaults in when not present.
  282. if (is_callable($a['default'])
  283. && (
  284. is_array($a['default'])
  285. || $a['default'] instanceof \Closure
  286. )
  287. ) {
  288. $args[$key] = $a['default']($args);
  289. } else {
  290. $args[$key] = $a['default'];
  291. }
  292. } elseif (empty($a['required'])) {
  293. continue;
  294. } else {
  295. $this->throwRequired($args);
  296. }
  297. }
  298. // Validate the types against the provided value.
  299. foreach ($a['valid'] as $check) {
  300. if (isset(self::$typeMap[$check])) {
  301. $fn = self::$typeMap[$check];
  302. if ($fn($args[$key])) {
  303. goto is_valid;
  304. }
  305. } elseif ($args[$key] instanceof $check) {
  306. goto is_valid;
  307. }
  308. }
  309. $this->invalidType($key, $args[$key]);
  310. // Apply the value
  311. is_valid:
  312. if (isset($a['fn'])) {
  313. $a['fn']($args[$key], $args, $list);
  314. }
  315. if ($a['type'] === 'config') {
  316. $args['config'][$key] = $args[$key];
  317. }
  318. }
  319. return $args;
  320. }
  321. /**
  322. * Creates a verbose error message for an invalid argument.
  323. *
  324. * @param string $name Name of the argument that is missing.
  325. * @param array $args Provided arguments
  326. * @param bool $useRequired Set to true to show the required fn text if
  327. * available instead of the documentation.
  328. * @return string
  329. */
  330. private function getArgMessage($name, $args = [], $useRequired = false)
  331. {
  332. $arg = $this->argDefinitions[$name];
  333. $msg = '';
  334. $modifiers = [];
  335. if (isset($arg['valid'])) {
  336. $modifiers[] = implode('|', $arg['valid']);
  337. }
  338. if (isset($arg['choice'])) {
  339. $modifiers[] = 'One of ' . implode(', ', $arg['choice']);
  340. }
  341. if ($modifiers) {
  342. $msg .= '(' . implode('; ', $modifiers) . ')';
  343. }
  344. $msg = wordwrap("{$name}: {$msg}", 75, "\n ");
  345. if ($useRequired && is_callable($arg['required'])) {
  346. $msg .= "\n\n ";
  347. $msg .= str_replace("\n", "\n ", call_user_func($arg['required'], $args));
  348. } elseif (isset($arg['doc'])) {
  349. $msg .= wordwrap("\n\n {$arg['doc']}", 75, "\n ");
  350. }
  351. return $msg;
  352. }
  353. /**
  354. * Throw when an invalid type is encountered.
  355. *
  356. * @param string $name Name of the value being validated.
  357. * @param mixed $provided The provided value.
  358. * @throws \InvalidArgumentException
  359. */
  360. private function invalidType($name, $provided)
  361. {
  362. $expected = implode('|', $this->argDefinitions[$name]['valid']);
  363. $msg = "Invalid configuration value "
  364. . "provided for \"{$name}\". Expected {$expected}, but got "
  365. . describe_type($provided) . "\n\n"
  366. . $this->getArgMessage($name);
  367. throw new IAE($msg);
  368. }
  369. /**
  370. * Throws an exception for missing required arguments.
  371. *
  372. * @param array $args Passed in arguments.
  373. * @throws \InvalidArgumentException
  374. */
  375. private function throwRequired(array $args)
  376. {
  377. $missing = [];
  378. foreach ($this->argDefinitions as $k => $a) {
  379. if (empty($a['required'])
  380. || isset($a['default'])
  381. || isset($args[$k])
  382. ) {
  383. continue;
  384. }
  385. $missing[] = $this->getArgMessage($k, $args, true);
  386. }
  387. $msg = "Missing required client configuration options: \n\n";
  388. $msg .= implode("\n\n", $missing);
  389. throw new IAE($msg);
  390. }
  391. public static function _apply_retries($value, array &$args, HandlerList $list)
  392. {
  393. // A value of 0 for the config option disables retries
  394. if ($value) {
  395. $config = RetryConfigProvider::unwrap($value);
  396. if ($config->getMode() === 'legacy') {
  397. // # of retries is 1 less than # of attempts
  398. $decider = RetryMiddleware::createDefaultDecider(
  399. $config->getMaxAttempts() - 1
  400. );
  401. $list->appendSign(
  402. Middleware::retry($decider, null, $args['stats']['retries']),
  403. 'retry'
  404. );
  405. } else {
  406. $list->appendSign(
  407. RetryMiddlewareV2::wrap(
  408. $config,
  409. ['collect_stats' => $args['stats']['retries']]
  410. ),
  411. 'retry'
  412. );
  413. }
  414. }
  415. }
  416. public static function _apply_credentials($value, array &$args)
  417. {
  418. if (is_callable($value)) {
  419. return;
  420. }
  421. if ($value instanceof CredentialsInterface) {
  422. $args['credentials'] = CredentialProvider::fromCredentials($value);
  423. } elseif (is_array($value)
  424. && isset($value['key'])
  425. && isset($value['secret'])
  426. ) {
  427. $args['credentials'] = CredentialProvider::fromCredentials(
  428. new Credentials(
  429. $value['key'],
  430. $value['secret'],
  431. isset($value['token']) ? $value['token'] : null,
  432. isset($value['expires']) ? $value['expires'] : null
  433. )
  434. );
  435. } elseif ($value === false) {
  436. $args['credentials'] = CredentialProvider::fromCredentials(
  437. new Credentials('', '')
  438. );
  439. $args['config']['signature_version'] = 'anonymous';
  440. } elseif ($value instanceof CacheInterface) {
  441. $args['credentials'] = CredentialProvider::defaultProvider($args);
  442. } else {
  443. throw new IAE('Credentials must be an instance of '
  444. . 'Aws\Credentials\CredentialsInterface, an associative '
  445. . 'array that contains "key", "secret", and an optional "token" '
  446. . 'key-value pairs, a credentials provider function, or false.');
  447. }
  448. }
  449. public static function _default_credential_provider(array $args)
  450. {
  451. return CredentialProvider::defaultProvider($args);
  452. }
  453. public static function _apply_csm($value, array &$args, HandlerList $list)
  454. {
  455. if ($value === false) {
  456. $value = new Configuration(
  457. false,
  458. \Aws\ClientSideMonitoring\ConfigurationProvider::DEFAULT_HOST,
  459. \Aws\ClientSideMonitoring\ConfigurationProvider::DEFAULT_PORT,
  460. \Aws\ClientSideMonitoring\ConfigurationProvider::DEFAULT_CLIENT_ID
  461. );
  462. $args['csm'] = $value;
  463. }
  464. $list->appendBuild(
  465. ApiCallMonitoringMiddleware::wrap(
  466. $args['credentials'],
  467. $value,
  468. $args['region'],
  469. $args['api']->getServiceId()
  470. ),
  471. 'ApiCallMonitoringMiddleware'
  472. );
  473. $list->appendAttempt(
  474. ApiCallAttemptMonitoringMiddleware::wrap(
  475. $args['credentials'],
  476. $value,
  477. $args['region'],
  478. $args['api']->getServiceId()
  479. ),
  480. 'ApiCallAttemptMonitoringMiddleware'
  481. );
  482. }
  483. public static function _apply_api_provider(callable $value, array &$args)
  484. {
  485. $api = new Service(
  486. ApiProvider::resolve(
  487. $value,
  488. 'api',
  489. $args['service'],
  490. $args['version']
  491. ),
  492. $value
  493. );
  494. if (
  495. empty($args['config']['signing_name'])
  496. && isset($api['metadata']['signingName'])
  497. ) {
  498. $args['config']['signing_name'] = $api['metadata']['signingName'];
  499. }
  500. $args['api'] = $api;
  501. $args['parser'] = Service::createParser($api);
  502. $args['error_parser'] = Service::createErrorParser($api->getProtocol(), $api);
  503. }
  504. public static function _apply_endpoint_provider(callable $value, array &$args)
  505. {
  506. if (!isset($args['endpoint'])) {
  507. $endpointPrefix = isset($args['api']['metadata']['endpointPrefix'])
  508. ? $args['api']['metadata']['endpointPrefix']
  509. : $args['service'];
  510. // Check region is a valid host label when it is being used to
  511. // generate an endpoint
  512. if (!self::isValidRegion($args['region'])) {
  513. throw new InvalidRegionException('Region must be a valid RFC'
  514. . ' host label.');
  515. }
  516. // Invoke the endpoint provider and throw if it does not resolve.
  517. $result = EndpointProvider::resolve($value, [
  518. 'service' => $endpointPrefix,
  519. 'region' => $args['region'],
  520. 'scheme' => $args['scheme'],
  521. 'options' => self::getEndpointProviderOptions($args),
  522. ]);
  523. $args['endpoint'] = $result['endpoint'];
  524. if (
  525. empty($args['config']['signature_version'])
  526. && isset($result['signatureVersion'])
  527. ) {
  528. $args['config']['signature_version']
  529. = $result['signatureVersion'];
  530. }
  531. if (
  532. empty($args['config']['signing_region'])
  533. && isset($result['signingRegion'])
  534. ) {
  535. $args['config']['signing_region'] = $result['signingRegion'];
  536. }
  537. if (
  538. empty($args['config']['signing_name'])
  539. && isset($result['signingName'])
  540. ) {
  541. $args['config']['signing_name'] = $result['signingName'];
  542. }
  543. }
  544. }
  545. public static function _apply_endpoint_discovery($value, array &$args) {
  546. $args['endpoint_discovery'] = $value;
  547. }
  548. public static function _default_endpoint_discovery_provider(array $args)
  549. {
  550. return ConfigurationProvider::defaultProvider($args);
  551. }
  552. public static function _apply_serializer($value, array &$args, HandlerList $list)
  553. {
  554. $list->prependBuild(Middleware::requestBuilder($value), 'builder');
  555. }
  556. public static function _apply_debug($value, array &$args, HandlerList $list)
  557. {
  558. if ($value !== false) {
  559. $list->interpose(
  560. new TraceMiddleware(
  561. $value === true ? [] : $value,
  562. $args['api'])
  563. );
  564. }
  565. }
  566. public static function _apply_stats($value, array &$args, HandlerList $list)
  567. {
  568. // Create an array of stat collectors that are disabled (set to false)
  569. // by default. If the user has passed in true, enable all stat
  570. // collectors.
  571. $defaults = array_fill_keys(
  572. ['http', 'retries', 'timer'],
  573. $value === true
  574. );
  575. $args['stats'] = is_array($value)
  576. ? array_replace($defaults, $value)
  577. : $defaults;
  578. if ($args['stats']['timer']) {
  579. $list->prependInit(Middleware::timer(), 'timer');
  580. }
  581. }
  582. public static function _apply_profile($_, array &$args)
  583. {
  584. $args['credentials'] = CredentialProvider::ini($args['profile']);
  585. }
  586. public static function _apply_validate($value, array &$args, HandlerList $list)
  587. {
  588. if ($value === false) {
  589. return;
  590. }
  591. $validator = $value === true
  592. ? new Validator()
  593. : new Validator($value);
  594. $list->appendValidate(
  595. Middleware::validation($args['api'], $validator),
  596. 'validation'
  597. );
  598. }
  599. public static function _apply_handler($value, array &$args, HandlerList $list)
  600. {
  601. $list->setHandler($value);
  602. }
  603. public static function _default_handler(array &$args)
  604. {
  605. return new WrappedHttpHandler(
  606. default_http_handler(),
  607. $args['parser'],
  608. $args['error_parser'],
  609. $args['exception_class'],
  610. $args['stats']['http']
  611. );
  612. }
  613. public static function _apply_http_handler($value, array &$args, HandlerList $list)
  614. {
  615. $args['handler'] = new WrappedHttpHandler(
  616. $value,
  617. $args['parser'],
  618. $args['error_parser'],
  619. $args['exception_class'],
  620. $args['stats']['http']
  621. );
  622. }
  623. public static function _apply_user_agent($value, array &$args, HandlerList $list)
  624. {
  625. if (!is_array($value)) {
  626. $value = [$value];
  627. }
  628. $value = array_map('strval', $value);
  629. if (defined('HHVM_VERSION')) {
  630. array_unshift($value, 'HHVM/' . HHVM_VERSION);
  631. }
  632. array_unshift($value, 'aws-sdk-php/' . Sdk::VERSION);
  633. $args['ua_append'] = $value;
  634. $list->appendBuild(static function (callable $handler) use ($value) {
  635. return function (
  636. CommandInterface $command,
  637. RequestInterface $request
  638. ) use ($handler, $value) {
  639. return $handler($command, $request->withHeader(
  640. 'User-Agent',
  641. implode(' ', array_merge(
  642. $value,
  643. $request->getHeader('User-Agent')
  644. ))
  645. ));
  646. };
  647. });
  648. }
  649. public static function _apply_endpoint($value, array &$args, HandlerList $list)
  650. {
  651. $parts = parse_url($value);
  652. if (empty($parts['scheme']) || empty($parts['host'])) {
  653. throw new IAE(
  654. 'Endpoints must be full URIs and include a scheme and host'
  655. );
  656. }
  657. $args['endpoint'] = $value;
  658. }
  659. public static function _apply_idempotency_auto_fill(
  660. $value,
  661. array &$args,
  662. HandlerList $list
  663. ) {
  664. $enabled = false;
  665. $generator = null;
  666. if (is_bool($value)) {
  667. $enabled = $value;
  668. } elseif (is_callable($value)) {
  669. $enabled = true;
  670. $generator = $value;
  671. }
  672. if ($enabled) {
  673. $list->prependInit(
  674. IdempotencyTokenMiddleware::wrap($args['api'], $generator),
  675. 'idempotency_auto_fill'
  676. );
  677. }
  678. }
  679. public static function _default_endpoint_provider(array $args)
  680. {
  681. $options = self::getEndpointProviderOptions($args);
  682. return PartitionEndpointProvider::defaultProvider($options)
  683. ->getPartition($args['region'], $args['service']);
  684. }
  685. public static function _default_serializer(array $args)
  686. {
  687. return Service::createSerializer(
  688. $args['api'],
  689. $args['endpoint']
  690. );
  691. }
  692. public static function _default_signature_provider()
  693. {
  694. return SignatureProvider::defaultProvider();
  695. }
  696. public static function _default_signature_version(array &$args)
  697. {
  698. if (isset($args['config']['signature_version'])) {
  699. return $args['config']['signature_version'];
  700. }
  701. $args['__partition_result'] = isset($args['__partition_result'])
  702. ? isset($args['__partition_result'])
  703. : call_user_func(PartitionEndpointProvider::defaultProvider(), [
  704. 'service' => $args['service'],
  705. 'region' => $args['region'],
  706. ]);
  707. return isset($args['__partition_result']['signatureVersion'])
  708. ? $args['__partition_result']['signatureVersion']
  709. : $args['api']->getSignatureVersion();
  710. }
  711. public static function _default_signing_name(array &$args)
  712. {
  713. if (isset($args['config']['signing_name'])) {
  714. return $args['config']['signing_name'];
  715. }
  716. $args['__partition_result'] = isset($args['__partition_result'])
  717. ? isset($args['__partition_result'])
  718. : call_user_func(PartitionEndpointProvider::defaultProvider(), [
  719. 'service' => $args['service'],
  720. 'region' => $args['region'],
  721. ]);
  722. if (isset($args['__partition_result']['signingName'])) {
  723. return $args['__partition_result']['signingName'];
  724. }
  725. if ($signingName = $args['api']->getSigningName()) {
  726. return $signingName;
  727. }
  728. return $args['service'];
  729. }
  730. public static function _default_signing_region(array &$args)
  731. {
  732. if (isset($args['config']['signing_region'])) {
  733. return $args['config']['signing_region'];
  734. }
  735. $args['__partition_result'] = isset($args['__partition_result'])
  736. ? isset($args['__partition_result'])
  737. : call_user_func(PartitionEndpointProvider::defaultProvider(), [
  738. 'service' => $args['service'],
  739. 'region' => $args['region'],
  740. ]);
  741. return isset($args['__partition_result']['signingRegion'])
  742. ? $args['__partition_result']['signingRegion']
  743. : $args['region'];
  744. }
  745. public static function _missing_version(array $args)
  746. {
  747. $service = isset($args['service']) ? $args['service'] : '';
  748. $versions = ApiProvider::defaultProvider()->getVersions($service);
  749. $versions = implode("\n", array_map(function ($v) {
  750. return "* \"$v\"";
  751. }, $versions)) ?: '* (none found)';
  752. return <<<EOT
  753. A "version" configuration value is required. Specifying a version constraint
  754. ensures that your code will not be affected by a breaking change made to the
  755. service. For example, when using Amazon S3, you can lock your API version to
  756. "2006-03-01".
  757. Your build of the SDK has the following version(s) of "{$service}": {$versions}
  758. You may provide "latest" to the "version" configuration value to utilize the
  759. most recent available API version that your client's API provider can find.
  760. Note: Using 'latest' in a production application is not recommended.
  761. A list of available API versions can be found on each client's API documentation
  762. page: http://docs.aws.amazon.com/aws-sdk-php/v3/api/index.html. If you are
  763. unable to load a specific API version, then you may need to update your copy of
  764. the SDK.
  765. EOT;
  766. }
  767. public static function _missing_region(array $args)
  768. {
  769. $service = isset($args['service']) ? $args['service'] : '';
  770. return <<<EOT
  771. A "region" configuration value is required for the "{$service}" service
  772. (e.g., "us-west-2"). A list of available public regions and endpoints can be
  773. found at http://docs.aws.amazon.com/general/latest/gr/rande.html.
  774. EOT;
  775. }
  776. /**
  777. * Extracts client options for the endpoint provider to its own array
  778. *
  779. * @param array $args
  780. * @return array
  781. */
  782. private static function getEndpointProviderOptions(array $args)
  783. {
  784. $options = [];
  785. $optionKeys = ['sts_regional_endpoints', 's3_us_east_1_regional_endpoint'];
  786. foreach ($optionKeys as $key) {
  787. if (isset($args[$key])) {
  788. $options[$key] = $args[$key];
  789. }
  790. }
  791. return $options;
  792. }
  793. /**
  794. * Validates a region to be used for endpoint construction
  795. *
  796. * @param $region
  797. * @return bool
  798. */
  799. private static function isValidRegion($region)
  800. {
  801. return is_valid_hostlabel($region);
  802. }
  803. }