Lesson 35 - Get Compute Auth Token Working

This commit is contained in:
Norman Lansing
2026-02-28 12:32:28 -05:00
parent 1d477ee42a
commit 4fde462bce
7743 changed files with 1397833 additions and 18 deletions

View File

@@ -0,0 +1,142 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <smithy/client/AwsSmithyClientBase.h>
#include <smithy/client/AwsSmithyClientAsyncRequestContext.h>
#include <smithy/client/common/AwsSmithyRequestSigning.h>
#include <smithy/identity/identity/AwsIdentity.h>
#include <smithy/identity/auth/AuthSchemeOption.h>
#include <smithy/identity/auth/AuthSchemeResolverBase.h>
#include <smithy/tracing/TelemetryProvider.h>
#include <aws/crt/Variant.h>
#include <aws/core/client/ClientConfiguration.h>
#include <aws/core/http/HttpResponse.h>
#include <aws/core/utils/memory/stl/AWSMap.h>
#include <aws/core/utils/FutureOutcome.h>
#include <aws/core/utils/Outcome.h>
namespace smithy {
namespace client
{
template<const char* ServiceNameT,
typename ServiceClientConfigurationT,
typename ServiceAuthSchemeResolverT,
typename AuthSchemesVariantT,
typename EndpointProviderT,
typename SerializerT,
typename ResponseT>
class AwsSmithyClientT : public AwsSmithyClientBase
{
public:
explicit AwsSmithyClientT(const ServiceClientConfigurationT& clientConfig, const Aws::String& serviceName,
const std::shared_ptr<Aws::Http::HttpClient>& httpClient,
const std::shared_ptr<Aws::Client::AWSErrorMarshaller>& errorMarshaller,
const std::shared_ptr<EndpointProviderT> endpointProvider,
const std::shared_ptr<ServiceAuthSchemeResolverT>& authSchemeResolver,
const Aws::UnorderedMap<Aws::String, AuthSchemesVariantT>& authSchemes)
: AwsSmithyClientBase(Aws::MakeUnique<ServiceClientConfigurationT>(ServiceNameT, clientConfig), serviceName, httpClient, errorMarshaller),
m_clientConfiguration(*static_cast<ServiceClientConfigurationT*>(AwsSmithyClientBase::m_clientConfig.get())),
m_endpointProvider(endpointProvider),
m_authSchemeResolver(authSchemeResolver),
m_authSchemes(authSchemes),
m_serializer(Aws::MakeUnique<SerializerT>(ServiceNameT, m_clientConfiguration.telemetryProvider))
{
m_serviceName = ServiceNameT;
}
virtual ~AwsSmithyClientT() = default;
protected:
inline const char* GetServiceClientName() const override { return m_serviceName.c_str(); }
ResolveEndpointOutcome ResolveEndpoint(const Aws::Endpoint::EndpointParameters& endpointParameters, EndpointUpdateCallback&& epCallback) const override
{
assert(m_endpointProvider);
ResolveEndpointOutcome resolveEndpointOutcome = m_endpointProvider->ResolveEndpoint(endpointParameters);
if (resolveEndpointOutcome.IsSuccess())
{
epCallback(resolveEndpointOutcome.GetResult());
}
return resolveEndpointOutcome;
}
SelectAuthSchemeOptionOutcome SelectAuthSchemeOption(const AwsSmithyClientAsyncRequestContext& ctx) const override
{
assert(m_authSchemeResolver);
typename ServiceAuthSchemeResolverT::ServiceAuthSchemeParameters identityParams;
identityParams.serviceName = m_serviceName;
identityParams.operation = ctx.m_requestName;
identityParams.region = m_clientConfiguration.region;
if (ctx.m_pRequest) {
// refactor once auth scheme resolver will use it's own rule set
const auto& epParams = ctx.m_pRequest->GetEndpointContextParams();
for (const auto& epParam : epParams) {
using ParameterType = Aws::Endpoint::EndpointParameter::ParameterType;
if(epParam.GetStoredType() == ParameterType::STRING)
identityParams.additionalProperties.insert({epParam.GetName(), epParam.GetStrValueNoCheck()});
else if (epParam.GetStoredType() == ParameterType::BOOLEAN)
identityParams.additionalProperties.insert({epParam.GetName(), epParam.GetBoolValueNoCheck()});
else
assert(!"Unknown endpoint parameter!");
}
const auto& serviceParams = ctx.m_pRequest->GetServiceSpecificParameters();
if (serviceParams) {
for (const auto& serviceParam : serviceParams->parameterMap) {
identityParams.additionalProperties.insert({serviceParam.first, serviceParam.second});
}
}
}
Aws::Vector<AuthSchemeOption> authSchemeOptions = m_authSchemeResolver->resolveAuthScheme(identityParams);
auto authSchemeOptionIt = std::find_if(authSchemeOptions.begin(), authSchemeOptions.end(),
[this](const AuthSchemeOption& opt)
{
return m_authSchemes.find(opt.schemeId) != m_authSchemes.end();
});
assert(authSchemeOptionIt != authSchemeOptions.end());
if (authSchemeOptionIt != authSchemeOptions.end()) {
return SelectAuthSchemeOptionOutcome(*authSchemeOptionIt);
}
return AWSError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE,
"",
"Failed to select an auth scheme",
false/*retryable*/);
}
SigningOutcome SignRequest(std::shared_ptr<HttpRequest> httpRequest, const AuthSchemeOption& targetAuthSchemeOption) const override
{
return AwsClientRequestSigning<AuthSchemesVariantT>::SignRequest(httpRequest, targetAuthSchemeOption, m_authSchemes);
}
bool AdjustClockSkew(HttpResponseOutcome& outcome, const AuthSchemeOption& authSchemeOption) const override
{
return AwsClientRequestSigning<AuthSchemesVariantT>::AdjustClockSkew(outcome, authSchemeOption, m_authSchemes);
}
ResponseT MakeRequestDeserialize(Aws::AmazonWebServiceRequest const * const request,
const char* requestName,
Aws::Http::HttpMethod method,
EndpointUpdateCallback&& endpointCallback) const
{
auto httpResponseOutcome = MakeRequestSync(request, requestName, method, std::move(endpointCallback));
return m_serializer->Deserialize(std::move(httpResponseOutcome), GetServiceClientName(), requestName);
}
protected:
ServiceClientConfigurationT& m_clientConfiguration;
std::shared_ptr<EndpointProviderT> m_endpointProvider{};
std::shared_ptr<ServiceAuthSchemeResolverT> m_authSchemeResolver{};
Aws::UnorderedMap<Aws::String, AuthSchemesVariantT> m_authSchemes{};
Aws::UniquePtr<SerializerT> m_serializer{};
};
} // namespace client
} // namespace smithy

View File

@@ -0,0 +1,77 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <smithy/Smithy_EXPORTS.h>
#include <smithy/interceptor/InterceptorContext.h>
#include <aws/core/endpoint/AWSEndpoint.h>
#include <aws/core/http/HttpRequest.h>
#include <aws/core/AmazonWebServiceRequest.h>
#include <aws/core/utils/DateTime.h>
#include <aws/core/utils/Outcome.h>
namespace smithy
{
namespace client
{
class AwsSmithyClientAsyncRequestContext
{
public:
using AwsCoreError = Aws::Client::AWSError<Aws::Client::CoreErrors>;
using HttpResponseOutcome = Aws::Utils::Outcome<std::shared_ptr<Aws::Http::HttpResponse>, AwsCoreError>;
using ResponseHandlerFunc = std::function<void(HttpResponseOutcome&&)>;
struct RequestInfo
{
Aws::Utils::DateTime ttl;
long attempt;
long maxAttempts;
Aws::String ToString() const
{
Aws::StringStream ss;
if (ttl.WasParseSuccessful() && ttl != Aws::Utils::DateTime())
{
assert(attempt > 1);
ss << "ttl=" << ttl.ToGmtString(Aws::Utils::DateFormat::ISO_8601_BASIC) << "; ";
}
ss << "attempt=" << attempt;
if (maxAttempts > 0)
{
ss << "; max=" << maxAttempts;
}
return ss.str();
}
explicit operator Aws::String() const
{
return ToString();
}
};
Aws::String m_invocationId;
Aws::Http::HttpMethod m_method;
const Aws::AmazonWebServiceRequest* m_pRequest; // optional
RequestInfo m_requestInfo;
Aws::String m_requestName;
std::shared_ptr<Aws::Http::HttpRequest> m_httpRequest;
AuthSchemeOption m_authSchemeOption;
Aws::Endpoint::AWSEndpoint m_endpoint;
Aws::Crt::Optional<AwsCoreError> m_lastError;
size_t m_retryCount;
Aws::Vector<void*> m_monitoringContexts;
ResponseHandlerFunc m_responseHandler;
std::shared_ptr<Aws::Utils::Threading::Executor> m_pExecutor;
std::shared_ptr<interceptor::InterceptorContext> m_interceptorContext;
};
} // namespace client
} // namespace smithy

View File

@@ -0,0 +1,172 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <smithy/identity/auth/AuthSchemeOption.h>
#include <smithy/identity/identity/AwsIdentity.h>
#include <smithy/tracing/TelemetryProvider.h>
#include <smithy/interceptor/Interceptor.h>
#include <smithy/client/features/ChecksumInterceptor.h>
#include <aws/crt/Variant.h>
#include <aws/core/client/ClientConfiguration.h>
#include <aws/core/endpoint/EndpointParameter.h>
#include <aws/core/http/HttpResponse.h>
#include <aws/core/utils/FutureOutcome.h>
#include <aws/core/utils/memory/stl/AWSMap.h>
#include <aws/core/utils/Outcome.h>
namespace Aws
{
namespace Utils
{
namespace RateLimits
{
class RateLimiterInterface;
}
}
namespace Http
{
class HttpClient;
}
namespace Client
{
class AWSErrorMarshaller;
class RetryStrategy;
}
namespace Utils
{
namespace Threading
{
class Executor;
}
}
class AmazonWebServiceRequest;
}
namespace Aws
{
namespace Endpoint
{
class AWSEndpoint;
}
}
namespace smithy {
namespace client
{
class AwsSmithyClientAsyncRequestContext;
/* Non-template base client class that contains main Aws Client Request pipeline logic */
class SMITHY_API AwsSmithyClientBase
{
public:
using HttpRequest = Aws::Http::HttpRequest;
using HttpResponse = Aws::Http::HttpResponse;
using CoreErrors = Aws::Client::CoreErrors;
using AWSError = Aws::Client::AWSError<CoreErrors>;
using ClientError = AWSError;
using SigningError = AWSError;
using SigningOutcome = Aws::Utils::FutureOutcome<std::shared_ptr<Aws::Http::HttpRequest>, SigningError>;
using EndpointUpdateCallback = std::function<void(Aws::Endpoint::AWSEndpoint&)>;
using HttpResponseOutcome = Aws::Utils::Outcome<std::shared_ptr<Aws::Http::HttpResponse>, AWSError>;
using ResponseHandlerFunc = std::function<void(HttpResponseOutcome&&)>;
using SelectAuthSchemeOptionOutcome = Aws::Utils::Outcome<AuthSchemeOption, AWSError>;
using ResolveEndpointOutcome = Aws::Utils::Outcome<Aws::Endpoint::AWSEndpoint, AWSError>;
AwsSmithyClientBase(Aws::UniquePtr<Aws::Client::ClientConfiguration>&& clientConfig,
Aws::String serviceName,
std::shared_ptr<Aws::Http::HttpClient> httpClient,
std::shared_ptr<Aws::Client::AWSErrorMarshaller> errorMarshaller) :
m_clientConfig(std::move(clientConfig)),
m_serviceName(std::move(serviceName)),
m_userAgent(),
m_httpClient(std::move(httpClient)),
m_errorMarshaller(std::move(errorMarshaller)),
m_interceptors{Aws::MakeShared<ChecksumInterceptor>("AwsSmithyClientBase")}
{
if (!m_clientConfig->retryStrategy)
{
assert(m_clientConfig->configFactories.retryStrategyCreateFn);
m_clientConfig->retryStrategy = m_clientConfig->configFactories.retryStrategyCreateFn();
}
if (!m_clientConfig->executor)
{
assert(m_clientConfig->configFactories.executorCreateFn);
m_clientConfig->executor = m_clientConfig->configFactories.executorCreateFn();
}
if (!m_clientConfig->writeRateLimiter)
{
assert(m_clientConfig->configFactories.writeRateLimiterCreateFn);
m_clientConfig->writeRateLimiter = m_clientConfig->configFactories.writeRateLimiterCreateFn();
}
if (!m_clientConfig->readRateLimiter)
{
assert(m_clientConfig->configFactories.readRateLimiterCreateFn);
m_clientConfig->readRateLimiter = m_clientConfig->configFactories.readRateLimiterCreateFn();
}
if (!m_clientConfig->telemetryProvider)
{
assert(m_clientConfig->configFactories.telemetryProviderCreateFn);
m_clientConfig->telemetryProvider = m_clientConfig->configFactories.telemetryProviderCreateFn();
}
m_userAgent = Aws::Client::ComputeUserAgentString(m_clientConfig.get());
}
AwsSmithyClientBase(const AwsSmithyClientBase&) = delete;
AwsSmithyClientBase(AwsSmithyClientBase&&) = delete;
AwsSmithyClientBase& operator=(const AwsSmithyClientBase&) = delete;
AwsSmithyClientBase& operator=(AwsSmithyClientBase&&) = delete;
virtual ~AwsSmithyClientBase() = default;
void MakeRequestAsync(Aws::AmazonWebServiceRequest const * const request,
const char* requestName,
Aws::Http::HttpMethod method,
EndpointUpdateCallback&& endpointCallback,
ResponseHandlerFunc&& responseHandler,
std::shared_ptr<Aws::Utils::Threading::Executor> pExecutor) const;
HttpResponseOutcome MakeRequestSync(Aws::AmazonWebServiceRequest const * const request,
const char* requestName,
Aws::Http::HttpMethod method,
EndpointUpdateCallback&& endpointCallback) const;
protected:
/**
* Transforms the AmazonWebServicesResult object into an HttpRequest.
*/
std::shared_ptr<Aws::Http::HttpRequest> BuildHttpRequest(const std::shared_ptr<AwsSmithyClientAsyncRequestContext>& pRequestCtx, const Aws::Http::URI& uri, Aws::Http::HttpMethod method) const;
virtual void AttemptOneRequestAsync(std::shared_ptr<AwsSmithyClientAsyncRequestContext> pRequestCtx) const;
virtual void HandleAsyncReply(std::shared_ptr<AwsSmithyClientAsyncRequestContext> pRequestCtx,
std::shared_ptr<Aws::Http::HttpResponse> httpResponse) const;
inline virtual const char* GetServiceClientName() const { return m_serviceName.c_str(); }
inline virtual const std::shared_ptr<Aws::Http::HttpClient>& GetHttpClient() { return m_httpClient; }
virtual void DisableRequestProcessing();
virtual ResolveEndpointOutcome ResolveEndpoint(const Aws::Endpoint::EndpointParameters& endpointParameters, EndpointUpdateCallback&& epCallback) const = 0;
virtual SelectAuthSchemeOptionOutcome SelectAuthSchemeOption(const AwsSmithyClientAsyncRequestContext& ctx) const = 0;
virtual SigningOutcome SignRequest(std::shared_ptr<HttpRequest> httpRequest, const AuthSchemeOption& targetAuthSchemeOption) const = 0;
virtual bool AdjustClockSkew(HttpResponseOutcome& outcome, const AuthSchemeOption& authSchemeOption) const = 0;
protected:
Aws::UniquePtr<Aws::Client::ClientConfiguration> m_clientConfig;
Aws::String m_serviceName;
Aws::String m_userAgent;
std::shared_ptr<Aws::Http::HttpClient> m_httpClient;
std::shared_ptr<Aws::Client::AWSErrorMarshaller> m_errorMarshaller;
Aws::Vector<std::shared_ptr<smithy::interceptor::Interceptor>> m_interceptors{};
};
} // namespace client
} // namespace smithy

View File

@@ -0,0 +1,160 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <iomanip>
#include <aws/core/http/HttpRequest.h>
#include <cassert>
#include <aws/core/http/HttpClient.h>
#include <aws/core/utils/HashingUtils.h>
#include <aws/core/utils/crypto/MD5.h>
#include <aws/core/utils/logging/LogMacros.h>
namespace smithy
{
namespace client
{
static const char AWS_SMITHY_CLIENT_UTILS_TAG[] = "AwsSmithyClientUtils";
class Utils
{
public:
using HttpMethod = Aws::Http::HttpMethod;
using HeaderValueCollection = Aws::Http::HeaderValueCollection;
using DateTime = Aws::Utils::DateTime;
using DateFormat = Aws::Utils::DateFormat;
using ClientError = Aws::Client::AWSError<Aws::Client::CoreErrors>;
static void AppendHeaderValueToRequest(const std::shared_ptr<Aws::Http::HttpRequest>& httpRequest,
const Aws::String& header,
const Aws::String& value)
{
assert(httpRequest);
assert(!header.empty());
if (!httpRequest->HasHeader(header.c_str()))
{
httpRequest->SetHeaderValue(header, value);
}
else
{
Aws::String contentEncoding = httpRequest->GetHeaderValue(header.c_str());
contentEncoding.append(",").append(value);
httpRequest->SetHeaderValue(header, contentEncoding);
}
}
static void AddContentBodyToRequest(const std::shared_ptr<Aws::Http::HttpRequest>& httpRequest,
const std::shared_ptr<Aws::IOStream>& body,
const std::shared_ptr<Aws::Http::HttpClient>& httpClient,
bool needsContentMd5,
bool isChunked)
{
assert(httpRequest);
httpRequest->AddContentBody(body);
//If there is no body, we have a content length of 0
//note: we also used to remove content-type, but S3 actually needs content-type on InitiateMultipartUpload and it isn't
//forbidden by the spec. If we start getting weird errors related to this, make sure it isn't caused by this removal.
if (!body)
{
AWS_LOGSTREAM_TRACE(AWS_SMITHY_CLIENT_UTILS_TAG, "No content body, content-length headers");
if (httpRequest->GetMethod() == HttpMethod::HTTP_POST || httpRequest->GetMethod() ==
HttpMethod::HTTP_PUT)
{
httpRequest->SetHeaderValue(Aws::Http::CONTENT_LENGTH_HEADER, "0");
}
else
{
httpRequest->DeleteHeader(Aws::Http::CONTENT_LENGTH_HEADER);
}
}
//Add transfer-encoding:chunked to header
if (body && isChunked && !httpRequest->HasHeader(Aws::Http::CONTENT_LENGTH_HEADER))
{
httpRequest->SetTransferEncoding(Aws::Http::CHUNKED_VALUE);
}
//in the scenario where we are adding a content body as a stream, the request object likely already
//has a content-length header set and we don't want to seek the stream just to find this information.
else if (body && !httpRequest->HasHeader(Aws::Http::CONTENT_LENGTH_HEADER))
{
assert(httpClient);
if (!httpClient->SupportsChunkedTransferEncoding())
{
AWS_LOGSTREAM_WARN(AWS_SMITHY_CLIENT_UTILS_TAG,
"This http client doesn't support transfer-encoding:chunked. " <<
"The request may fail if it's not a seekable stream.");
}
AWS_LOGSTREAM_TRACE(AWS_SMITHY_CLIENT_UTILS_TAG,
"Found body, but content-length has not been set, attempting to compute content-length");
body->seekg(0, body->end);
auto streamSize = body->tellg();
body->seekg(0, body->beg);
Aws::StringStream ss;
ss << streamSize;
httpRequest->SetContentLength(ss.str());
}
if (needsContentMd5 && body && !httpRequest->HasHeader(Aws::Http::CONTENT_MD5_HEADER))
{
AWS_LOGSTREAM_TRACE(AWS_SMITHY_CLIENT_UTILS_TAG, "Found body, and content-md5 needs to be set" <<
", attempting to compute content-md5");
//changing the internal state of the hash computation is not a logical state
//change as far as constness goes for this class. Due to the platform specificness
//of hash computations, we can't control the fact that computing a hash mutates
//state on some platforms such as windows (but that isn't a concern of this class.
// TODO: check refactoring from:
// auto md5HashResult = const_cast<AWSClient*>(this)->m_hash->Calculate(*body);
Aws::Utils::Crypto::MD5 hash;
auto md5HashResult = hash.Calculate(*body);
body->clear();
if (md5HashResult.IsSuccess())
{
httpRequest->SetHeaderValue(Aws::Http::CONTENT_MD5_HEADER,
Aws::Utils::HashingUtils::Base64Encode(md5HashResult.GetResult()));
}
}
}
static bool DoesResponseGenerateError(const std::shared_ptr<Aws::Http::HttpResponse>& response)
{
assert(response);
if (response->HasClientError())
return true;
static const int SUCCESS_RESPONSE_MIN = 200;
static const int SUCCESS_RESPONSE_MAX = 299;
const int responseCode = static_cast<int>(response->GetResponseCode());
return responseCode < SUCCESS_RESPONSE_MIN || responseCode > SUCCESS_RESPONSE_MAX;
}
static Aws::Utils::DateTime GetServerTimeFromError(const ClientError& error)
{
const HeaderValueCollection& headers = error.GetResponseHeaders();
auto awsDateHeaderIter = headers.find(Aws::Utils::StringUtils::ToLower(Aws::Http::AWS_DATE_HEADER));
auto dateHeaderIter = headers.find(Aws::Utils::StringUtils::ToLower(Aws::Http::DATE_HEADER));
if (awsDateHeaderIter != headers.end())
{
return DateTime(awsDateHeaderIter->second.c_str(), DateFormat::AutoDetect);
}
else if (dateHeaderIter != headers.end())
{
return DateTime(dateHeaderIter->second.c_str(), DateFormat::AutoDetect);
}
return DateTime();
}
};
}
}

View File

@@ -0,0 +1,215 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <smithy/client/common/AwsSmithyClientUtils.h>
#include <smithy/identity/auth/AuthSchemeOption.h>
#include <smithy/identity/identity/AwsIdentity.h>
#include <smithy/identity/resolver/AwsIdentityResolverBase.h>
#include <smithy/identity/signer/AwsSignerBase.h>
#include <aws/core/utils/FutureOutcome.h>
#include <aws/core/client/AWSError.h>
#include <aws/core/http/HttpRequest.h>
#include <aws/crt/Variant.h>
#include <aws/crt/Optional.h>
#include <aws/core/utils/memory/stl/AWSMap.h>
#include <cassert>
namespace smithy
{
static const char AWS_SMITHY_CLIENT_SIGNING_TAG[] = "AwsClientRequestSigning";
//4 Minutes
static const std::chrono::milliseconds TIME_DIFF_MAX = std::chrono::minutes(4);
//-4 Minutes
static const std::chrono::milliseconds TIME_DIFF_MIN = std::chrono::minutes(-4);
template <typename AuthSchemesVariantT>
class AwsClientRequestSigning
{
public:
using HttpRequest = Aws::Http::HttpRequest;
using SigningError = Aws::Client::AWSError<Aws::Client::CoreErrors>;
using SigningOutcome = Aws::Utils::FutureOutcome<std::shared_ptr<HttpRequest>, SigningError>;
using HttpResponseOutcome = Aws::Utils::Outcome<std::shared_ptr<Aws::Http::HttpResponse>, Aws::Client::AWSError<Aws::Client::CoreErrors>>;
static SigningOutcome SignRequest(std::shared_ptr<HttpRequest> HTTPRequest, const AuthSchemeOption& authSchemeOption,
const Aws::UnorderedMap<Aws::String, AuthSchemesVariantT>& authSchemes)
{
auto authSchemeIt = authSchemes.find(authSchemeOption.schemeId);
if (authSchemeIt == authSchemes.end())
{
assert(!"Auth scheme has not been found for a given auth option!");
return (SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE,
"",
"Requested AuthSchemeOption was not found within client Auth Schemes",
false/*retryable*/));
}
const AuthSchemesVariantT& authScheme = authSchemeIt->second;
return SignWithAuthScheme(std::move(HTTPRequest), authScheme, authSchemeOption);
}
static bool AdjustClockSkew(HttpResponseOutcome& outcome, const AuthSchemeOption& authSchemeOption,
const Aws::UnorderedMap<Aws::String, AuthSchemesVariantT>& authSchemes)
{
assert(!outcome.IsSuccess());
AWS_LOGSTREAM_WARN(AWS_SMITHY_CLIENT_SIGNING_TAG, "If the signature check failed. This could be because of a time skew. Attempting to adjust the signer.");
using DateTime = Aws::Utils::DateTime;
DateTime serverTime = smithy::client::Utils::GetServerTimeFromError(outcome.GetError());
auto authSchemeIt = authSchemes.find(authSchemeOption.schemeId);
if (authSchemeIt == authSchemes.end())
{
assert(!"Auth scheme has not been found for a given auth option!");
return false;
}
AuthSchemesVariantT authScheme = authSchemeIt->second;
ClockSkewVisitor visitor(outcome, serverTime, authSchemeOption);
authScheme.Visit(visitor);
return visitor.m_resultShouldWait;
}
protected:
struct SignerVisitor
{
SignerVisitor(std::shared_ptr<HttpRequest> httpRequest, const AuthSchemeOption& targetAuthSchemeOption)
: m_httpRequest(std::move(httpRequest)), m_targetAuthSchemeOption(targetAuthSchemeOption)
{
}
const std::shared_ptr<HttpRequest> m_httpRequest;
const AuthSchemeOption& m_targetAuthSchemeOption;
Aws::Crt::Optional<SigningOutcome> result;
template <typename AuthSchemeAlternativeT>
void operator()(AuthSchemeAlternativeT& authScheme)
{
// Auth Scheme Variant alternative contains the requested auth option
assert(strcmp(authScheme.schemeId, m_targetAuthSchemeOption.schemeId) == 0);
using IdentityT = typename std::remove_reference<decltype(authScheme)>::type::IdentityT;
using IdentityResolver = IdentityResolverBase<IdentityT>;
using Signer = AwsSignerBase<IdentityT>;
std::shared_ptr<IdentityResolver> identityResolver = authScheme.identityResolver();
if (!identityResolver)
{
result.emplace(SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE,
"",
"Auth scheme provided a nullptr identityResolver",
false/*retryable*/));
return;
}
auto identityResult = identityResolver->getIdentity(m_targetAuthSchemeOption.identityProperties(), m_targetAuthSchemeOption.identityProperties());
if (!identityResult.IsSuccess())
{
result.emplace(identityResult.GetError());
return;
}
auto identity = std::move(identityResult.GetResultWithOwnership());
std::shared_ptr<Signer> signer = authScheme.signer();
if (!signer)
{
result.emplace(SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE,
"",
"Auth scheme provided a nullptr signer",
false/*retryable*/));
return;
}
result.emplace(signer->sign(m_httpRequest, *identity, m_targetAuthSchemeOption.signerProperties()));
}
};
static
SigningOutcome SignWithAuthScheme(std::shared_ptr<HttpRequest> httpRequest, const AuthSchemesVariantT& authSchemesVariant,
const AuthSchemeOption& targetAuthSchemeOption)
{
SignerVisitor visitor(httpRequest, targetAuthSchemeOption);
AuthSchemesVariantT authSchemesVariantCopy(authSchemesVariant); // TODO: allow const visiting
authSchemesVariantCopy.Visit(visitor);
if (!visitor.result)
{
return (SigningError(Aws::Client::CoreErrors::CLIENT_SIGNING_FAILURE,
"",
"Failed to sign with an unknown error",
false/*retryable*/));
}
return std::move(*visitor.result);
}
struct ClockSkewVisitor
{
using DateTime = Aws::Utils::DateTime;
using DateFormat = Aws::Utils::DateFormat;
using ClientError = Aws::Client::AWSError<Aws::Client::CoreErrors>;
ClockSkewVisitor(HttpResponseOutcome& outcome, const DateTime& serverTime, const AuthSchemeOption& targetAuthSchemeOption)
: m_outcome(outcome), m_serverTime(serverTime), m_targetAuthSchemeOption(targetAuthSchemeOption)
{
}
bool m_resultShouldWait = false;
HttpResponseOutcome& m_outcome;
const Aws::Utils::DateTime& m_serverTime;
const AuthSchemeOption& m_targetAuthSchemeOption;
template <typename AuthSchemeAlternativeT>
void operator()(AuthSchemeAlternativeT& authScheme)
{
// Auth Scheme Variant alternative contains the requested auth option
assert(strcmp(authScheme.schemeId, m_targetAuthSchemeOption.schemeId) == 0);
using IdentityT = typename std::remove_reference<decltype(authScheme)>::type::IdentityT;
using Signer = AwsSignerBase<IdentityT>;
std::shared_ptr<Signer> signer = authScheme.signer();
if (!signer)
{
AWS_LOGSTREAM_ERROR(AWS_SMITHY_CLIENT_SIGNING_TAG, "Failed to adjust signing clock skew. Signer is null.");
return;
}
const auto signingTimestamp = signer->GetSigningTimestamp();
if (!m_serverTime.WasParseSuccessful() || m_serverTime == DateTime())
{
AWS_LOGSTREAM_DEBUG(AWS_SMITHY_CLIENT_SIGNING_TAG, "Date header was not found in the response, can't attempt to detect clock skew");
return;
}
AWS_LOGSTREAM_DEBUG(AWS_SMITHY_CLIENT_SIGNING_TAG, "Server time is " << m_serverTime.ToGmtString(DateFormat::RFC822) << ", while client time is " << DateTime::Now().ToGmtString(DateFormat::RFC822));
auto diff = DateTime::Diff(m_serverTime, signingTimestamp);
//only try again if clock skew was the cause of the error.
if (diff >= TIME_DIFF_MAX || diff <= TIME_DIFF_MIN)
{
diff = DateTime::Diff(m_serverTime, DateTime::Now());
AWS_LOGSTREAM_INFO(AWS_SMITHY_CLIENT_SIGNING_TAG, "Computed time difference as " << diff.count() << " milliseconds. Adjusting signer with the skew.");
signer->SetClockSkew(diff);
ClientError newError(m_outcome.GetError());
newError.SetRetryableType(Aws::Client::RetryableType::RETRYABLE);
m_outcome = std::move(newError);
m_resultShouldWait = true;
}
}
};
};
}

View File

@@ -0,0 +1,261 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <smithy/interceptor/Interceptor.h>
#include <aws/core/AmazonWebServiceRequest.h>
#include <aws/core/http/HttpRequest.h>
#include <aws/core/http/HttpResponse.h>
#include <aws/core/utils/HashingUtils.h>
#include <aws/core/utils/crypto/MD5.h>
#include <aws/core/utils/crypto/CRC32.h>
#include <aws/core/utils/crypto/Sha256.h>
#include <aws/core/utils/crypto/Sha1.h>
#include <aws/core/utils/crypto/PrecalculatedHash.h>
#include <aws/core/platform/Environment.h>
#include <iomanip>
namespace smithy
{
namespace client
{
static const char AWS_SMITHY_CLIENT_CHECKSUM[] = "AwsSmithyClientChecksums";
static const char CHECKSUM_CONTENT_MD5_HEADER[] = "content-md5";
class ChecksumInterceptor: public smithy::interceptor::Interceptor
{
public:
using HeaderValueCollection = Aws::Http::HeaderValueCollection;
using HashingUtils = Aws::Utils::HashingUtils;
using MD5 = Aws::Utils::Crypto::MD5;
using CRC32 = Aws::Utils::Crypto::CRC32;
using CRC32C = Aws::Utils::Crypto::CRC32C;
using Sha256 = Aws::Utils::Crypto::Sha256;
using Sha1 = Aws::Utils::Crypto::Sha1;
using PrecalculatedHash = Aws::Utils::Crypto::PrecalculatedHash;
~ChecksumInterceptor() override = default;
ChecksumInterceptor() = default;
ChecksumInterceptor(const ChecksumInterceptor& other) = delete;
ChecksumInterceptor(ChecksumInterceptor&& other) noexcept = default;
ChecksumInterceptor& operator=(const ChecksumInterceptor& other) = delete;
ChecksumInterceptor& operator=(ChecksumInterceptor&& other) noexcept = default;
static std::shared_ptr<Aws::IOStream> GetBodyStream(const Aws::AmazonWebServiceRequest& request)
{
if (request.GetBody() != nullptr)
{
return request.GetBody();
}
// Return an empty string stream for no body
return Aws::MakeShared<Aws::StringStream>(AWS_SMITHY_CLIENT_CHECKSUM, "");
}
ModifyRequestOutcome ModifyBeforeSigning(interceptor::InterceptorContext& context) override
{
const auto& httpRequest = context.GetTransmitRequest();
const auto& request = context.GetModeledRequest();
if (httpRequest == nullptr)
{
return Aws::Client::AWSError<Aws::Client::CoreErrors>{Aws::Client::CoreErrors::VALIDATION,
"ValidationErrorException",
"Checksum request validation missing request",
false};
}
Aws::String checksumAlgorithmName = Aws::Utils::StringUtils::ToLower(
request.GetChecksumAlgorithmName().c_str());
if (request.GetServiceSpecificParameters())
{
auto requestChecksumOverride = request.GetServiceSpecificParameters()->parameterMap.find(
"overrideChecksum");
if (requestChecksumOverride != request.GetServiceSpecificParameters()->parameterMap.end())
{
checksumAlgorithmName = requestChecksumOverride->second;
}
}
bool shouldSkipChecksum = request.GetServiceSpecificParameters() &&
request.GetServiceSpecificParameters()->parameterMap.find("overrideChecksumDisable") !=
request.GetServiceSpecificParameters()->parameterMap.end();
//Check if user has provided the checksum algorithm
if (!checksumAlgorithmName.empty() && !shouldSkipChecksum)
{
// Check if user has provided a checksum value for the specified algorithm
const Aws::String checksumType = "x-amz-checksum-" + checksumAlgorithmName;
const HeaderValueCollection& headers = request.GetHeaders();
const auto checksumHeader = headers.find(checksumType);
bool checksumValueAndAlgorithmProvided = checksumHeader != headers.end();
// For non-streaming payload, the resolved checksum location is always header.
// For streaming payload, the resolved checksum location depends on whether it is an unsigned payload, we let AwsAuthSigner decide it.
if (request.IsStreaming() && checksumValueAndAlgorithmProvided)
{
const auto hash = Aws::MakeShared<PrecalculatedHash>(
AWS_SMITHY_CLIENT_CHECKSUM, checksumHeader->second);
httpRequest->SetRequestHash(checksumAlgorithmName, hash);
}
else if (checksumValueAndAlgorithmProvided)
{
httpRequest->SetHeaderValue(checksumType, checksumHeader->second);
}
else if (checksumAlgorithmName == "crc32")
{
if (request.IsStreaming())
{
httpRequest->SetRequestHash(checksumAlgorithmName,
Aws::MakeShared<CRC32>(AWS_SMITHY_CLIENT_CHECKSUM));
}
else
{
httpRequest->SetHeaderValue(checksumType,
HashingUtils::Base64Encode(
HashingUtils::CalculateCRC32(*(GetBodyStream(request)))));
}
}
else if (checksumAlgorithmName == "crc32c")
{
if (request.IsStreaming())
{
httpRequest->SetRequestHash(checksumAlgorithmName,
Aws::MakeShared<CRC32C>(AWS_SMITHY_CLIENT_CHECKSUM));
}
else
{
httpRequest->SetHeaderValue(checksumType,
HashingUtils::Base64Encode(
HashingUtils::CalculateCRC32C(*(GetBodyStream(request)))));
}
}
else if (checksumAlgorithmName == "sha256")
{
if (request.IsStreaming())
{
httpRequest->SetRequestHash(checksumAlgorithmName,
Aws::MakeShared<Sha256>(AWS_SMITHY_CLIENT_CHECKSUM));
}
else
{
httpRequest->SetHeaderValue(checksumType,
HashingUtils::Base64Encode(
HashingUtils::CalculateSHA256(*(GetBodyStream(request)))));
}
}
else if (checksumAlgorithmName == "sha1")
{
if (request.IsStreaming())
{
httpRequest->SetRequestHash(checksumAlgorithmName,
Aws::MakeShared<Sha1>(AWS_SMITHY_CLIENT_CHECKSUM));
}
else
{
httpRequest->SetHeaderValue(checksumType,
HashingUtils::Base64Encode(
HashingUtils::CalculateSHA1(*(GetBodyStream(request)))));
}
}
else if (checksumAlgorithmName == "md5" && headers.find(CHECKSUM_CONTENT_MD5_HEADER) == headers.end())
{
httpRequest->SetHeaderValue(CHECKSUM_CONTENT_MD5_HEADER,
HashingUtils::Base64Encode(
HashingUtils::CalculateMD5(*(GetBodyStream(request)))));
}
else
{
AWS_LOGSTREAM_WARN(AWS_SMITHY_CLIENT_CHECKSUM,
"Checksum algorithm: " << checksumAlgorithmName <<
"is not supported by SDK.");
}
}
// Response checksums
if (request.ShouldValidateResponseChecksum())
{
for (const Aws::String& responseChecksumAlgorithmName : request.GetResponseChecksumAlgorithmNames())
{
checksumAlgorithmName = Aws::Utils::StringUtils::ToLower(responseChecksumAlgorithmName.c_str());
if (checksumAlgorithmName == "crc32c")
{
std::shared_ptr<CRC32C> crc32c = Aws::MakeShared<
CRC32C>(AWS_SMITHY_CLIENT_CHECKSUM);
httpRequest->AddResponseValidationHash("crc32c", crc32c);
}
else if (checksumAlgorithmName == "crc32")
{
std::shared_ptr<CRC32> crc32 = Aws::MakeShared<
CRC32>(AWS_SMITHY_CLIENT_CHECKSUM);
httpRequest->AddResponseValidationHash("crc32", crc32);
}
else if (checksumAlgorithmName == "sha1")
{
std::shared_ptr<Sha1> sha1 = Aws::MakeShared<Sha1>(
AWS_SMITHY_CLIENT_CHECKSUM);
httpRequest->AddResponseValidationHash("sha1", sha1);
}
else if (checksumAlgorithmName == "sha256")
{
std::shared_ptr<Sha256> sha256 = Aws::MakeShared<
Sha256>(AWS_SMITHY_CLIENT_CHECKSUM);
httpRequest->AddResponseValidationHash("sha256", sha256);
}
else
{
AWS_LOGSTREAM_WARN(AWS_SMITHY_CLIENT_CHECKSUM,
"Checksum algorithm: " << checksumAlgorithmName <<
" is not supported in validating response body yet.");
}
}
}
return httpRequest;
}
ModifyResponseOutcome ModifyBeforeDeserialization(interceptor::InterceptorContext& context) override
{
const auto httpRequest = context.GetTransmitRequest();
const auto httpResponse = context.GetTransmitResponse();
if (httpRequest == nullptr || httpResponse == nullptr)
{
return Aws::Client::AWSError<Aws::Client::CoreErrors>{Aws::Client::CoreErrors::VALIDATION,
"ValidationErrorException",
"Checksum response validation missing request or response",
false};
}
for (const auto& hashIterator : httpRequest->GetResponseValidationHashes())
{
Aws::String checksumHeaderKey = Aws::String("x-amz-checksum-") + hashIterator.first;
// TODO: If checksum ends with -#, then skip
if (httpResponse->HasHeader(checksumHeaderKey.c_str()))
{
const Aws::String& checksumHeaderValue = httpResponse->GetHeader(checksumHeaderKey);
if (HashingUtils::Base64Encode(hashIterator.second->GetHash().GetResult()) !=
checksumHeaderValue)
{
auto error = Aws::Client::AWSError<Aws::Client::CoreErrors>{
Aws::Client::CoreErrors::VALIDATION, "",
"Response checksums mismatch",
false/*retryable*/};
error.SetResponseHeaders(httpResponse->GetHeaders());
error.SetResponseCode(httpResponse->GetResponseCode());
error.SetRemoteHostIpAddress(
httpResponse->GetOriginatingRequest().GetResolvedRemoteHost());
AWS_LOGSTREAM_ERROR(AWS_SMITHY_CLIENT_CHECKSUM, error);
return {error};
}
// Validate only a single checksum returned in an HTTP response
break;
}
}
return httpResponse;
}
};
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <aws/core/platform/Environment.h>
#include <aws/core/http/HttpRequest.h>
#include <iomanip>
namespace smithy
{
namespace client
{
static const char SMITHY_AWS_LAMBDA_FUNCTION_NAME[] = "AWS_LAMBDA_FUNCTION_NAME";
static const char SMITHY_X_AMZN_TRACE_ID[] = "_X_AMZN_TRACE_ID";
class RecursionDetection
{
public:
static void AppendRecursionDetectionHeader(const std::shared_ptr<Aws::Http::HttpRequest>& ioRequest)
{
if (!ioRequest || ioRequest->HasHeader(Aws::Http::X_AMZN_TRACE_ID_HEADER))
{
return;
}
const Aws::String& awsLambdaFunctionName = Aws::Environment::GetEnv(SMITHY_AWS_LAMBDA_FUNCTION_NAME);
if (awsLambdaFunctionName.empty())
{
return;
}
Aws::String xAmznTraceIdVal = Aws::Environment::GetEnv(SMITHY_X_AMZN_TRACE_ID);
if (xAmznTraceIdVal.empty())
{
return;
}
// Escape all non-printable ASCII characters by percent encoding
Aws::OStringStream xAmznTraceIdValEncodedStr;
for (const char ch : xAmznTraceIdVal)
{
if (ch >= 0x20 && ch <= 0x7e) // ascii chars [32-126] or [' ' to '~'] are not escaped
{
xAmznTraceIdValEncodedStr << ch;
}
else
{
// A percent-encoded octet is encoded as a character triplet
xAmznTraceIdValEncodedStr << '%' // consisting of the percent character "%"
<< std::hex << std::setfill('0') << std::setw(2) << std::uppercase
<< (size_t)ch
//followed by the two hexadecimal digits representing that octet's numeric value
<< std::dec << std::setfill(' ') << std::setw(0) << std::nouppercase;
}
}
xAmznTraceIdVal = xAmznTraceIdValEncodedStr.str();
ioRequest->SetHeaderValue(Aws::Http::X_AMZN_TRACE_ID_HEADER, xAmznTraceIdVal);
}
};
}
}

View File

@@ -0,0 +1,63 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <aws/core/AmazonWebServiceRequest.h>
#include <aws/core/client/RequestCompression.h>
#include <aws/core/http/HttpClient.h>
#include <aws/core/http/HttpRequest.h>
#include <smithy/client/common/AwsSmithyClientUtils.h>
#include <iomanip>
#include <cassert>
namespace smithy
{
namespace client
{
static const char AWS_CLIENT_REQUEST_COMPRESSION_LOG_TAG[] = "RequestPayloadCompression";
class RequestPayloadCompression
{
public:
static void AddCompressedContentBodyToRequest(const Aws::AmazonWebServiceRequest* pRequest,
const std::shared_ptr<Aws::Http::HttpRequest>& httpRequest,
const Aws::Client::CompressionAlgorithm& compressionAlgorithm,
const std::shared_ptr<Aws::Http::HttpClient>& httpClient)
{
if (Aws::Client::CompressionAlgorithm::NONE != compressionAlgorithm)
{
Aws::Client::RequestCompression rc;
auto compressOutcome = rc.compress(pRequest->GetBody(), compressionAlgorithm);
if (compressOutcome.IsSuccess())
{
const Aws::String compressionAlgorithmId = Aws::Client::GetCompressionAlgorithmId(
compressionAlgorithm);
Utils::AppendHeaderValueToRequest(httpRequest, Aws::Http::CONTENT_ENCODING_HEADER,
compressionAlgorithmId);
Utils::AddContentBodyToRequest(httpRequest,
compressOutcome.GetResult(),
httpClient,
pRequest->ShouldComputeContentMd5(),
pRequest->IsStreaming() && pRequest->IsChunked() && httpClient->
SupportsChunkedTransferEncoding());
}
else
{
AWS_LOGSTREAM_ERROR(AWS_CLIENT_REQUEST_COMPRESSION_LOG_TAG,
"Failed to compress request, submitting uncompressed");
Utils::AddContentBodyToRequest(httpRequest,
pRequest->GetBody(),
httpClient,
pRequest->ShouldComputeContentMd5(),
pRequest->IsStreaming() && pRequest->IsChunked() && httpClient->
SupportsChunkedTransferEncoding());
}
}
}
};
}
}

View File

@@ -0,0 +1,100 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <aws/core/AmazonWebServiceResult.h>
#include <aws/core/client/AWSError.h>
#include <aws/core/utils/Outcome.h>
#include <aws/core/utils/json/JsonSerializer.h>
#include <smithy/tracing/TracingUtils.h>
#include <smithy/tracing/TelemetryProvider.h>
namespace smithy
{
namespace client
{
using TracingUtils = components::tracing::TracingUtils;
using CoreErrors = Aws::Client::CoreErrors;
using AWSError = Aws::Client::AWSError<CoreErrors>;
using JsonValue = Aws::Utils::Json::JsonValue;
using HttpResponseOutcome = Aws::Utils::Outcome<std::shared_ptr<Aws::Http::HttpResponse>, AWSError>;
using JsonOutcome = Aws::Utils::Outcome<Aws::AmazonWebServiceResult<JsonValue>, AWSError>;
using TelemetryProvider = components::tracing::TelemetryProvider;
class JsonOutcomeSerializer
{
public:
explicit JsonOutcomeSerializer(const std::shared_ptr<TelemetryProvider>& telemetryProvider)
: m_telemetryProvider(telemetryProvider)
{
}
JsonOutcomeSerializer(const JsonOutcomeSerializer& other) = delete;
JsonOutcomeSerializer(JsonOutcomeSerializer&& other) noexcept = default;
JsonOutcomeSerializer& operator=(const JsonOutcomeSerializer& other) = delete;
JsonOutcomeSerializer& operator=(JsonOutcomeSerializer&& other) noexcept = default;
virtual ~JsonOutcomeSerializer() = default;
JsonOutcome Deserialize(HttpResponseOutcome&& httpOutcome,
const Aws::String& serviceName,
const Aws::String& requestName) const
{
if (!httpOutcome.IsSuccess())
{
return TracingUtils::MakeCallWithTiming<JsonOutcome>(
[&]() -> JsonOutcome {
return JsonOutcome{std::move(httpOutcome)};
},
TracingUtils::SMITHY_CLIENT_DESERIALIZATION_METRIC,
*m_telemetryProvider->getMeter(serviceName, {}),
{{TracingUtils::SMITHY_METHOD_DIMENSION, requestName},
{TracingUtils::SMITHY_SERVICE_DIMENSION, serviceName}});
}
if (httpOutcome.GetResult()->GetResponseBody().good() &&
httpOutcome.GetResult()->GetResponseBody().tellp() > 0)
{
JsonValue jsonValue(httpOutcome.GetResult()->GetResponseBody());
if (!jsonValue.WasParseSuccessful()) {
return TracingUtils::MakeCallWithTiming<JsonOutcome>(
[&]() -> JsonOutcome {
return JsonOutcome{AWSError(CoreErrors::UNKNOWN,
"Json Parser Error",
jsonValue.GetErrorMessage(),
false)};
},
TracingUtils::SMITHY_CLIENT_DESERIALIZATION_METRIC,
*m_telemetryProvider->getMeter(serviceName, {}),
{{TracingUtils::SMITHY_METHOD_DIMENSION, requestName},
{TracingUtils::SMITHY_SERVICE_DIMENSION, serviceName}});
}
return TracingUtils::MakeCallWithTiming<JsonOutcome>(
[&]() -> JsonOutcome {
return JsonOutcome{Aws::AmazonWebServiceResult<JsonValue>(std::move(jsonValue),
httpOutcome.GetResult()->GetHeaders(),
httpOutcome.GetResult()->GetResponseCode())};
},
TracingUtils::SMITHY_CLIENT_DESERIALIZATION_METRIC,
*m_telemetryProvider->getMeter(serviceName, {}),
{{TracingUtils::SMITHY_METHOD_DIMENSION, requestName},
{TracingUtils::SMITHY_SERVICE_DIMENSION, serviceName}});
}
return TracingUtils::MakeCallWithTiming<JsonOutcome>(
[&]() -> JsonOutcome {
return JsonOutcome{Aws::AmazonWebServiceResult<JsonValue>(JsonValue(),
httpOutcome.GetResult()->GetHeaders())};
},
TracingUtils::SMITHY_CLIENT_DESERIALIZATION_METRIC,
*m_telemetryProvider->getMeter(serviceName, {}),
{{TracingUtils::SMITHY_METHOD_DIMENSION, requestName},
{TracingUtils::SMITHY_SERVICE_DIMENSION, serviceName}});
}
private:
std::shared_ptr<TelemetryProvider> m_telemetryProvider;
};
} // namespace client
} // namespace smithy

View File

@@ -0,0 +1,88 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/
#pragma once
#include <aws/core/AmazonWebServiceResult.h>
#include <aws/core/client/AWSError.h>
#include <aws/core/utils/Outcome.h>
#include <aws/core/utils/xml/XmlSerializer.h>
#include <smithy/tracing/TracingUtils.h>
#include <smithy/tracing/TelemetryProvider.h>
namespace smithy
{
namespace client
{
using TracingUtils = components::tracing::TracingUtils;
using CoreErrors = Aws::Client::CoreErrors;
using AWSError = Aws::Client::AWSError<CoreErrors>;
using XmlDocument = Aws::Utils::Xml::XmlDocument;
using HttpResponseOutcome = Aws::Utils::Outcome<std::shared_ptr<Aws::Http::HttpResponse>, AWSError>;
using XmlServiceResult = Aws::AmazonWebServiceResult<XmlDocument>;
using XmlOutcome = Aws::Utils::Outcome<XmlServiceResult, AWSError>;
using TelemetryProvider = components::tracing::TelemetryProvider;
class XmlOutcomeSerializer
{
public:
explicit XmlOutcomeSerializer(const std::shared_ptr<TelemetryProvider>& telemetryProvider)
: m_telemetryProvider(telemetryProvider)
{
}
XmlOutcomeSerializer(const XmlOutcomeSerializer& other) = delete;
XmlOutcomeSerializer(XmlOutcomeSerializer&& other) noexcept = default;
XmlOutcomeSerializer& operator=(const XmlOutcomeSerializer& other) = delete;
XmlOutcomeSerializer& operator=(XmlOutcomeSerializer&& other) noexcept = default;
virtual ~XmlOutcomeSerializer() = default;
XmlOutcome Deserialize(HttpResponseOutcome&& httpOutcome,
const Aws::String& serviceName,
const Aws::String& requestName) const
{
if (!httpOutcome.IsSuccess())
{
return TracingUtils::MakeCallWithTiming<XmlOutcome>(
[&]() -> XmlOutcome {
return {std::move(httpOutcome)};
},
TracingUtils::SMITHY_CLIENT_DESERIALIZATION_METRIC,
*m_telemetryProvider->getMeter(serviceName, {}),
{{TracingUtils::SMITHY_METHOD_DIMENSION, requestName}, {TracingUtils::SMITHY_SERVICE_DIMENSION, serviceName}});
}
if (httpOutcome.GetResult()->GetResponseBody().good() &&
httpOutcome.GetResult()->GetResponseBody().tellp() > 0)
{
return TracingUtils::MakeCallWithTiming<XmlOutcome>(
[&]() -> XmlOutcome {
XmlDocument xmlDoc = XmlDocument::CreateFromXmlStream(httpOutcome.GetResult()->GetResponseBody());
if (!xmlDoc.WasParseSuccessful())
{
AWS_LOGSTREAM_ERROR("XmlOutcomeSerializer", "Xml parsing for error failed with message " << xmlDoc.GetErrorMessage().c_str());
return AWSError(CoreErrors::UNKNOWN,
"Xml Parse Error",
xmlDoc.GetErrorMessage(),
false);
}
return {XmlServiceResult(std::move(xmlDoc),
httpOutcome.GetResult()->GetHeaders(),
httpOutcome.GetResult()->GetResponseCode())};
},
TracingUtils::SMITHY_CLIENT_DESERIALIZATION_METRIC,
*m_telemetryProvider->getMeter(serviceName, {}),
{{TracingUtils::SMITHY_METHOD_DIMENSION, requestName}, {TracingUtils::SMITHY_SERVICE_DIMENSION, serviceName}});
}
return {XmlServiceResult(XmlDocument(), httpOutcome.GetResult()->GetHeaders())};
}
private:
std::shared_ptr<TelemetryProvider> m_telemetryProvider;
};
} // namespace client
} // namespace smithy