Skip to content

feat: Add support for redirection requests. #31

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
May 1, 2023
7 changes: 6 additions & 1 deletion apps/hello-cpp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

#include <boost/asio/io_context.hpp>

#include "config/detail/builders/data_source_builder.hpp"
#include "console_backend.hpp"
#include "context_builder.hpp"
#include "launchdarkly/client_side/data_sources/detail/streaming_data_source.hpp"
Expand Down Expand Up @@ -35,6 +34,12 @@ int main() {

Client client(
ConfigBuilder(key)
.ServiceEndpoints(
launchdarkly::client_side::EndpointsBuilder()
// Set to http to demonstrate redirect to https.
.PollingBaseUrl("http://sdk.launchdarkly.com")
.StreamingBaseUrl("https://stream.launchdarkly.com")
.EventsBaseUrl("https://events.launchdarkly.com"))
.DataSource(DataSourceBuilder()
.Method(DataSourceBuilder::Polling().PollInterval(
std::chrono::seconds{30}))
Expand Down
12 changes: 6 additions & 6 deletions libs/client-sdk/src/api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ static std::unique_ptr<IDataSource> MakeDataSource(
return std::make_unique<launchdarkly::client_side::data_sources::
detail::StreamingDataSource>(
config, executor, context, &flag_updater, status_manager, logger);
} else {
return std::make_unique<
launchdarkly::client_side::data_sources::detail::PollingDataSource>(
config, executor, context, &flag_updater, status_manager, logger);
}
return std::make_unique<
launchdarkly::client_side::data_sources::detail::PollingDataSource>(
config, executor, context, &flag_updater, status_manager, logger);
}

Client::Client(Config config, Context context)
Expand All @@ -48,8 +47,6 @@ Client::Client(Config config, Context context)
status_manager_,
logger_)),
initialized_(false) {
data_source_->Start();

status_manager_.OnDataSourceStatusChange([this](auto status) {
if (status.State() == DataSourceStatus::DataSourceState::kValid ||
status.State() == DataSourceStatus::DataSourceState::kShutdown ||
Expand All @@ -62,6 +59,9 @@ Client::Client(Config config, Context context)
}
});

// Should listen to status before attempting to start.
data_source_->Start();

run_thread_ = std::move(std::thread([&]() { ioc_.run(); }));
}

Expand Down
38 changes: 28 additions & 10 deletions libs/client-sdk/src/data_sources/polling_data_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@

namespace launchdarkly::client_side::data_sources::detail {

static char const* const kCouldNotParseEndpoint =
"Could not parse polling endpoint URL.";

static network::detail::HttpRequest MakeRequest(Config const& config,
Context const& context) {
std::string url = config.ServiceEndpoints().PollingBaseUrl();
auto url = std::make_optional(config.ServiceEndpoints().PollingBaseUrl());

auto& data_source_config = config.DataSourceConfig();
auto const& data_source_config = config.DataSourceConfig();

auto& polling_config = boost::get<
auto const& polling_config = boost::get<
config::detail::built::PollingConfig<config::detail::ClientSDK>>(
config.DataSourceConfig().method);

Expand All @@ -28,27 +31,32 @@ static network::detail::HttpRequest MakeRequest(Config const& config,
network::detail::HttpMethod method = network::detail::HttpMethod::kGet;

if (data_source_config.use_report) {
url.append(polling_config.polling_report_path);
url =
network::detail::AppendUrl(url, polling_config.polling_report_path);
method = network::detail::HttpMethod::kReport;
body = string_context;
} else {
url.append(polling_config.polling_get_path);
url = network::detail::AppendUrl(url, polling_config.polling_get_path);
// When not using 'REPORT' we need to base64
// encode the context so that we can safely
// put it in a url.
url.append("/" + Base64UrlEncode(string_context));
url = network::detail::AppendUrl(url, Base64UrlEncode(string_context));
}

if (data_source_config.with_reasons) {
url.append("?withReasons=true");
// TODO: Handle better.
if (url) {
url->append("?withReasons=true");
}
}

config::detail::builders::HttpPropertiesBuilder<config::detail::ClientSDK>
builder(config.HttpProperties());

builder.Header("authorization", config.SdkKey());

return {url, method, builder.Build(), body};
// If no URL is set, then we will fail the request.
return {url.value_or(""), method, builder.Build(), body};
}

PollingDataSource::PollingDataSource(Config const& config,
Expand All @@ -70,7 +78,7 @@ PollingDataSource::PollingDataSource(Config const& config,
config.DataSourceConfig().method)
.poll_interval),
request_(MakeRequest(config, context)) {
auto& polling_config = boost::get<
auto const& polling_config = boost::get<
config::detail::built::PollingConfig<config::detail::ClientSDK>>(
config.DataSourceConfig().method);
if (polling_interval_ < polling_config.min_polling_interval) {
Expand Down Expand Up @@ -147,7 +155,6 @@ void PollingDataSource::DoPoll() {
}

void PollingDataSource::StartPollingTimer() {
// TODO: Calculate interval based on request time.
auto time_since_poll_seconds =
std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now() - last_poll_start_);
Expand Down Expand Up @@ -181,6 +188,17 @@ void PollingDataSource::StartPollingTimer() {
}

void PollingDataSource::Start() {
if (!request_.Valid()) {
LD_LOG(logger_, LogLevel::kError) << kCouldNotParseEndpoint;
status_manager_.SetState(
DataSourceStatus::DataSourceState::kShutdown,
DataSourceStatus::ErrorInfo::ErrorKind::kNetworkError,
kCouldNotParseEndpoint);

// No need to attempt to poll if the URL is not valid.
return;
}

DoPoll();
}

Expand Down
46 changes: 35 additions & 11 deletions libs/client-sdk/src/data_sources/streaming_data_source.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,14 @@
#include "context_builder.hpp"
#include "launchdarkly/client_side/data_sources/detail/base_64.hpp"
#include "launchdarkly/client_side/data_sources/detail/streaming_data_source.hpp"
#include "network/detail/http_requester.hpp"
#include "serialization/json_context.hpp"

namespace launchdarkly::client_side::data_sources::detail {

static char const* const kCouldNotParseEndpoint =
"Could not parse streaming endpoint URL.";

StreamingDataSource::StreamingDataSource(
Config const& config,
boost::asio::any_io_executor ioc,
Expand All @@ -25,13 +29,6 @@ StreamingDataSource::StreamingDataSource(
status_manager_(status_manager),
data_source_handler_(
DataSourceEventHandler(handler, logger, status_manager_)) {
auto uri_components =
boost::urls::parse_uri(config.ServiceEndpoints().StreamingBaseUrl());

// TODO: Handle parsing error?
// TODO: Initial reconnect delay.
boost::urls::url url = uri_components.value();

auto string_context =
boost::json::serialize(boost::json::value_from(context));

Expand All @@ -41,15 +38,32 @@ StreamingDataSource::StreamingDataSource(
config::detail::built::StreamingConfig<config::detail::ClientSDK>>(
data_source_config.method);

// Add the eval endpoint.
url.set_path(url.path().append(streaming_config.streaming_path));
auto updated_url =
network::detail::AppendUrl(config.ServiceEndpoints().StreamingBaseUrl(),
streaming_config.streaming_path);

if (!data_source_config.use_report) {
// When not using 'REPORT' we need to base64
// encode the context so that we can safely
// put it in a url.
url.set_path(url.path().append("/" + Base64UrlEncode(string_context)));
updated_url = network::detail::AppendUrl(
updated_url, Base64UrlEncode(string_context));
}
// Bad URL, don't set the client. Start will then report the bad status.
if (!updated_url) {
return;
}

auto uri_components = boost::urls::parse_uri(*updated_url);

// Unlikely that it could be parsed earlier and it cannot be parsed now.
if (!uri_components) {
return;
}

// TODO: Initial reconnect delay.
boost::urls::url url = uri_components.value();

if (data_source_config.with_reasons) {
url.params().set("withReasons", "true");
}
Expand Down Expand Up @@ -86,12 +100,22 @@ StreamingDataSource::StreamingDataSource(
}

void StreamingDataSource::Start() {
if (!client_) {
LD_LOG(logger_, LogLevel::kError) << kCouldNotParseEndpoint;
status_manager_.SetState(
DataSourceStatus::DataSourceState::kShutdown,
DataSourceStatus::ErrorInfo::ErrorKind::kNetworkError,
kCouldNotParseEndpoint);
return;
}
client_->run();
}

void StreamingDataSource::Close() {
status_manager_.SetState(DataSourceStatus::DataSourceState::kShutdown);
client_->close();
if (client_) {
client_->close();
}
}

} // namespace launchdarkly::client_side::data_sources::detail
13 changes: 4 additions & 9 deletions libs/common/include/config/detail/built/data_source_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ struct StreamingConfig;
template <>
struct StreamingConfig<ClientSDK> {
std::chrono::milliseconds initial_reconnect_delay;

inline static const std::string streaming_path = "/meval";
std::string streaming_path;
};

template <>
Expand All @@ -29,13 +28,9 @@ struct PollingConfig;
template <>
struct PollingConfig<ClientSDK> {
std::chrono::seconds poll_interval;

inline const static std::string polling_get_path = "/msdk/evalx/contexts";

inline const static std::string polling_report_path = "/msdk/evalx/context";

inline const static std::chrono::seconds min_polling_interval =
std::chrono::seconds{30};
std::string polling_get_path;
std::string polling_report_path;
std::chrono::seconds min_polling_interval;
};

template <>
Expand Down
5 changes: 3 additions & 2 deletions libs/common/include/config/detail/defaults.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ struct Defaults<ClientSDK> {
}

static auto StreamingConfig() -> built::StreamingConfig<ClientSDK> {
return {std::chrono::milliseconds{1000}};
return {std::chrono::milliseconds{1000}, "/meval"};
}

static auto DataSourceConfig() -> built::DataSourceConfig<ClientSDK> {
Expand All @@ -56,7 +56,8 @@ struct Defaults<ClientSDK> {

static auto PollingConfig() -> built::PollingConfig<ClientSDK> {
// Default to 5 minutes;
return {std::chrono::seconds{5 * 60}};
return {std::chrono::seconds{5 * 60}, "/msdk/evalx/contexts",
"/msdk/evalx/context", std::chrono::seconds{30}};
}
};

Expand Down
Loading