Http request counter (#1738)

* Add counter to request about the retry number using the Context
This commit is contained in:
Victor Vazquez 2021-03-03 20:51:19 +00:00 committed by GitHub
parent d782c99777
commit a5ff474118
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 165 additions and 7 deletions

View File

@ -14,6 +14,7 @@
- Moved `NullBodyStream` to internal usage only. It is not meant for public use.
- Removed `LimitBodyStream`.
- Introduced `Azure::Core::CaseInsensitiveMap` which is now used to store headers in `Azure::Core::Http::Request` and `Azure::Core::Http::RawResponse`.
- Removed `StartTry()` from `Azure::Core::Http::Request`.
### Bug Fixes

View File

@ -29,6 +29,9 @@
namespace Azure { namespace Core { namespace Test {
class TestHttp_getters_Test;
class TestHttp_query_parameter_Test;
class TestHttp_RequestStartTry_Test;
class TestURL_getters_Test;
class TestURL_query_parameter_Test;
}}} // namespace Azure::Core::Test
#endif
@ -445,6 +448,9 @@ namespace Azure { namespace Core { namespace Http {
// make tests classes friends to validate set Retry
friend class Azure::Core::Test::TestHttp_getters_Test;
friend class Azure::Core::Test::TestHttp_query_parameter_Test;
friend class Azure::Core::Test::TestHttp_RequestStartTry_Test;
friend class Azure::Core::Test::TestURL_getters_Test;
friend class Azure::Core::Test::TestURL_query_parameter_Test;
#endif
private:
@ -464,6 +470,10 @@ namespace Azure { namespace Core { namespace Http {
// adapter will decide chunk size.
int64_t m_uploadChunkSize = 0;
// Expected to be called by a Retry policy to reset all headers set after this function was
// previously called
void StartTry();
public:
/**
* @brief Construct an #Azure::Core::Http::Request.
@ -583,9 +593,6 @@ namespace Azure { namespace Core { namespace Http {
* @brief Get URL.
*/
Url const& GetUrl() const { return this->m_url; }
// Expected to be called by a Retry policy to reset all headers set after this function was
// previously called
void StartTry();
};
/**

View File

@ -216,6 +216,19 @@ namespace Azure { namespace Core { namespace Http {
Context const& ctx,
Request& request,
NextHttpPolicy nextHttpPolicy) const override;
/**
* @brief Get the Retry Count from the context.
*
* @remark The sentinel `-1` is returned if there is no information in the \p Context about
* #RetryPolicy is trying to send a request. Then `0` is returned for the first try of sending a
* request by the #RetryPolicy. Any subsequent retry will be referenced with a number greater
* than 0.
*
* @param context The context used to call send request.
* @return A positive number indicating the current intent to send the request.
*/
static int GetRetryNumber(Context const& context);
};
/**

View File

@ -122,15 +122,49 @@ bool ShouldRetryOnResponse(
return true;
}
static constexpr char RetryKey[] = "AzureSdkRetryPolicyCounter";
/**
* @brief Creates a new #Context node from \p parent with the information about the retrying while
* sending an Http request.
*
* @param parent The parent context for the new created.
* @return Context with information about retry counter.
*/
Context inline CreateRetryContext(Context const& parent)
{
// First try as default
int retryCount = 0;
if (parent.HasKey(RetryKey))
{
retryCount = parent[RetryKey].Get<int>() + 1;
}
return parent.WithValue(RetryKey, retryCount);
}
} // namespace
int Azure::Core::Http::RetryPolicy::GetRetryNumber(Context const& context)
{
if (!context.HasKey(RetryKey))
{
// Context with no data abut sending request with retry policy = -1
// First try = 0
// Second try = 1
// third try = 2
// ...
return -1;
}
return context[RetryKey].Get<int>();
}
std::unique_ptr<RawResponse> Azure::Core::Http::RetryPolicy::Send(
Context const& ctx,
Request& request,
NextHttpPolicy nextHttpPolicy) const
{
auto const shouldLog = Logging::Internal::ShouldLog(Logging::LogLevel::Informational);
auto retryContext = CreateRetryContext(ctx);
for (RetryNumber attempt = 1;; ++attempt)
{
Delay retryAfter{};
@ -140,7 +174,7 @@ std::unique_ptr<RawResponse> Azure::Core::Http::RetryPolicy::Send(
try
{
auto response = nextHttpPolicy.Send(ctx, request);
auto response = nextHttpPolicy.Send(retryContext, request);
// If we are out of retry attempts, if a response is non-retriable (or simply 200 OK, i.e
// doesn't need to be retried), then ShouldRetry returns false.
@ -179,5 +213,8 @@ std::unique_ptr<RawResponse> Azure::Core::Http::RetryPolicy::Send(
// Restore the original query parameters before next retry
request.GetUrl().SetQueryParameters(std::move(originalQueryParameters));
// Update retry number
retryContext = CreateRetryContext(retryContext);
}
}

View File

@ -23,6 +23,58 @@ public:
return nullptr;
}
};
// A policy to test retry state
static int retryCounterState = 0;
struct TestRetryPolicySharedState : public Azure::Core::Http::HttpPolicy
{
std::unique_ptr<HttpPolicy> Clone() const override
{
return std::make_unique<TestRetryPolicySharedState>(*this);
}
std::unique_ptr<Azure::Core::Http::RawResponse> Send(
Azure::Core::Context const& ctx,
Azure::Core::Http::Request& request,
Azure::Core::Http::NextHttpPolicy nextHttpPolicy) const override
{
EXPECT_EQ(retryCounterState, Azure::Core::Http::RetryPolicy::GetRetryNumber(ctx));
retryCounterState += 1;
return nextHttpPolicy.Send(ctx, request);
}
};
class SuccessAfter : public Azure::Core::Http::HttpPolicy {
private:
int m_successAfter; // Always success
public:
std::unique_ptr<Azure::Core::Http::HttpPolicy> Clone() const override
{
return std::make_unique<SuccessAfter>(*this);
}
SuccessAfter(int successAfter = 1) : m_successAfter(successAfter) {}
std::unique_ptr<Azure::Core::Http::RawResponse> Send(
Azure::Core::Context const& context,
Azure::Core::Http::Request&,
Azure::Core::Http::NextHttpPolicy) const override
{
auto retryNumber = Azure::Core::Http::RetryPolicy::GetRetryNumber(context);
if (retryNumber == m_successAfter)
{
auto response = std::make_unique<Azure::Core::Http::RawResponse>(
1, 1, Azure::Core::Http::HttpStatusCode::Ok, "All Fine");
return response;
}
auto retryResponse = std::make_unique<Azure::Core::Http::RawResponse>(
1, 1, Azure::Core::Http::HttpStatusCode::ServiceUnavailable, "retry please :)");
return retryResponse;
}
};
} // namespace
TEST(Policy, throwWhenNoTransportPolicy)
@ -88,3 +140,44 @@ TEST(Policy, ValuePolicy)
ASSERT_EQ(headers, decltype(headers)({{"hdrkey1", "HdrVal1"}, {"hdrkey2", "HdrVal2"}}));
ASSERT_EQ(queryParams, decltype(queryParams)({{"QryKey1", "QryVal1"}, {"QryKey2", "QryVal2"}}));
}
TEST(Policy, RetryPolicyCounter)
{
using namespace Azure::Core;
using namespace Azure::Core::Http;
using namespace Azure::Core::Internal::Http;
// Check when there's no info about retry on the context
auto initialContext = GetApplicationContext();
EXPECT_EQ(-1, RetryPolicy::GetRetryNumber(initialContext));
// Pipeline with retry test
std::vector<std::unique_ptr<HttpPolicy>> policies;
RetryOptions opt;
policies.push_back(std::make_unique<RetryPolicy>(opt));
policies.push_back(std::make_unique<TestRetryPolicySharedState>());
policies.push_back(std::make_unique<SuccessAfter>());
HttpPipeline pipeline(policies);
Request request(HttpMethod::Get, Url("url"));
pipeline.Send(initialContext, request);
}
TEST(Policy, RetryPolicyRetryCycle)
{
using namespace Azure::Core;
using namespace Azure::Core::Http;
using namespace Azure::Core::Internal::Http;
// Pipeline with retry test
std::vector<std::unique_ptr<HttpPolicy>> policies;
RetryOptions opt;
opt.RetryDelay = std::chrono::milliseconds(10);
policies.push_back(std::make_unique<RetryPolicy>(opt));
policies.push_back(std::make_unique<TestRetryPolicySharedState>());
policies.push_back(std::make_unique<SuccessAfter>(3));
HttpPipeline pipeline(policies);
Request request(HttpMethod::Get, Url("url"));
pipeline.Send(GetApplicationContext(), request);
}

View File

@ -5,11 +5,18 @@
#include <azure/core/http/http.hpp>
namespace Azure { namespace Core { namespace Test {
class TestURL : public ::testing::Test {
};
}}} // namespace Azure::Core::Test
using namespace Azure::Core;
namespace Azure { namespace Core { namespace Test {
TEST(URL, getters)
TEST(TestURL, getters)
{
Http::HttpMethod httpMethod = Http::HttpMethod::Get;
Http::Url url("http://test.url.com");
@ -79,7 +86,7 @@ namespace Azure { namespace Core { namespace Test {
EXPECT_FALSE(headers.count("newheader"));
}
TEST(URL, query_parameter)
TEST(TestURL, query_parameter)
{
Http::HttpMethod httpMethod = Http::HttpMethod::Put;
Http::Url url("http://test.com");