Skip to content

Commit 64b8aaf

Browse files
authored
feat: Support handling invalid URLs for asio requests. (#30)
1 parent 7ef503b commit 64b8aaf

File tree

9 files changed

+88
-8
lines changed

9 files changed

+88
-8
lines changed

apps/hello-cpp/main.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,13 @@ int main() {
4444
.value(),
4545
ContextBuilder().kind("user", "ryan").build());
4646

47+
LD_LOG(logger, LogLevel::kInfo)
48+
<< "Initial Status: " << client.DataSourceStatus().Status();
49+
50+
client.DataSourceStatus().OnDataSourceStatusChange([&logger](auto status) {
51+
LD_LOG(logger, LogLevel::kInfo) << "Got status: " << status;
52+
});
53+
4754
client.WaitForReadySync(std::chrono::seconds(30));
4855

4956
auto value = client.BoolVariation("my-boolean-flag", false);

libs/client-sdk/include/launchdarkly/client_side/data_sources/data_source_status.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,4 +238,10 @@ std::ostream& operator<<(std::ostream& out,
238238
std::ostream& operator<<(std::ostream& out,
239239
DataSourceStatus::ErrorInfo::ErrorKind const& kind);
240240

241+
std::ostream& operator<<(std::ostream& out,
242+
DataSourceStatus const& status);
243+
244+
std::ostream& operator<<(std::ostream& out,
245+
DataSourceStatus::ErrorInfo const& error);
246+
241247
} // namespace launchdarkly::client_side::data_sources

libs/client-sdk/include/launchdarkly/client_side/data_sources/detail/data_source_status_manager.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class DataSourceStatusManager : public IDataSourceStatusProvider {
8585

8686
boost::signals2::signal<void(data_sources::DataSourceStatus status)>
8787
data_source_status_signal_;
88-
mutable std::mutex status_mutex_;
88+
mutable std::recursive_mutex status_mutex_;
8989
bool UpdateState(DataSourceStatus::DataSourceState const& requested_state);
9090
};
9191

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11

2+
#include <iomanip>
23
#include <utility>
34

45
#include "launchdarkly/client_side/data_sources/data_source_status.hpp"
6+
57
namespace launchdarkly::client_side::data_sources {
68

79
DataSourceStatus::ErrorInfo::ErrorKind DataSourceStatus::ErrorInfo::Kind()
@@ -92,4 +94,25 @@ std::ostream& operator<<(std::ostream& out,
9294
return out;
9395
}
9496

97+
std::ostream& operator<<(std::ostream& out, DataSourceStatus const& status) {
98+
std::time_t as_time_t =
99+
std::chrono::system_clock::to_time_t(status.StateSince());
100+
out << "Status(" << status.State() << ", Since("
101+
<< std::put_time(std::gmtime(&as_time_t), "%Y-%m-%d %H:%M:%S") << ")";
102+
if (status.LastError()) {
103+
out << ", " << status.LastError().value();
104+
}
105+
out << ")";
106+
return out;
107+
}
108+
109+
std::ostream& operator<<(std::ostream& out,
110+
DataSourceStatus::ErrorInfo const& error) {
111+
std::time_t as_time_t = std::chrono::system_clock::to_time_t(error.Time());
112+
out << "Error(" << error.Kind() << ", " << error.Message()
113+
<< ", StatusCode(" << error.StatusCode() << "), Since("
114+
<< std::put_time(std::gmtime(&as_time_t), "%Y-%m-%d %H:%M:%S") << "))";
115+
return out;
116+
}
117+
95118
} // namespace launchdarkly::client_side::data_sources

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ void PollingDataSource::DoPoll() {
117117
DataSourceStatus::ErrorInfo::ErrorKind::kNetworkError,
118118
res.ErrorMessage() ? *res.ErrorMessage() : "unknown error");
119119
LD_LOG(logger_, LogLevel::kWarn)
120-
<< "Polling for feature flag updates failed:"
120+
<< "Polling for feature flag updates failed: "
121121
<< (res.ErrorMessage() ? *res.ErrorMessage() : "unknown error");
122122
} else if (res.Status() == 200) {
123123
data_source_handler_.HandleMessage("put", res.Body().value());

libs/common/include/network/detail/asio_requester.hpp

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,9 @@ class
111111
void Fail(beast::error_code ec, char const* what) {
112112
// TODO: Is it safe to cancel this if it has already failed?
113113
DoClose();
114-
handler_(HttpResult(std::string(what) + ": " + ec.message()));
114+
std::optional<std::string> error_string =
115+
std::string(what) + ": " + ec.message();
116+
handler_(HttpResult(error_string));
115117
}
116118

117119
void DoResolve() {
@@ -122,8 +124,9 @@ class
122124
}
123125

124126
void OnResolve(beast::error_code ec, tcp::resolver::results_type results) {
125-
if (ec)
127+
if (ec) {
126128
return Fail(ec, "resolve");
129+
}
127130

128131
beast::get_lowest_layer(GetDerived().Stream())
129132
.expires_after(connect_timeout_);
@@ -207,7 +210,8 @@ class
207210
headers.insert_or_assign(field.name_string(), field.value());
208211
}
209212
auto result = HttpResult(parser_.get().result_int(),
210-
parser_.get().body(), std::move(headers));
213+
std::make_optional(parser_.get().body()),
214+
std::move(headers));
211215
return result;
212216
}
213217

@@ -283,8 +287,9 @@ class EncryptedClient : public Session<EncryptedClient>,
283287

284288
DoClose();
285289
// TODO: Should this be treated as a terminal error for the request.
286-
handler_(HttpResult("failed to set TLS host name extension: " +
287-
ec.message()));
290+
std::optional<std::string> error_string =
291+
"failed to set TLS host name extension: " + ec.message();
292+
handler_(HttpResult(error_string));
288293
return;
289294
}
290295

@@ -364,6 +369,7 @@ class AsioRequester {
364369
auto Request(HttpRequest request, CompletionToken&& token) {
365370
// TODO: Clang-tidy wants to pass the request by reference, but I am not
366371
// confident that lifetime would make sense.
372+
367373
namespace asio = boost::asio;
368374
namespace system = boost::system;
369375

@@ -375,6 +381,19 @@ class AsioRequester {
375381
Result result(handler);
376382

377383
auto strand = net::make_strand(ctx_);
384+
385+
// The request is invalid and cannot be made, so produce an error
386+
// result.
387+
if (!request.Valid()) {
388+
boost::asio::post(
389+
strand, [strand, handler, request, this]() mutable {
390+
std::optional<std::string> error_string =
391+
"The request was malformed and could not be made.";
392+
handler(HttpResult(error_string));
393+
});
394+
return;
395+
}
396+
378397
boost::asio::post(strand, [strand, handler, request, this]() mutable {
379398
auto beast_request = MakeBeastRequest(request);
380399

libs/common/include/network/detail/http_requester.hpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <cstdint>
66
#include <future>
77
#include <map>
8+
#include <optional>
89
#include <ostream>
910
#include <string>
1011

@@ -92,6 +93,14 @@ class HttpRequest {
9293
std::string const& Path() const;
9394
bool Https() const;
9495

96+
/**
97+
* Indicates if a request is valid. Meaning that it has correctly formed
98+
* data that can be used to make an http request.
99+
*
100+
* @return True if the request is valid.
101+
*/
102+
bool Valid() const;
103+
95104
HttpRequest(std::string const& url,
96105
HttpMethod method,
97106
config::detail::built::HttpProperties properties,
@@ -115,6 +124,7 @@ class HttpRequest {
115124
std::string path_;
116125
std::map<std::string, std::string> params_;
117126
bool is_https_;
127+
bool valid_;
118128
};
119129

120130
bool IsRecoverableStatus(HttpResult::StatusCode status);

libs/common/src/data/evaluation_result.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "data/evaluation_result.hpp"
22

33
#include <chrono>
4+
#include <iomanip>
5+
#include <utility>
46

57
namespace launchdarkly {
68

@@ -53,7 +55,8 @@ std::ostream& operator<<(std::ostream& out, EvaluationResult const& result) {
5355
if (result.debug_events_until_date_.has_value()) {
5456
std::time_t as_time_t = std::chrono::system_clock::to_time_t(
5557
result.debug_events_until_date_.value());
56-
out << " debugEventsUntilDate: " << std::ctime(&as_time_t);
58+
out << " debugEventsUntilDate: "
59+
<< std::put_time(std::gmtime(&as_time_t), "%Y-%m-%d %H:%M:%S");
5760
}
5861
out << " detail: " << result.detail_;
5962
out << "}";

libs/common/src/network/http_requester.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ HttpRequest::HttpRequest(std::string const& url,
6666
body_(std::move(body)) {
6767
auto uri_components = boost::urls::parse_uri(url);
6868

69+
// If the URI cannot be parsed, then the request is not valid.
70+
if (!uri_components) {
71+
valid_ = false;
72+
return;
73+
}
74+
6975
host_ = uri_components->host();
7076
// For a boost beast request we need the query string in the path.
7177
path_ = uri_components->path() + "?" + uri_components->query();
@@ -79,6 +85,7 @@ HttpRequest::HttpRequest(std::string const& url,
7985
} else {
8086
port_ = is_https_ ? "443" : "80";
8187
}
88+
valid_ = true;
8289
}
8390

8491
HttpRequest::HttpRequest(HttpRequest& base_request,
@@ -90,6 +97,7 @@ HttpRequest::HttpRequest(HttpRequest& base_request,
9097
host_ = base_request.host_;
9198
port_ = base_request.port_;
9299
is_https_ = base_request.is_https_;
100+
valid_ = base_request.valid_;
93101
}
94102

95103
std::string const& HttpRequest::Port() const {
@@ -99,6 +107,10 @@ bool HttpRequest::Https() const {
99107
return is_https_;
100108
}
101109

110+
bool HttpRequest::Valid() const {
111+
return valid_;
112+
}
113+
102114
bool IsRecoverableStatus(HttpResult::StatusCode status) {
103115
return status < 400 || status > 499 || status == 400 || status == 408 ||
104116
status == 429;

0 commit comments

Comments
 (0)