userver: samples/http_caching/http_caching.cpp
Loading...
Searching...
No Matches
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, const components::ComponentContext& context);
~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,
) 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,
) {
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, const components::ComponentContext& context)
: HttpHandlerBase(config, context), cache_(context.FindComponent<HttpCachedTranslations>()) {}
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"});
request.GetHttpResponse().SetContentType(http::content_type::kTextPlain);
return fmt::format("{}, {}! {}", hello, request.GetArg("username"), welcome);
}
private:
samples::http_cache::HttpCachedTranslations& cache_;
};
yaml_config::Schema HttpCachedTranslations::GetStaticConfigSchema() {
return yaml_config::MergeSchemas<components::CachingComponentBase<KeyLangToTranslation>>(R"(
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 = components::MinimalServerComponentList()
.Append<samples::http_cache::HttpCachedTranslations>()
.Append<samples::http_cache::GreetUser>()
.Append<server::handlers::TestsControl>()
.Append<components::HttpClient>();
return utils::DaemonMain(argc, argv, component_list);
}