Skip to content

Commit ba5c5ae

Browse files
authored
feat: Add support for redirection requests. (#31)
1 parent 64b8aaf commit ba5c5ae

File tree

11 files changed

+373
-104
lines changed

11 files changed

+373
-104
lines changed

apps/hello-cpp/main.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
#include <boost/asio/io_context.hpp>
55

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

3635
Client client(
3736
ConfigBuilder(key)
37+
.ServiceEndpoints(
38+
launchdarkly::client_side::EndpointsBuilder()
39+
// Set to http to demonstrate redirect to https.
40+
.PollingBaseUrl("http://sdk.launchdarkly.com")
41+
.StreamingBaseUrl("https://stream.launchdarkly.com")
42+
.EventsBaseUrl("https://events.launchdarkly.com"))
3843
.DataSource(DataSourceBuilder()
3944
.Method(DataSourceBuilder::Polling().PollInterval(
4045
std::chrono::seconds{30}))

libs/client-sdk/src/api.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,10 @@ static std::unique_ptr<IDataSource> MakeDataSource(
2323
return std::make_unique<launchdarkly::client_side::data_sources::
2424
detail::StreamingDataSource>(
2525
config, executor, context, &flag_updater, status_manager, logger);
26-
} else {
27-
return std::make_unique<
28-
launchdarkly::client_side::data_sources::detail::PollingDataSource>(
29-
config, executor, context, &flag_updater, status_manager, logger);
3026
}
27+
return std::make_unique<
28+
launchdarkly::client_side::data_sources::detail::PollingDataSource>(
29+
config, executor, context, &flag_updater, status_manager, logger);
3130
}
3231

3332
Client::Client(Config config, Context context)
@@ -48,8 +47,6 @@ Client::Client(Config config, Context context)
4847
status_manager_,
4948
logger_)),
5049
initialized_(false) {
51-
data_source_->Start();
52-
5350
status_manager_.OnDataSourceStatusChange([this](auto status) {
5451
if (status.State() == DataSourceStatus::DataSourceState::kValid ||
5552
status.State() == DataSourceStatus::DataSourceState::kShutdown ||
@@ -62,6 +59,9 @@ Client::Client(Config config, Context context)
6259
}
6360
});
6461

62+
// Should listen to status before attempting to start.
63+
data_source_->Start();
64+
6565
run_thread_ = std::move(std::thread([&]() { ioc_.run(); }));
6666
}
6767

libs/client-sdk/src/data_sources/polling_data_source.cpp

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@
99

1010
namespace launchdarkly::client_side::data_sources::detail {
1111

12+
static char const* const kCouldNotParseEndpoint =
13+
"Could not parse polling endpoint URL.";
14+
1215
static network::detail::HttpRequest MakeRequest(Config const& config,
1316
Context const& context) {
14-
std::string url = config.ServiceEndpoints().PollingBaseUrl();
17+
auto url = std::make_optional(config.ServiceEndpoints().PollingBaseUrl());
1518

16-
auto& data_source_config = config.DataSourceConfig();
19+
auto const& data_source_config = config.DataSourceConfig();
1720

18-
auto& polling_config = boost::get<
21+
auto const& polling_config = boost::get<
1922
config::detail::built::PollingConfig<config::detail::ClientSDK>>(
2023
config.DataSourceConfig().method);
2124

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

3033
if (data_source_config.use_report) {
31-
url.append(polling_config.polling_report_path);
34+
url =
35+
network::detail::AppendUrl(url, polling_config.polling_report_path);
3236
method = network::detail::HttpMethod::kReport;
3337
body = string_context;
3438
} else {
35-
url.append(polling_config.polling_get_path);
39+
url = network::detail::AppendUrl(url, polling_config.polling_get_path);
3640
// When not using 'REPORT' we need to base64
3741
// encode the context so that we can safely
3842
// put it in a url.
39-
url.append("/" + Base64UrlEncode(string_context));
43+
url = network::detail::AppendUrl(url, Base64UrlEncode(string_context));
4044
}
4145

4246
if (data_source_config.with_reasons) {
43-
url.append("?withReasons=true");
47+
// TODO: Handle better.
48+
if (url) {
49+
url->append("?withReasons=true");
50+
}
4451
}
4552

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

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

51-
return {url, method, builder.Build(), body};
58+
// If no URL is set, then we will fail the request.
59+
return {url.value_or(""), method, builder.Build(), body};
5260
}
5361

5462
PollingDataSource::PollingDataSource(Config const& config,
@@ -70,7 +78,7 @@ PollingDataSource::PollingDataSource(Config const& config,
7078
config.DataSourceConfig().method)
7179
.poll_interval),
7280
request_(MakeRequest(config, context)) {
73-
auto& polling_config = boost::get<
81+
auto const& polling_config = boost::get<
7482
config::detail::built::PollingConfig<config::detail::ClientSDK>>(
7583
config.DataSourceConfig().method);
7684
if (polling_interval_ < polling_config.min_polling_interval) {
@@ -147,7 +155,6 @@ void PollingDataSource::DoPoll() {
147155
}
148156

149157
void PollingDataSource::StartPollingTimer() {
150-
// TODO: Calculate interval based on request time.
151158
auto time_since_poll_seconds =
152159
std::chrono::duration_cast<std::chrono::seconds>(
153160
std::chrono::system_clock::now() - last_poll_start_);
@@ -181,6 +188,17 @@ void PollingDataSource::StartPollingTimer() {
181188
}
182189

183190
void PollingDataSource::Start() {
191+
if (!request_.Valid()) {
192+
LD_LOG(logger_, LogLevel::kError) << kCouldNotParseEndpoint;
193+
status_manager_.SetState(
194+
DataSourceStatus::DataSourceState::kShutdown,
195+
DataSourceStatus::ErrorInfo::ErrorKind::kNetworkError,
196+
kCouldNotParseEndpoint);
197+
198+
// No need to attempt to poll if the URL is not valid.
199+
return;
200+
}
201+
184202
DoPoll();
185203
}
186204

libs/client-sdk/src/data_sources/streaming_data_source.cpp

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@
1010
#include "context_builder.hpp"
1111
#include "launchdarkly/client_side/data_sources/detail/base_64.hpp"
1212
#include "launchdarkly/client_side/data_sources/detail/streaming_data_source.hpp"
13+
#include "network/detail/http_requester.hpp"
1314
#include "serialization/json_context.hpp"
1415

1516
namespace launchdarkly::client_side::data_sources::detail {
1617

18+
static char const* const kCouldNotParseEndpoint =
19+
"Could not parse streaming endpoint URL.";
20+
1721
StreamingDataSource::StreamingDataSource(
1822
Config const& config,
1923
boost::asio::any_io_executor ioc,
@@ -25,13 +29,6 @@ StreamingDataSource::StreamingDataSource(
2529
status_manager_(status_manager),
2630
data_source_handler_(
2731
DataSourceEventHandler(handler, logger, status_manager_)) {
28-
auto uri_components =
29-
boost::urls::parse_uri(config.ServiceEndpoints().StreamingBaseUrl());
30-
31-
// TODO: Handle parsing error?
32-
// TODO: Initial reconnect delay.
33-
boost::urls::url url = uri_components.value();
34-
3532
auto string_context =
3633
boost::json::serialize(boost::json::value_from(context));
3734

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

44-
// Add the eval endpoint.
45-
url.set_path(url.path().append(streaming_config.streaming_path));
41+
auto updated_url =
42+
network::detail::AppendUrl(config.ServiceEndpoints().StreamingBaseUrl(),
43+
streaming_config.streaming_path);
4644

4745
if (!data_source_config.use_report) {
4846
// When not using 'REPORT' we need to base64
4947
// encode the context so that we can safely
5048
// put it in a url.
51-
url.set_path(url.path().append("/" + Base64UrlEncode(string_context)));
49+
updated_url = network::detail::AppendUrl(
50+
updated_url, Base64UrlEncode(string_context));
51+
}
52+
// Bad URL, don't set the client. Start will then report the bad status.
53+
if (!updated_url) {
54+
return;
5255
}
56+
57+
auto uri_components = boost::urls::parse_uri(*updated_url);
58+
59+
// Unlikely that it could be parsed earlier and it cannot be parsed now.
60+
if (!uri_components) {
61+
return;
62+
}
63+
64+
// TODO: Initial reconnect delay.
65+
boost::urls::url url = uri_components.value();
66+
5367
if (data_source_config.with_reasons) {
5468
url.params().set("withReasons", "true");
5569
}
@@ -86,12 +100,22 @@ StreamingDataSource::StreamingDataSource(
86100
}
87101

88102
void StreamingDataSource::Start() {
103+
if (!client_) {
104+
LD_LOG(logger_, LogLevel::kError) << kCouldNotParseEndpoint;
105+
status_manager_.SetState(
106+
DataSourceStatus::DataSourceState::kShutdown,
107+
DataSourceStatus::ErrorInfo::ErrorKind::kNetworkError,
108+
kCouldNotParseEndpoint);
109+
return;
110+
}
89111
client_->run();
90112
}
91113

92114
void StreamingDataSource::Close() {
93115
status_manager_.SetState(DataSourceStatus::DataSourceState::kShutdown);
94-
client_->close();
116+
if (client_) {
117+
client_->close();
118+
}
95119
}
96120

97121
} // namespace launchdarkly::client_side::data_sources::detail

libs/common/include/config/detail/built/data_source_config.hpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ struct StreamingConfig;
1414
template <>
1515
struct StreamingConfig<ClientSDK> {
1616
std::chrono::milliseconds initial_reconnect_delay;
17-
18-
inline static const std::string streaming_path = "/meval";
17+
std::string streaming_path;
1918
};
2019

2120
template <>
@@ -29,13 +28,9 @@ struct PollingConfig;
2928
template <>
3029
struct PollingConfig<ClientSDK> {
3130
std::chrono::seconds poll_interval;
32-
33-
inline const static std::string polling_get_path = "/msdk/evalx/contexts";
34-
35-
inline const static std::string polling_report_path = "/msdk/evalx/context";
36-
37-
inline const static std::chrono::seconds min_polling_interval =
38-
std::chrono::seconds{30};
31+
std::string polling_get_path;
32+
std::string polling_report_path;
33+
std::chrono::seconds min_polling_interval;
3934
};
4035

4136
template <>

libs/common/include/config/detail/defaults.hpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ struct Defaults<ClientSDK> {
4747
}
4848

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

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

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

0 commit comments

Comments
 (0)