userver: samples/http_caching/http_caching.cpp
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
samples/http_caching/http_caching.cpp
namespace samples::http_cache {
struct KeyLang {
std::string key;
std::string language;
};
struct KeyLangEq {
bool operator()(const KeyLang& x, const KeyLang& y) const noexcept;
};
struct KeyLangHash {
bool operator()(const KeyLang& x) const noexcept;
};
using KeyLangToTranslation =
std::unordered_map<KeyLang, std::string, KeyLangHash, KeyLangEq>;
} // namespace samples::http_cache
namespace samples::http_cache {
bool KeyLangEq::operator()(const KeyLang& x, const KeyLang& y) const noexcept {
return x.key == y.key && x.language == y.language;
}
bool KeyLangHash::operator()(const KeyLang& x) const noexcept {
std::string data;
data.append(x.key);
data.append(x.language);
return std::hash<std::string>{}(data);
}
} // namespace samples::http_cache
namespace samples::http_cache {
class HttpCachedTranslations final
: public components::CachingComponentBase<KeyLangToTranslation> {
public:
static constexpr std::string_view kName = "cache-http-translations";
HttpCachedTranslations(const components::ComponentConfig& config,
~HttpCachedTranslations() override;
void Update(
[[maybe_unused]] const std::chrono::system_clock::time_point& last_update,
[[maybe_unused]] const std::chrono::system_clock::time_point& now,
cache::UpdateStatisticsScope& stats_scope) override;
static yaml_config::Schema GetStaticConfigSchema();
private:
clients::http::Client& http_client_;
const std::string translations_url_;
std::string last_update_remote_;
formats::json::Value GetAllData() const;
formats::json::Value GetUpdatedData() const;
void MergeAndSetData(KeyLangToTranslation&& content,
};
} // namespace samples::http_cache
namespace samples::http_cache {
HttpCachedTranslations::HttpCachedTranslations(
: CachingComponentBase(config, context),
http_client_(
context.FindComponent<components::HttpClient>().GetHttpClient()),
translations_url_(config["translations-url"].As<std::string>()) {
CacheUpdateTrait::StartPeriodicUpdates();
}
HttpCachedTranslations::~HttpCachedTranslations() {
CacheUpdateTrait::StopPeriodicUpdates();
}
void HttpCachedTranslations::Update(
[[maybe_unused]] const std::chrono::system_clock::time_point& last_update,
[[maybe_unused]] const std::chrono::system_clock::time_point& now,
switch (type) {
json = GetAllData();
break;
json = GetUpdatedData();
break;
default:
UASSERT(false);
}
if (json.IsEmpty()) {
stats_scope.FinishNoChanges();
return;
}
KeyLangToTranslation content;
const auto snapshot = Get(); // smart pointer to a shared cache data
content = *snapshot; // copying the shared data
}
MergeAndSetData(std::move(content), json, stats_scope);
}
formats::json::Value HttpCachedTranslations::GetAllData() const {
auto response = http_client_.CreateRequest()
.get(translations_url_) // HTTP GET translations_url_ URL
.retry(2) // retry once in case of error
.timeout(std::chrono::milliseconds{500})
.perform(); // start performing the request
response->raise_for_status();
return formats::json::FromString(response->body_view());
}
formats::json::Value HttpCachedTranslations::GetUpdatedData() const {
const auto url =
http::MakeUrl(translations_url_, {{"last_update", last_update_remote_}});
auto response = http_client_.CreateRequest()
.get(url)
.retry(2)
.timeout(std::chrono::milliseconds{500})
.perform();
response->raise_for_status();
return formats::json::FromString(response->body_view());
}
void HttpCachedTranslations::MergeAndSetData(
KeyLangToTranslation&& content, formats::json::Value json,
for (const auto& [key, value] : Items(json["content"])) {
for (const auto& [lang, text] : Items(value)) {
content.insert_or_assign(KeyLang{key, lang}, text.As<std::string>());
}
stats_scope.IncreaseDocumentsReadCount(value.GetSize());
}
auto update_time = json["update_time"].As<std::string>();
const auto size = content.size();
Set(std::move(content));
last_update_remote_ = std::move(update_time);
stats_scope.Finish(size);
}
class GreetUser final : public server::handlers::HttpHandlerBase {
public:
static constexpr std::string_view kName = "handler-greet-user";
GreetUser(const components::ComponentConfig& config,
: HttpHandlerBase(config, context),
cache_(context.FindComponent<HttpCachedTranslations>()) {}
std::string HandleRequestThrow(
const server::http::HttpRequest& request,
const auto cache_snapshot = cache_.Get();
using samples::http_cache::KeyLang;
const auto& hello = cache_snapshot->at(KeyLang{"hello", "ru"});
const auto& welcome = cache_snapshot->at(KeyLang{"welcome", "ru"});
return fmt::format("{}, {}! {}", hello, request.GetArg("username"),
welcome);
}
private:
samples::http_cache::HttpCachedTranslations& cache_;
};
yaml_config::Schema HttpCachedTranslations::GetStaticConfigSchema() {
type: object
description: HTTP caching sample component
additionalProperties: false
properties:
translations-url:
type: string
description: some other microservice listens on this URL
)");
}
} // namespace samples::http_cache
int main(int argc, char* argv[]) {
const auto component_list =
.Append<samples::http_cache::HttpCachedTranslations>()
.Append<samples::http_cache::GreetUser>()
.Append<server::handlers::TestsControl>()
.Append<components::HttpClient>();
return utils::DaemonMain(argc, argv, component_list);
}