Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/aws-cpp-sdk-core/include/aws/core/client/ClientConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,18 @@ namespace Aws
* First available auth scheme will be used for each operation.
*/
Aws::Vector<Aws::String> authPreferences;

/**
* Buffer size in bytes that will be used to content encode
* bodies using aws-chunked. Changing this is useful when you
* want to minimize memory use while uploading to S3. Size MUST
* be greater than 8KB otherwise S3 will reject the request.
*
* Defaults to 64KiB.
*
* https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html
*/
size_t awsChunkedBufferSize = 64UL * 1024;
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ namespace client
m_interceptors({
Aws::MakeShared<ChecksumInterceptor>("AwsSmithyClientBase", *m_clientConfig),
Aws::MakeShared<features::ChunkingInterceptor>("AwsSmithyClientBase",
m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : m_clientConfig->httpClientChunkedMode)
m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : m_clientConfig->httpClientChunkedMode,
m_clientConfig->awsChunkedBufferSize)
})
{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,18 @@ class AwsChunkedIOStream : public Aws::IOStream {
class ChunkingInterceptor : public smithy::interceptor::Interceptor {
public:
explicit ChunkingInterceptor(Aws::Client::HttpClientChunkedMode httpClientChunkedMode)
: m_httpClientChunkedMode(httpClientChunkedMode) {}
: m_httpClientChunkedMode(httpClientChunkedMode), m_bufferSize(AWS_DATA_BUFFER_SIZE) {}
explicit ChunkingInterceptor(Aws::Client::HttpClientChunkedMode httpClientChunkedMode, size_t bufferSize)
: m_httpClientChunkedMode(httpClientChunkedMode), m_bufferSize(bufferSize) {}
~ChunkingInterceptor() override = default;

ModifyRequestOutcome ModifyBeforeSigning(smithy::interceptor::InterceptorContext& context) override {
if (m_bufferSize < 8 * 1024) {
return Aws::Client::AWSError<Aws::Client::CoreErrors>{Aws::Client::CoreErrors::VALIDATION,
"ValidationErrorException",
"aws-chunked buffer must be over 8KiB to content encode",
false};
}
auto request = context.GetTransmitRequest();

if (!ShouldApplyChunking(request, context)) {
Expand Down Expand Up @@ -188,6 +196,12 @@ class ChunkingInterceptor : public smithy::interceptor::Interceptor {
}

ModifyRequestOutcome ModifyBeforeTransmit(smithy::interceptor::InterceptorContext& context) override {
if (m_bufferSize < 8 * 1024) {
return Aws::Client::AWSError<Aws::Client::CoreErrors>{Aws::Client::CoreErrors::VALIDATION,
"ValidationErrorException",
"aws-chunked buffer must be over 8KiB to content encode",
false};
}
auto request = context.GetTransmitRequest();

if (!ShouldApplyChunking(request, context)) {
Expand All @@ -199,8 +213,7 @@ class ChunkingInterceptor : public smithy::interceptor::Interceptor {
return request;
}

auto chunkedBody = Aws::MakeShared<AwsChunkedIOStream>(
ALLOCATION_TAG, request.get(), originalBody);
auto chunkedBody = Aws::MakeShared<AwsChunkedIOStream>(ALLOCATION_TAG, request.get(), originalBody, m_bufferSize);

request->AddContentBody(chunkedBody);
return request;
Expand Down Expand Up @@ -233,6 +246,7 @@ class ChunkingInterceptor : public smithy::interceptor::Interceptor {
}

Aws::Client::HttpClientChunkedMode m_httpClientChunkedMode;
size_t m_bufferSize;
};

} // namespace features
Expand Down
8 changes: 6 additions & 2 deletions src/aws-cpp-sdk-core/source/client/AWSClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,9 @@ AWSClient::AWSClient(const Aws::Client::ClientConfiguration& configuration,
m_requestCompressionConfig(configuration.requestCompressionConfig),
m_userAgentInterceptor{Aws::MakeShared<smithy::client::UserAgentInterceptor>(AWS_CLIENT_LOG_TAG, configuration, m_retryStrategy->GetStrategyName(), m_serviceName)},
m_interceptors{Aws::MakeShared<smithy::client::ChecksumInterceptor>(AWS_CLIENT_LOG_TAG), Aws::MakeShared<smithy::client::features::ChunkingInterceptor>(AWS_CLIENT_LOG_TAG,
m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : configuration.httpClientChunkedMode), m_userAgentInterceptor}
m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : configuration.httpClientChunkedMode,
configuration.awsChunkedBufferSize),
m_userAgentInterceptor}
{
}

Expand All @@ -168,7 +170,9 @@ AWSClient::AWSClient(const Aws::Client::ClientConfiguration& configuration,
m_requestCompressionConfig(configuration.requestCompressionConfig),
m_userAgentInterceptor{Aws::MakeShared<smithy::client::UserAgentInterceptor>(AWS_CLIENT_LOG_TAG, configuration, m_retryStrategy->GetStrategyName(), m_serviceName)},
m_interceptors{Aws::MakeShared<smithy::client::ChecksumInterceptor>(AWS_CLIENT_LOG_TAG, configuration), Aws::MakeShared<smithy::client::features::ChunkingInterceptor>(AWS_CLIENT_LOG_TAG,
m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : configuration.httpClientChunkedMode), m_userAgentInterceptor}
m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : configuration.httpClientChunkedMode,
configuration.awsChunkedBufferSize),
m_userAgentInterceptor}
{
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ void AwsSmithyClientBase::baseCopyAssign(const AwsSmithyClientBase& other,
m_interceptors = Aws::Vector<std::shared_ptr<interceptor::Interceptor>>{
Aws::MakeShared<ChecksumInterceptor>("AwsSmithyClientBase", *m_clientConfig),
Aws::MakeShared<features::ChunkingInterceptor>("AwsSmithyClientBase",
m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : m_clientConfig->httpClientChunkedMode)
m_httpClient->IsDefaultAwsHttpClient() ? Aws::Client::HttpClientChunkedMode::DEFAULT : m_clientConfig->httpClientChunkedMode,
m_clientConfig->awsChunkedBufferSize)
};

baseCopyInit();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,19 @@ TEST_F(ChunkingInterceptorTest, ShouldNotApplyChunkingForCustomHttpClient) {
EXPECT_EQ(request, result.GetResult());
EXPECT_FALSE(request->HasHeader(Aws::Http::AWS_TRAILER_HEADER));
EXPECT_FALSE(request->HasHeader(Aws::Http::TRANSFER_ENCODING_HEADER));
}
}

TEST_F(ChunkingInterceptorTest, ShouldFailWhenBufferIsTooShort) {
const Aws::Client::ClientConfiguration config;
ChunkingInterceptor interceptor(config.httpClientChunkedMode, 7UL * 1024);

// Create interceptor context with a mock request
const MockRequest mockRequest;
smithy::interceptor::InterceptorContext context(mockRequest);
auto respnse = interceptor.ModifyBeforeSigning(context);
EXPECT_FALSE(respnse.IsSuccess());
EXPECT_STREQ("aws-chunked buffer must be over 8KiB to content encode", respnse.GetError().GetMessage().c_str());
respnse = interceptor.ModifyBeforeTransmit(context);
EXPECT_FALSE(respnse.IsSuccess());
EXPECT_STREQ("aws-chunked buffer must be over 8KiB to content encode", respnse.GetError().GetMessage().c_str());
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ namespace
static std::string BASE_CONTENT_ENCODING_BUCKET_NAME = "contentencoding";
static std::string BASE_CROSS_REGION_BUCKET_NAME = "crossregion";
static std::string BASE_ENDPOINT_OVERRIDE_BUCKET_NAME = "endpointoverride";
static std::string BASE_STREAM_SIZE_BUCKET_NAME = "streamsize";
static const char* ALLOCATION_TAG = "BucketAndObjectOperationTest";
static const char* TEST_OBJ_KEY = "TestObjectKey";
static const char* TEST_NEWLINE_KEY = "TestNewlineKey";
Expand Down Expand Up @@ -143,6 +144,7 @@ namespace
std::ref(BASE_CONTENT_ENCODING_BUCKET_NAME),
std::ref(BASE_CROSS_REGION_BUCKET_NAME),
std::ref(BASE_ENDPOINT_OVERRIDE_BUCKET_NAME),
std::ref(BASE_STREAM_SIZE_BUCKET_NAME)
};

for (auto& testBucketName : TEST_BUCKETS) {
Expand Down Expand Up @@ -2715,4 +2717,29 @@ namespace
.WithKey(objectKey));
AWS_EXPECT_SUCCESS(getObjectResponse);
}

TEST_F(BucketAndObjectOperationTest, ShouldSuccessfullyUploadObjectForSmallerBufferSize) {
const String fullBucketName = CalculateBucketName(BASE_STREAM_SIZE_BUCKET_NAME.c_str());
SCOPED_TRACE(Aws::String("FullBucketName ") + fullBucketName);
CreateBucketRequest createBucketRequest;
createBucketRequest.SetBucket(fullBucketName);
createBucketRequest.SetACL(BucketCannedACL::private_);
CreateBucketOutcome createBucketOutcome = CreateBucket(createBucketRequest);
AWS_ASSERT_SUCCESS(createBucketOutcome);

S3ClientConfiguration configuration{};
configuration.region = Aws::Region::US_EAST_1;
// Set aws-chunked buffer to 12KB
configuration.awsChunkedBufferSize = 1024 * 12;
const S3Client shortStreamClient{configuration};

auto request = PutObjectRequest().WithBucket(fullBucketName).WithKey("the-jack-bros");

// Create a 24KB body
const Aws::String body(24L * 1024, 'a');
request.SetBody(Aws::MakeShared<StringStream>(ALLOCATION_TAG, body));

const auto response = shortStreamClient.PutObject(request);
AWS_EXPECT_SUCCESS(response);
}
}
Loading