Skip to content
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

Add close message support #92

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
19 changes: 17 additions & 2 deletions src/signalrclient/hub_connection_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,8 +409,23 @@ namespace signalr
}
break;
case message_type::close:
// TODO
break;
{
auto close = static_cast<close_message*>(val.get());
if (m_logger.is_enabled(trace_level::debug))
{
if (close->error.length() == 0)
{
m_logger.log(trace_level::debug, "received close message.");
}
else
{
m_logger.log(trace_level::debug, "received close message with an error: " + close->error);
}

}
m_connection->stop([](std::exception_ptr) {}, std::make_exception_ptr(std::runtime_error("the server closed the connection with the following error: " + close->error)));
return;
}
default:
throw std::runtime_error("unknown message type '" + std::to_string(static_cast<int>(val->message_type)) + "' received");
break;
Expand Down
8 changes: 8 additions & 0 deletions src/signalrclient/hub_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,14 @@ namespace signalr
bool has_result;
};

struct close_message : hub_message
{
close_message(std::string&& error, bool allowReconnect) : hub_message(signalr::message_type::close), error(error), allowReconnect(allowReconnect) {}

std::string error;
bool allowReconnect;
};

struct ping_message : hub_message
{
ping_message() : hub_message(signalr::message_type::ping) {}
Expand Down
25 changes: 25 additions & 0 deletions src/signalrclient/json_hub_protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,31 @@ namespace signalr
hub_message = std::unique_ptr<signalr::hub_message>(new ping_message());
break;
}
case message_type::close:
{
found = obj.find("error");
std::string error;
if (found != obj.end())
{
if (!found->second.is_string())
{
throw signalr_exception("Expected 'error' to be of type 'string'");
}
error = found->second.as_string();
}
bool allowReconnect = false;
found = obj.find("allowReconnect");
if (found != obj.end())
{
if (!found->second.is_bool())
{
throw signalr_exception("Expected 'allowReconnect' to be of type 'bool'");
}
allowReconnect = found->second.as_bool();
}
hub_message = std::unique_ptr<signalr::hub_message>(new close_message(std::move(error), allowReconnect));
break;
}
// TODO: other message types
default:
// Future protocol changes can add message types, old clients can ignore them
Expand Down
30 changes: 30 additions & 0 deletions src/signalrclient/messagepack_hub_protocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,36 @@ namespace signalr
vec.emplace_back(std::unique_ptr<hub_message>(new ping_message()));
break;
}
case message_type::close:
{
std::string error;
if (msgpack_obj_index->type == msgpack::type::NIL)
{
// Empty string
}
else if (msgpack_obj_index->type != msgpack::type::STR)
{
throw signalr_exception("reading 'error' as string failed");
}
else
{
error.append(msgpack_obj_index->via.str.ptr, msgpack_obj_index->via.str.size);
}
++msgpack_obj_index;

bool allowReconnect = false;
if (num_elements_of_message > 2)
{
if (msgpack_obj_index->type != msgpack::type::BOOLEAN)
{
throw signalr_exception("reading 'allowReconnect' as bool failed");
}
allowReconnect = msgpack_obj_index->via.boolean;
}

vec.push_back(std::unique_ptr<hub_message>(new close_message(std::move(error), allowReconnect)));
break;
}
// TODO: other message types
default:
// Future protocol changes can add message types, old clients can ignore them
Expand Down
36 changes: 36 additions & 0 deletions test/signalrclienttests/hub_connection_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2182,4 +2182,40 @@ TEST(receive, unknown_message_type_closes_connection)
{
ASSERT_STREQ("unknown message type '100' received", ex.what());
}
}

TEST(receive, close_message_closes_connection)
{
auto websocket_client = create_test_websocket_client();
auto hub_connection = create_hub_connection(websocket_client);

auto disconnect_mre = manual_reset_event<void>();
hub_connection.set_disconnected([&disconnect_mre](std::exception_ptr ex)
{
disconnect_mre.set(ex);
});

auto mre = manual_reset_event<void>();
hub_connection.start([&mre](std::exception_ptr exception)
{
mre.set(exception);
});

ASSERT_FALSE(websocket_client->receive_loop_started.wait(5000));
ASSERT_FALSE(websocket_client->handshake_sent.wait(5000));
websocket_client->receive_message("{ }\x1e");

mre.get();

websocket_client->receive_message("{ \"type\": 7, \"error\":\"custom error from server\" }\x1e");

try
{
disconnect_mre.get();
ASSERT_TRUE(false);
}
catch (const std::exception& ex)
{
ASSERT_STREQ("the server closed the connection with the following error: custom error from server", ex.what());
}
}
35 changes: 35 additions & 0 deletions test/signalrclienttests/json_hub_protocol_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,35 @@ TEST(json_hub_protocol, parsing_field_order_does_not_matter)
assert_hub_message_equality(&message, output[0].get());
}

std::vector<std::pair<std::string, std::shared_ptr<hub_message>>> close_messages
{
// close message with error
{ "{\"error\":\"error\",\"type\":7}\x1e",
std::shared_ptr<hub_message>(new close_message("error", false)) },

// close message without error
{ "{\"type\":7}\x1e",
std::shared_ptr<hub_message>(new close_message("", false)) },

// close message with error and reconnect
{ "{\"error\":\"error\",\"allowReconnect\":true,\"type\":7}\x1e",
std::shared_ptr<hub_message>(new close_message("error", true)) },

// close message with extra property
{ "{\"error\":\"error\",\"extra\":true,\"type\":7}\x1e",
std::shared_ptr<hub_message>(new close_message("error", false)) },
};

TEST(json_hub_protocol, can_parse_close_message)
{
for (auto& data : close_messages)
{
auto output = json_hub_protocol().parse_messages(data.first);
ASSERT_EQ(1, output.size());
assert_hub_message_equality(data.second.get(), output[0].get());
}
}

TEST(json_hub_protocol, can_serialize_binary)
{
auto output = json_hub_protocol().write_message(
Expand Down Expand Up @@ -146,16 +175,22 @@ std::vector<std::pair<std::string, std::string>> invalid_messages

{ "{\"arguments\":[],\"target\":\"send\",\"invocationId\":42}\x1e", "Field 'type' not found" },

// invocation message
{ "{\"type\":1}\x1e", "Field 'target' not found for 'invocation' message" },
{ "{\"type\":1,\"target\":\"send\",\"invocationId\":42}\x1e", "Field 'arguments' not found for 'invocation' message" },
{ "{\"type\":1,\"target\":\"send\",\"arguments\":[],\"invocationId\":42}\x1e", "Expected 'invocationId' to be of type 'string'" },
{ "{\"type\":1,\"target\":\"send\",\"arguments\":42,\"invocationId\":\"42\"}\x1e", "Expected 'arguments' to be of type 'array'" },
{ "{\"type\":1,\"target\":true,\"arguments\":[],\"invocationId\":\"42\"}\x1e", "Expected 'target' to be of type 'string'" },

// completion message
{ "{\"type\":3}\x1e", "Field 'invocationId' not found for 'completion' message" },
{ "{\"type\":3,\"invocationId\":42}\x1e", "Expected 'invocationId' to be of type 'string'" },
{ "{\"type\":3,\"invocationId\":\"42\",\"error\":[]}\x1e", "Expected 'error' to be of type 'string'" },
{ "{\"type\":3,\"invocationId\":\"42\",\"error\":\"foo\",\"result\":true}\x1e", "The 'error' and 'result' properties are mutually exclusive." },

// close message
{ "{\"type\":7,\"error\":32}\x1e", "Expected 'error' to be of type 'string'"},
{ "{\"type\":7,\"allowReconnect\":\"q\"}\x1e", "Expected 'allowReconnect' to be of type 'bool'"},
};

TEST(json_hub_protocol, invalid_messages_throw)
Expand Down
36 changes: 36 additions & 0 deletions test/signalrclienttests/messagepack_hub_protocol_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,38 @@ TEST(messagepack_hub_protocol, parse_message)
}
}

namespace
{
std::vector<std::pair<std::string, std::shared_ptr<hub_message>>> close_messages
{
// close message with error
{ string_from_bytes({0x08, 0x92, 0x07, 0xA5, 0x65, 0x72, 0x72, 0x6F, 0x72}),
std::shared_ptr<hub_message>(new close_message("error", false)) },

// close message without error
{ string_from_bytes({0x03, 0x92, 0x07, 0xC0}),
std::shared_ptr<hub_message>(new close_message("", false)) },

// close message with error and reconnect
{ string_from_bytes({0x09, 0x93, 0x07, 0xA5, 0x65, 0x72, 0x72, 0x6F, 0x72, 0xC3}),
std::shared_ptr<hub_message>(new close_message("error", true)) },

// close message with extra property
{ string_from_bytes({0x0A, 0x94, 0x07, 0xA5, 0x65, 0x72, 0x72, 0x6F, 0x72, 0xC2, 0x80}),
std::shared_ptr<hub_message>(new close_message("error", false)) },
};
}

TEST(messagepack_hub_protocol, can_parse_close_message)
{
for (auto& data : close_messages)
{
auto output = messagepack_hub_protocol().parse_messages(data.first);
ASSERT_EQ(1, output.size());
assert_hub_message_equality(data.second.get(), output[0].get());
}
}

TEST(messagepack_hub_protocol, can_parse_multiple_messages)
{
auto payload = string_from_bytes({ 0x0D, 0x96, 0x01, 0x80, 0xC0, 0xA6, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x90, 0x90,
Expand Down Expand Up @@ -152,6 +184,10 @@ namespace
{ string_from_bytes({0x05, 0x93, 0x03, 0x80, 0xA1, 0x31}), "completion message has too few properties"},
{ string_from_bytes({0x06, 0x94, 0x03, 0x80, 0xA1, 0x31, 0x03}), "completion message has too few properties"},
{ string_from_bytes({0x08, 0x95, 0x03, 0x80, 0xA1, 0x31, 0x01, 0x91, 0x03}), "reading 'error' as string failed"},

// close message
{ string_from_bytes({0x03, 0x92, 0x07, 0xC3}), "reading 'error' as string failed"},
{ string_from_bytes({0x04, 0x93, 0x07, 0xA0, 0xA0}), "reading 'allowReconnect' as bool failed"},
};
}

Expand Down
9 changes: 9 additions & 0 deletions test/signalrclienttests/test_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,15 @@ void assert_hub_message_equality(signalr::hub_message* expected, signalr::hub_me
// No fields on ping messages currently
break;
}
case message_type::close:
{
auto expected_message = reinterpret_cast<close_message*>(expected);
auto actual_message = reinterpret_cast<close_message*>(actual);

ASSERT_STREQ(expected_message->error.data(), actual_message->error.data());
ASSERT_EQ(expected_message->allowReconnect, actual_message->allowReconnect);
break;
}
default:
ASSERT_TRUE(false);
break;
Expand Down