Skip to content

Commit

Permalink
feature: add request body compression support (#2693)
Browse files Browse the repository at this point in the history
  • Loading branch information
stobrien89 committed Jun 28, 2023
1 parent 9290708 commit 1ec5313
Show file tree
Hide file tree
Showing 11 changed files with 1,117 additions and 11 deletions.
7 changes: 7 additions & 0 deletions .changes/nextrelease/compression-trait.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[
{
"type": "feature",
"category": "",
"description": "Adds support for automatically compressing request bodies when a service supports it."
}
]
12 changes: 12 additions & 0 deletions src/AwsClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ public function __construct(array $args)
$this->addInvocationId();
$this->addEndpointParameterMiddleware($args);
$this->addEndpointDiscoveryMiddleware($config, $args);
$this->addRequestCompressionMiddleware($config);
$this->loadAliases();
$this->addStreamRequestPayload();
$this->addRecursionDetection();
Expand Down Expand Up @@ -449,6 +450,17 @@ private function addSignatureMiddleware()
);
}

private function addRequestCompressionMiddleware($config)
{
if (empty($config['disable_request_compression'])) {
$list = $this->getHandlerList();
$list->appendBuild(
RequestCompressionMiddleware::wrap($config),
'request-compression'
);
}
}

private function addInvocationId()
{
// Add invocation id to each request
Expand Down
78 changes: 69 additions & 9 deletions src/ClientResolver.php
Original file line number Diff line number Diff line change
@@ -1,37 +1,38 @@
<?php
namespace Aws;

use Aws\Api\Validator;
use Aws\Api\ApiProvider;
use Aws\Api\Service;
use Aws\Api\Validator;
use Aws\ClientSideMonitoring\ApiCallAttemptMonitoringMiddleware;
use Aws\ClientSideMonitoring\ApiCallMonitoringMiddleware;
use Aws\ClientSideMonitoring\Configuration;
use Aws\Configuration\ConfigurationResolver;
use Aws\Credentials\CredentialProvider;
use Aws\Credentials\Credentials;
use Aws\Credentials\CredentialsInterface;
use Aws\DefaultsMode\ConfigurationInterface as ConfigModeInterface;
use Aws\DefaultsMode\ConfigurationProvider as ConfigModeProvider;
use Aws\Endpoint\EndpointProvider;
use Aws\Endpoint\PartitionEndpointProvider;
use Aws\Endpoint\UseFipsEndpoint\Configuration as UseFipsEndpointConfiguration;
use Aws\Endpoint\UseFipsEndpoint\ConfigurationProvider as UseFipsConfigProvider;
use Aws\Endpoint\UseFipsEndpoint\ConfigurationInterface as UseFipsEndpointConfigurationInterface;
use Aws\Endpoint\UseDualstackEndpoint\Configuration as UseDualStackEndpointConfiguration;
use Aws\Endpoint\UseDualstackEndpoint\ConfigurationProvider as UseDualStackConfigProvider;
use Aws\Endpoint\UseDualstackEndpoint\ConfigurationInterface as UseDualStackEndpointConfigurationInterface;
use Aws\Endpoint\UseDualstackEndpoint\ConfigurationProvider as UseDualStackConfigProvider;
use Aws\Endpoint\UseFipsEndpoint\Configuration as UseFipsEndpointConfiguration;
use Aws\Endpoint\UseFipsEndpoint\ConfigurationInterface as UseFipsEndpointConfigurationInterface;
use Aws\Endpoint\UseFipsEndpoint\ConfigurationProvider as UseFipsConfigProvider;
use Aws\EndpointDiscovery\ConfigurationInterface;
use Aws\EndpointDiscovery\ConfigurationProvider;
use Aws\EndpointV2\EndpointDefinitionProvider;
use Aws\Exception\AwsException;
use Aws\Exception\InvalidRegionException;
use Aws\Retry\ConfigurationInterface as RetryConfigInterface;
use Aws\Retry\ConfigurationProvider as RetryConfigProvider;
use Aws\DefaultsMode\ConfigurationInterface as ConfigModeInterface;
use Aws\DefaultsMode\ConfigurationProvider as ConfigModeProvider;
use Aws\Signature\SignatureProvider;
use Aws\Endpoint\EndpointProvider;
use Aws\Token\Token;
use Aws\Token\TokenInterface;
use Aws\Token\TokenProvider;
use GuzzleHttp\Promise\PromiseInterface;
use Aws\Credentials\CredentialProvider;
use InvalidArgumentException as IAE;
use Psr\Http\Message\RequestInterface;

Expand Down Expand Up @@ -219,6 +220,20 @@ class ClientResolver
'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).',
'fn' => [__CLASS__, '_apply_debug'],
],
'disable_request_compression' => [
'type' => 'value',
'valid' => ['bool', 'callable'],
'doc' => 'Set to true to disable request compression for supported operations',
'fn' => [__CLASS__, '_apply_disable_request_compression'],
'default' => [__CLASS__, '_default_disable_request_compression'],
],
'request_min_compression_size_bytes' => [
'type' => 'value',
'valid' => ['int', 'callable'],
'doc' => 'Set to a value between between 0 and 10485760 bytes, inclusive. This value will be ignored if `disable_request_compression` is set to `true`',
'fn' => [__CLASS__, '_apply_min_compression_size'],
'default' => [__CLASS__, '_default_min_compression_size'],
],
'csm' => [
'type' => 'value',
'valid' => [\Aws\ClientSideMonitoring\ConfigurationInterface::class, 'callable', 'array', 'bool'],
Expand Down Expand Up @@ -520,6 +535,51 @@ public static function _apply_defaults($value, array &$args, HandlerList $list)
}
}

public static function _apply_disable_request_compression($value, array &$args) {
if (is_callable($value)) {
$value = $value();
}
if (!is_bool($value)) {
throw new IAE(
"Invalid configuration value provided for 'disable_request_compression'."
. " value must be a bool."
);
}
$args['config']['disable_request_compression'] = $value;
}

public static function _default_disable_request_compression(array &$args) {
return ConfigurationResolver::resolve(
'disable_request_compression',
false,
'bool',
$args
);
}

public static function _apply_min_compression_size($value, array &$args) {
if (is_callable($value)) {
$value = $value();
}
if (!is_int($value)
|| (is_int($value)
&& ($value < 0 || $value > 10485760))
) {
throw new IAE(" Invalid configuration value provided for 'min_compression_size_bytes'."
. " value must be an integer between 0 and 10485760, inclusive.");
}
$args['config']['request_min_compression_size_bytes'] = $value;
}

public static function _default_min_compression_size(array &$args) {
return ConfigurationResolver::resolve(
'request_min_compression_size_bytes',
10240,
'int',
$args
);
}

public static function _apply_credentials($value, array &$args)
{
if (is_callable($value)) {
Expand Down
180 changes: 180 additions & 0 deletions src/Configuration/ConfigurationResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?php

namespace Aws\Configuration;

class ConfigurationResolver
{
const ENV_PROFILE = 'AWS_PROFILE';
const ENV_CONFIG_FILE = 'AWS_CONFIG_FILE';

public static $envPrefix = 'AWS_';

/**
* Generic configuration resolver that first checks for environment
* variables, then checks for a specified profile in the environment-defined
* config file location (env variable is 'AWS_CONFIG_FILE', file location
* defaults to ~/.aws/config), then checks for the "default" profile in the
* environment-defined config file location, and failing those uses a default
* fallback value.
*
* @param string $key Configuration key to be used when attempting
* to retrieve value from the environment or ini file.
* @param mixed $defaultValue
* @param string $expectedType The expected type of the retrieved value.
* @param array $config
* @param array $additionalArgs
*
* @return mixed
*/
public static function resolve(
$key,
$defaultValue,
$expectedType,
$config = []
)
{
$envValue = self::env($key, $expectedType);
if (!is_null($envValue)) {
return $envValue;
}

if (!isset($config['use_aws_shared_config_files'])
|| $config['use_aws_shared_config_files'] != false
) {
$iniValue = self::ini($key, $expectedType);
if(!is_null($iniValue)) {
return $iniValue;
}
}

return $defaultValue;
}

/**
* Resolves config values from environment variables.
*
* @param string $key Configuration key to be used when attempting
* to retrieve value from the environment.
* @param string $expectedType The expected type of the retrieved value.
*
* @return null | mixed
*/
public static function env($key, $expectedType)
{
// Use config from environment variables, if available
$envValue = getenv(self::$envPrefix . strtoupper($key));
if (!empty($envValue)) {
if ($expectedType) {
$envValue = self::convertType($envValue, $expectedType);
}
return $envValue;
}

return null;
}

/**
* Gets config values from a config file whose location
* is specified by an environment variable 'AWS_CONFIG_FILE', defaulting to
* ~/.aws/config if not specified
*
*
* @param string $key Configuration key to be used when attempting
* to retrieve value from ini file.
* @param string $expectedType The expected type of the retrieved value.
* @param string|null $profile Profile to use. If not specified will use
* the "default" profile.
* @param string|null $filename If provided, uses a custom filename rather
* than looking in the default directory.
*
* @return null | mixed
*/
public static function ini($key, $expectedType, $profile = null, $filename = null)
{
$filename = $filename ?: (self::getDefaultConfigFilename());
$profile = $profile ?: (getenv(self::ENV_PROFILE) ?: 'default');

if (!@is_readable($filename)) {
return null;
}
// Use INI_SCANNER_NORMAL instead of INI_SCANNER_TYPED for PHP 5.5 compatibility
//TODO change after deprecation
$data = @\Aws\parse_ini_file($filename, true, INI_SCANNER_NORMAL);
if ($data === false
|| !isset($data[$profile])
|| !isset($data[$profile][$key])
) {
return null;
}

// INI_SCANNER_NORMAL parses false-y values as an empty string
if ($data[$profile][$key] === "") {
if ($expectedType === 'bool') {
$data[$profile][$key] = false;
} elseif ($expectedType === 'int') {
$data[$profile][$key] = 0;
}
}

return self::convertType($data[$profile][$key], $expectedType);
}

/**
* Gets the environment's HOME directory if available.
*
* @return null | string
*/
private static function getHomeDir()
{
// On Linux/Unix-like systems, use the HOME environment variable
if ($homeDir = getenv('HOME')) {
return $homeDir;
}

// Get the HOMEDRIVE and HOMEPATH values for Windows hosts
$homeDrive = getenv('HOMEDRIVE');
$homePath = getenv('HOMEPATH');

return ($homeDrive && $homePath) ? $homeDrive . $homePath : null;
}

/**
* Gets default config file location from environment, falling back to aws
* default location
*
* @return string
*/
private static function getDefaultConfigFilename()
{
if ($filename = getenv(self::ENV_CONFIG_FILE)) {
return $filename;
}
return self::getHomeDir() . '/.aws/config';
}

/**
* Normalizes string values pulled out of ini files and
* environment variables.
*
* @param string $value The value retrieved from the environment or
* ini file.
* @param $type $string The type that the value needs to be converted to.
*
* @return mixed
*/
private static function convertType($value, $type)
{
if ($type === 'bool'
&& !is_null($convertedValue = \Aws\boolean_value($value))
) {
return $convertedValue;
}

if ($type === 'int'
&& filter_var($value, FILTER_VALIDATE_INT)
) {
$value = intVal($value);
}
return $value;
}
}
1 change: 0 additions & 1 deletion src/Endpoint/UseFipsEndpoint/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
namespace Aws\Endpoint\UseFipsEndpoint;

use Aws;
use Aws\ClientResolver;
use Aws\Endpoint\UseFipsEndpoint\Exception\ConfigurationException;

class Configuration implements ConfigurationInterface
Expand Down
Loading

0 comments on commit 1ec5313

Please sign in to comment.