Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/client/include/RestClientNative.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -659,7 +659,11 @@
switch (cmd.command) {
case mdp::Command::Get:
case mdp::Command::Set: {
auto session = ensureSession(ssl_ctx, sessions, sslSettings, cmd.topic);
auto session = ensureSession(ssl_ctx, sessions, sslSettings, cmd.topic);
if (!session) {

Check failure on line 663 in src/client/include/RestClientNative.hpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use has_value() or another member function to clarify that the code tests the presence of a value in the "expected", not the contained "opencmw::client::detail::Http2ClientSession *" value itself.

See more on https://sonarcloud.io/project/issues?id=fair-acc_opencmw-cpp&issues=AZ_7DkhrZNVC56UOOnlk&open=AZ_7DkhrZNVC56UOOnlk&pullRequest=408

Check failure on line 663 in src/client/include/RestClientNative.hpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Refactor this code to not nest more than 3 if|for|do|while|switch statements.

See more on https://sonarcloud.io/project/issues?id=fair-acc_opencmw-cpp&issues=AZ_7DkhrZNVC56UOOnlj&open=AZ_7DkhrZNVC56UOOnlj&pullRequest=408
reportError(cmd, std::format("Could not create REST session for endpoint '{}': {}", cmd.topic.str(), session.error()));
continue;
}
auto preferred = preferredMimeType(cmd.topic);
session.value()->submitRequest(std::move(cmd), mode, std::move(preferred), {});
} break;
Expand Down
69 changes: 68 additions & 1 deletion src/client/test/nghttp2_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@
TEST_CASE("GET HTTP", "[http2]") {
using namespace opencmw::client;

auto serverThread = std::jthread([](std::stop_token stopToken) {
auto serverThread = std::jthread([](std::stop_token stopToken) {
RestServer server;
majordomo::rest::Settings settings{ .port = kServerPort, .protocols = majordomo::rest::Http2 };
REQUIRE(server.bind(settings));
Expand Down Expand Up @@ -408,6 +408,73 @@
waitFor(responseCount, 3);
}

TEST_CASE("REST client survives hostname resolution failure", "[http2]") {
std::atomic<int> responseCount = 0;
mdp::Message failedResponse;
mdp::Message successfulResponse;
client::RestClient client;

client::Command invalidRequest;
invalidRequest.command = mdp::Command::Set;
invalidRequest.clientRequestID = opencmw::IoBuffer("unresolvable");
invalidRequest.topic = URI<>("http://opencmw-rest-client-test.invalid:12345/dns");

Check warning on line 420 in src/client/test/nghttp2_tests.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Using HTTP is insecure. Use HTTPS instead.

See more on https://sonarcloud.io/project/issues?id=fair-acc_opencmw-cpp&issues=AZ_7DkifZNVC56UOOnlm&open=AZ_7DkifZNVC56UOOnlm&pullRequest=408
invalidRequest.callback = [&failedResponse, &responseCount](const mdp::Message &msg) {
failedResponse = msg;
responseCount++;
};
client.request(std::move(invalidRequest));

REQUIRE(waitFor(responseCount, 1, std::chrono::seconds(30)));
REQUIRE(failedResponse.command == mdp::Command::Final);
REQUIRE(failedResponse.topic.str().contains("opencmw-rest-client-test.invalid"));
REQUIRE(failedResponse.error.contains("opencmw-rest-client-test.invalid"));
REQUIRE(failedResponse.error.contains("Could not resolve address"));

auto serverThread = std::jthread([](std::stop_token stopToken) {
RestServer server;
majordomo::rest::Settings settings{ .port = kServerPort, .protocols = majordomo::rest::Http2 };
REQUIRE(server.bind(settings));

std::deque<Message> messages;
ensureMessageReceived(server, stopToken, messages);
REQUIRE(messages.size() >= 1);
const auto request = std::move(messages.front());
messages.pop_front();
REQUIRE(request.command == mdp::Command::Get);
REQUIRE(request.topic.path() == "/sayhello");

Message reply;
reply.command = mdp::Command::Final;
reply.clientRequestID = request.clientRequestID;
reply.topic = URI<>("/sayhello");
reply.data = opencmw::IoBuffer("worker survived");
server.handleResponse(std::move(reply));

ensureMessageReceived(server, stopToken, messages); // makes sure the response is sent
});

Stopper stopper(serverThread.get_stop_source());
std::this_thread::sleep_for(std::chrono::milliseconds(300)); // give the server some time to start listening

client::Command validRequest;
validRequest.command = mdp::Command::Get;
validRequest.clientRequestID = opencmw::IoBuffer("valid");
validRequest.topic = URI<>(std::format("http://localhost:{}/sayhello", kServerPort));
validRequest.callback = [&successfulResponse, &responseCount](const mdp::Message &msg) {
successfulResponse = msg;
responseCount++;
};
client.request(std::move(validRequest));

REQUIRE(waitFor(responseCount, 2));
INFO(successfulResponse.error);

Check warning on line 470 in src/client/test/nghttp2_tests.cpp

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Modify the macro definition so that it needs to be followed by a semicolon, or remove this empty statement.

See more on https://sonarcloud.io/project/issues?id=fair-acc_opencmw-cpp&issues=AZ_7DkifZNVC56UOOnll&open=AZ_7DkifZNVC56UOOnll&pullRequest=408
REQUIRE(successfulResponse.command == mdp::Command::Final);
REQUIRE(successfulResponse.error.empty());
REQUIRE(successfulResponse.data.asString() == "worker survived");
REQUIRE(successfulResponse.clientRequestID.asString() == "valid");
REQUIRE(successfulResponse.topic.path() == "/sayhello");
}

TEST_CASE("Long polling example", "[http2]") {
constexpr int kFooMessages = 50;

Expand Down
2 changes: 1 addition & 1 deletion src/rest/include/rest/RestUtils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ struct TcpSocket {
struct addrinfo *res;
int status = getaddrinfo(host.data(), nullptr, &hints, &res);
if (status != 0) {
return std::unexpected(std::format("Could not resolve address: {}", strerror(status)));
return std::unexpected(std::format("Could not resolve address '{}': {}", host, gai_strerror(status)));
}
address = AddrinfoPtr(res, freeaddrinfo);
reinterpret_cast<struct sockaddr_in *>(address->ai_addr)->sin_port = htons(port);
Expand Down
11 changes: 10 additions & 1 deletion src/services/include/services/dns_client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <atomic>
#include <chrono>
#include <IoSerialiserYaS.hpp>
#include <iostream>
#include <RestClient.hpp>
#include <URI.hpp>
#include <utility>
Expand All @@ -33,7 +34,9 @@ struct DnsClient {
uri = std::move(uri).setQuery(query::serialise(filter));

_clientContext.get(uri.build(), [callback = std::move(callback)](const mdp::Message &msg) {
std::cout << msg.error << std::endl;
if (!msg.error.empty()) {
std::cerr << "DNS signal query failed for '" << msg.topic.str() << "': " << msg.error << '\n';
}
IoBuffer buf{ msg.data };

FlatEntryList resp;
Expand All @@ -54,6 +57,9 @@ struct DnsClient {

_clientContext.set(
_endpoint, [callback = std::move(callback)](auto &msg) {
if (!msg.error.empty()) {
std::cerr << "DNS signal registration failed for '" << msg.topic.str() << "': " << msg.error << '\n';
}
FlatEntryList resp;
IoBuffer buf{ msg.data };
if (!buf.empty()) {
Expand All @@ -76,6 +82,9 @@ struct DnsClient {

_clientContext.set(
uri.build(), [callback = std::move(callback)](auto &msg) {
if (!msg.error.empty()) {
std::cerr << "DNS signal unregistration failed for '" << msg.topic.str() << "': " << msg.error << '\n';
}
FlatEntryList resp;
IoBuffer buf{ msg.data };
if (!buf.empty()) {
Expand Down
Loading