Fix the protocol abstraction so different protocols can be supported (#48)

This commit is contained in:
Brennan 2021-03-16 15:21:22 -07:00 коммит произвёл GitHub
Родитель f8ab4dfd2a
Коммит 9927ad4db0
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
29 изменённых файлов: 612 добавлений и 178 удалений

Просмотреть файл

@ -11,6 +11,7 @@
#include "trace_level.h"
#include "log_writer.h"
#include "signalr_client_config.h"
#include "transfer_format.h"
namespace signalr
{
@ -31,7 +32,7 @@ namespace signalr
SIGNALRCLIENT_API void __cdecl start(std::function<void(std::exception_ptr)> callback) noexcept;
SIGNALRCLIENT_API void __cdecl send(const std::string& data, std::function<void(std::exception_ptr)> callback) noexcept;
SIGNALRCLIENT_API void __cdecl send(const std::string& data, transfer_format transfer_format, std::function<void(std::exception_ptr)> callback) noexcept;
SIGNALRCLIENT_API void __cdecl set_message_received(const message_received_handler& message_received_callback);
SIGNALRCLIENT_API void __cdecl set_disconnected(const std::function<void __cdecl()>& disconnected_callback);

Просмотреть файл

@ -7,6 +7,7 @@
#include <string>
#include <vector>
#include <map>
#include <cstddef>
namespace signalr
{
@ -34,6 +35,11 @@ namespace signalr
*/
value();
/**
* Create an object representing a value_type::null value.
*/
value(std::nullptr_t);
/**
* Create an object representing a default value for the given value_type.
*/
@ -64,6 +70,11 @@ namespace signalr
*/
value(const char* val);
/**
* Create an object representing a value_type::string with the given string value.
*/
value(const char* val, size_t length);
/**
* Create an object representing a value_type::array with the given vector of value's.
*/

Просмотреть файл

@ -14,11 +14,11 @@ namespace signalr
public:
virtual ~websocket_client() {};
virtual void start(const std::string& url, transfer_format format, std::function<void(std::exception_ptr)> callback) = 0;
virtual void start(const std::string& url, std::function<void(std::exception_ptr)> callback) = 0;
virtual void stop(std::function<void(std::exception_ptr)> callback) = 0;
virtual void send(const std::string& payload, std::function<void(std::exception_ptr)> callback) = 0;
virtual void send(const std::string& payload, transfer_format transfer_format, std::function<void(std::exception_ptr)> callback) = 0;
virtual void receive(std::function<void(const std::string&, std::exception_ptr)> callback) = 0;
};

Просмотреть файл

@ -10,17 +10,17 @@ namespace signalr
{
// dtor_clear_arguments will be passed when closing any pending callbacks when the `callback_manager` is
// destroyed (i.e. in the dtor)
callback_manager::callback_manager(const signalr::value& dtor_clear_arguments)
callback_manager::callback_manager(const char* dtor_clear_arguments)
: m_dtor_clear_arguments(dtor_clear_arguments)
{ }
callback_manager::~callback_manager()
{
clear(m_dtor_clear_arguments);
clear(m_dtor_clear_arguments.data());
}
// note: callback must not throw except for the `on_progress` callback which will never be invoked from the dtor
std::string callback_manager::register_callback(const std::function<void(const signalr::value&)>& callback)
std::string callback_manager::register_callback(const std::function<void(const char*, const signalr::value&)>& callback)
{
auto callback_id = get_callback_id();
@ -35,9 +35,9 @@ namespace signalr
// invokes a callback and stops tracking it if remove callback set to true
bool callback_manager::invoke_callback(const std::string& callback_id, const signalr::value& arguments, bool remove_callback)
bool callback_manager::invoke_callback(const std::string& callback_id, const char* error, const signalr::value& arguments, bool remove_callback)
{
std::function<void(const signalr::value& arguments)> callback;
std::function<void(const char*, const signalr::value& arguments)> callback;
{
std::lock_guard<std::mutex> lock(m_map_lock);
@ -56,7 +56,7 @@ namespace signalr
}
}
callback(arguments);
callback(error, arguments);
return true;
}
@ -69,14 +69,14 @@ namespace signalr
}
}
void callback_manager::clear(const signalr::value& arguments)
void callback_manager::clear(const char* error)
{
{
std::lock_guard<std::mutex> lock(m_map_lock);
for (auto& kvp : m_callbacks)
{
kvp.second(arguments);
kvp.second(error, signalr::value());
}
m_callbacks.clear();

Просмотреть файл

@ -15,22 +15,22 @@ namespace signalr
class callback_manager
{
public:
explicit callback_manager(const signalr::value& dtor_error);
explicit callback_manager(const char* dtor_error);
~callback_manager();
callback_manager(const callback_manager&) = delete;
callback_manager& operator=(const callback_manager&) = delete;
std::string register_callback(const std::function<void(const signalr::value&)>& callback);
bool invoke_callback(const std::string& callback_id, const signalr::value& arguments, bool remove_callback);
std::string register_callback(const std::function<void(const char*, const signalr::value&)>& callback);
bool invoke_callback(const std::string& callback_id, const char* error, const signalr::value& arguments, bool remove_callback);
bool remove_callback(const std::string& callback_id);
void clear(const signalr::value& arguments);
void clear(const char* error);
private:
std::atomic<int> m_id { 0 };
std::unordered_map<std::string, std::function<void(const signalr::value&)>> m_callbacks;
std::unordered_map<std::string, std::function<void(const char*, const signalr::value&)>> m_callbacks;
std::mutex m_map_lock;
const signalr::value m_dtor_clear_arguments;
std::string m_dtor_clear_arguments;
std::string get_callback_id();
};

Просмотреть файл

@ -22,9 +22,9 @@ namespace signalr
m_pImpl->start(callback);
}
void connection::send(const std::string& data, std::function<void(std::exception_ptr)> callback) noexcept
void connection::send(const std::string& data, transfer_format transfer_format, std::function<void(std::exception_ptr)> callback) noexcept
{
m_pImpl->send(data, callback);
m_pImpl->send(data, transfer_format, callback);
}
void connection::set_message_received(const message_received_handler& message_received_callback)

Просмотреть файл

@ -455,7 +455,7 @@ namespace signalr
auto query_string = "id=" + m_connection_token;
auto connect_url = url_builder::build_connect(url, transport->get_transport_type(), query_string);
transport->start(connect_url, transfer_format::text, [callback, logger](std::exception_ptr exception)
transport->start(connect_url, [callback, logger](std::exception_ptr exception)
mutable {
try
{
@ -479,6 +479,7 @@ namespace signalr
void connection_impl::process_response(std::string&& response)
{
// TODO: log binary data better
m_logger.log(trace_level::messages,
std::string("processing message: ").append(response));
@ -504,7 +505,7 @@ namespace signalr
}
}
void connection_impl::send(const std::string& data, std::function<void(std::exception_ptr)> callback) noexcept
void connection_impl::send(const std::string& data, transfer_format transfer_format, std::function<void(std::exception_ptr)> callback) noexcept
{
// To prevent an (unlikely) condition where the transport is nulled out after we checked the connection_state
// and before sending data we store the pointer in the local variable. In this case `send()` will throw but
@ -524,7 +525,7 @@ namespace signalr
logger.log(trace_level::info, std::string("sending data: ").append(data));
transport->send(data, [logger, callback](std::exception_ptr exception)
transport->send(data, transfer_format, [logger, callback](std::exception_ptr exception)
mutable {
try
{

Просмотреть файл

@ -39,7 +39,7 @@ namespace signalr
~connection_impl();
void start(std::function<void(std::exception_ptr)> callback) noexcept;
void send(const std::string &data, std::function<void(std::exception_ptr)> callback) noexcept;
void send(const std::string &data, transfer_format transfer_format, std::function<void(std::exception_ptr)> callback) noexcept;
void stop(std::function<void(std::exception_ptr)> callback) noexcept;
connection_state get_connection_state() const noexcept;

Просмотреть файл

@ -6,6 +6,7 @@
#ifdef USE_CPPRESTSDK
#include "default_websocket_client.h"
#include <signalrclient/signalr_exception.h>
namespace signalr
{
@ -28,7 +29,7 @@ namespace signalr
: m_underlying_client(create_client_config(signalr_client_config))
{ }
void default_websocket_client::start(const std::string& url, transfer_format, std::function<void(std::exception_ptr)> callback)
void default_websocket_client::start(const std::string& url, std::function<void(std::exception_ptr)> callback)
{
m_underlying_client.connect(utility::conversions::to_string_t(url))
.then([callback](pplx::task<void> task)
@ -62,10 +63,18 @@ namespace signalr
});
}
void default_websocket_client::send(const std::string& payload, std::function<void(std::exception_ptr)> callback)
void default_websocket_client::send(const std::string& payload, signalr::transfer_format transfer_format, std::function<void(std::exception_ptr)> callback)
{
web::websockets::client::websocket_outgoing_message msg;
msg.set_utf8_message(payload);
if (transfer_format == signalr::transfer_format::binary)
{
throw signalr_exception("binary isn't supported currently");
}
else
{
msg.set_utf8_message(payload);
}
m_underlying_client.send(msg)
.then([callback](pplx::task<void> task)
{
@ -88,8 +97,15 @@ namespace signalr
{
try
{
std::string msg;
auto response = task.get();
auto msg = response.extract_string().get();
if (response.message_type() == web::websockets::client::websocket_message_type::binary_message)
{
throw signalr_exception("binary isn't supported currently");
}
msg = response.extract_string().get();
callback(msg, nullptr);
}
catch (...)

Просмотреть файл

@ -20,9 +20,9 @@ namespace signalr
public:
explicit default_websocket_client(const signalr_client_config& signalr_client_config = {}) noexcept;
void start(const std::string& url, transfer_format format, std::function<void(std::exception_ptr)> callback);
void start(const std::string& url, std::function<void(std::exception_ptr)> callback);
void stop(std::function<void(std::exception_ptr)> callback);
void send(const std::string& payload, std::function<void(std::exception_ptr)> callback);
void send(const std::string& payload, transfer_format transfer_format, std::function<void(std::exception_ptr)> callback);
void receive(std::function<void(const std::string&, std::exception_ptr)> callback);
private:
web::websockets::client::websocket_client m_underlying_client;

Просмотреть файл

@ -11,7 +11,7 @@ namespace signalr
{
namespace handshake
{
std::string write_handshake(std::shared_ptr<hub_protocol>& protocol)
std::string write_handshake(const std::unique_ptr<hub_protocol>& protocol)
{
auto map = std::map<std::string, signalr::value>
{

Просмотреть файл

@ -12,7 +12,7 @@ namespace signalr
{
namespace handshake
{
std::string write_handshake(std::shared_ptr<hub_protocol>&);
std::string write_handshake(const std::unique_ptr<hub_protocol>&);
std::tuple<std::string, signalr::value> parse_handshake(const std::string&);
}
}

Просмотреть файл

@ -17,16 +17,17 @@ namespace signalr
// unnamed namespace makes it invisble outside this translation unit
namespace
{
static std::function<void(const signalr::value&)> create_hub_invocation_callback(const logger& logger,
static std::function<void(const char*, const signalr::value&)> create_hub_invocation_callback(const logger& logger,
const std::function<void(const signalr::value&)>& set_result,
const std::function<void(const std::exception_ptr e)>& set_exception);
}
std::shared_ptr<hub_connection_impl> hub_connection_impl::create(const std::string& url, trace_level trace_level,
const std::shared_ptr<log_writer>& log_writer, std::shared_ptr<http_client> http_client,
std::shared_ptr<hub_connection_impl> hub_connection_impl::create(const std::string& url,
trace_level trace_level, const std::shared_ptr<log_writer>& log_writer, std::shared_ptr<http_client> http_client,
std::function<std::shared_ptr<websocket_client>(const signalr_client_config&)> websocket_factory, const bool skip_negotiation)
{
auto connection = std::shared_ptr<hub_connection_impl>(new hub_connection_impl(url, trace_level, log_writer, http_client, websocket_factory, skip_negotiation));
auto connection = std::shared_ptr<hub_connection_impl>(new hub_connection_impl(url,
trace_level, log_writer, http_client, websocket_factory, skip_negotiation));
connection->initialize();
@ -38,8 +39,8 @@ namespace signalr
std::function<std::shared_ptr<websocket_client>(const signalr_client_config&)> websocket_factory, const bool skip_negotiation)
: m_connection(connection_impl::create(url, trace_level, log_writer,
http_client, websocket_factory, skip_negotiation)), m_logger(log_writer, trace_level),
m_callback_manager(signalr::value(std::map<std::string, signalr::value> { { std::string("error"), std::string("connection went out of scope before invocation result was received") } })),
m_handshakeReceived(false), m_disconnected([]() noexcept {}), m_protocol(std::make_shared<json_hub_protocol>())
m_callback_manager("connection went out of scope before invocation result was received"),
m_handshakeReceived(false), m_disconnected([]() noexcept {}), m_protocol(std::unique_ptr<json_hub_protocol>(new json_hub_protocol()))
{}
void hub_connection_impl::initialize()
@ -64,7 +65,7 @@ namespace signalr
// start may be waiting on the handshake response so we complete it here, this no-ops if already set
connection->m_handshakeTask->set(std::make_exception_ptr(signalr_exception("connection closed while handshake was in progress.")));
connection->m_callback_manager.clear(signalr::value(std::map<std::string, signalr::value> { { std::string("error"), std::string("connection was stopped before invocation result was received") } }));
connection->m_callback_manager.clear("connection was stopped before invocation result was received");
connection->m_disconnected();
}
@ -139,7 +140,7 @@ namespace signalr
auto handshake_request = handshake::write_handshake(connection->m_protocol);
connection->m_connection->send(handshake_request, [weak_connection, callback](std::exception_ptr exception)
connection->m_connection->send(handshake_request, connection->m_protocol->transfer_format(), [weak_connection, callback](std::exception_ptr exception)
{
auto connection = weak_connection.lock();
if (!connection)
@ -262,25 +263,15 @@ namespace signalr
for (const auto& val : messages)
{
if (!val.is_map())
{
m_logger.log(trace_level::info, std::string("unexpected response received from the server: ")
.append(response));
return;
}
const auto &obj = val.as_map();
switch ((int)obj.at("type").as_double())
switch (val->message_type)
{
case message_type::invocation:
{
auto method = obj.at("target").as_string();
auto event = m_subscriptions.find(method);
auto invocation = static_cast<invocation_message*>(val.get());
auto event = m_subscriptions.find(invocation->target);
if (event != m_subscriptions.end())
{
const auto& args = obj.at("arguments");
const auto& args = invocation->arguments;
event->second(args);
}
else
@ -297,13 +288,8 @@ namespace signalr
break;
case message_type::completion:
{
auto error = obj.find("error");
auto result = obj.find("result");
if (error != obj.end() && result != obj.end())
{
// TODO: error
}
invoke_callback(obj);
auto completion = static_cast<completion_message*>(val.get());
invoke_callback(completion);
break;
}
case message_type::cancel_invocation:
@ -330,23 +316,19 @@ namespace signalr
}
}
bool hub_connection_impl::invoke_callback(const signalr::value& message)
bool hub_connection_impl::invoke_callback(completion_message* completion)
{
if (!message.is_map())
const char* error = nullptr;
if (!completion->error.empty())
{
throw signalr_exception("expected object");
error = completion->error.data();
}
auto& invocationId = message.as_map().at("invocationId");
if (!invocationId.is_string())
// TODO: consider transferring ownership of 'result' so that if we run user callbacks on a different thread we don't need to
// worry about object lifetime
if (!m_callback_manager.invoke_callback(completion->invocation_id, error, completion->result, true))
{
throw signalr_exception("invocationId is not a string");
}
auto& id = invocationId.as_string();
if (!m_callback_manager.invoke_callback(id, message, true))
{
m_logger.log(trace_level::info, std::string("no callback found for id: ").append(id));
m_logger.log(trace_level::info, std::string("no callback found for id: ").append(completion->invocation_id));
return false;
}
@ -385,24 +367,13 @@ namespace signalr
void hub_connection_impl::invoke_hub_method(const std::string& method_name, const signalr::value& arguments,
const std::string& callback_id, std::function<void()> set_completion, std::function<void(const std::exception_ptr)> set_exception) noexcept
{
auto map = std::map<std::string, signalr::value>
{
{ "type", signalr::value((double)message_type::invocation) },
{ "target", signalr::value(method_name) },
{ "arguments", arguments }
};
if (!callback_id.empty())
{
map["invocationId"] = signalr::value(callback_id);
}
auto message = m_protocol->write_message(signalr::value(std::move(map)));
invocation_message invocation(callback_id, method_name, arguments);
auto message = m_protocol->write_message(&invocation);
// weak_ptr prevents a circular dependency leading to memory leak and other problems
auto weak_hub_connection = std::weak_ptr<hub_connection_impl>(shared_from_this());
m_connection->send(message, [set_completion, set_exception, weak_hub_connection, callback_id](std::exception_ptr exception)
m_connection->send(message, m_protocol->transfer_format(), [set_completion, set_exception, weak_hub_connection, callback_id](std::exception_ptr exception)
{
if (exception)
{
@ -448,29 +419,21 @@ namespace signalr
// unnamed namespace makes it invisble outside this translation unit
namespace
{
static std::function<void(const signalr::value&)> create_hub_invocation_callback(const logger& logger,
static std::function<void(const char* error, const signalr::value&)> create_hub_invocation_callback(const logger& logger,
const std::function<void(const signalr::value&)>& set_result,
const std::function<void(const std::exception_ptr)>& set_exception)
{
return [logger, set_result, set_exception](const signalr::value& message)
return [logger, set_result, set_exception](const char* error, const signalr::value& message)
{
assert(message.is_map());
const auto& map = message.as_map();
auto found = map.find("result");
if (found != map.end())
if (error != nullptr)
{
set_result(found->second);
}
else if ((found = map.find("error")) != map.end())
{
assert(found->second.is_string());
set_exception(
std::make_exception_ptr(
hub_exception(found->second.as_string())));
hub_exception(error)));
}
else
{
set_result(signalr::value());
set_result(message);
}
};
}

Просмотреть файл

@ -24,8 +24,8 @@ namespace signalr
class hub_connection_impl : public std::enable_shared_from_this<hub_connection_impl>
{
public:
static std::shared_ptr<hub_connection_impl> create(const std::string& url, trace_level trace_level,
const std::shared_ptr<log_writer>& log_writer, std::shared_ptr<http_client> http_client,
static std::shared_ptr<hub_connection_impl> create(const std::string& url,
trace_level trace_level, const std::shared_ptr<log_writer>& log_writer, std::shared_ptr<http_client> http_client,
std::function<std::shared_ptr<websocket_client>(const signalr_client_config&)> websocket_factory, bool skip_negotiation = false);
hub_connection_impl(const hub_connection_impl&) = delete;
@ -59,7 +59,7 @@ namespace signalr
std::shared_ptr<completion_event> m_handshakeTask;
std::function<void()> m_disconnected;
signalr_client_config m_signalr_client_config;
std::shared_ptr<hub_protocol> m_protocol;
std::unique_ptr<hub_protocol> m_protocol;
std::mutex m_stop_callback_lock;
std::vector<std::function<void(std::exception_ptr)>> m_stop_callbacks;
@ -70,6 +70,6 @@ namespace signalr
void invoke_hub_method(const std::string& method_name, const signalr::value& arguments, const std::string& callback_id,
std::function<void()> set_completion, std::function<void(const std::exception_ptr)> set_exception) noexcept;
bool invoke_callback(const signalr::value& message);
bool invoke_callback(completion_message* completion);
};
}

Просмотреть файл

@ -5,16 +5,74 @@
#pragma once
#include "signalrclient/signalr_value.h"
#include "signalrclient/transfer_format.h"
#include "message_type.h"
#include <memory>
namespace signalr
{
struct hub_message
{
hub_message(signalr::message_type message_type) : message_type(message_type) {}
virtual ~hub_message() {}
signalr::message_type message_type;
};
struct hub_invocation_message : hub_message
{
hub_invocation_message(const std::string& invocation_id, signalr::message_type message_type)
: hub_message(message_type), invocation_id(invocation_id)
{ }
std::string invocation_id;
};
struct invocation_message : hub_invocation_message
{
invocation_message(const std::string& invocation_id, const std::string& target,
const signalr::value& args, const std::vector<std::string>& stream_ids = std::vector<std::string>())
: hub_invocation_message(invocation_id, signalr::message_type::invocation), target(target), arguments(args), stream_ids(stream_ids)
{ }
invocation_message(std::string&& invocation_id, std::string&& target,
signalr::value&& args, std::vector<std::string>&& stream_ids = std::vector<std::string>())
: hub_invocation_message(invocation_id, signalr::message_type::invocation), target(target), arguments(args), stream_ids(stream_ids)
{ }
std::string target;
signalr::value arguments;
std::vector<std::string> stream_ids;
};
struct completion_message : hub_invocation_message
{
completion_message(const std::string& invocation_id, const std::string& error, const signalr::value& result)
: hub_invocation_message(invocation_id, signalr::message_type::completion), error(error), result(result)
{ }
completion_message(std::string&& invocation_id, std::string&& error, signalr::value&& result)
: hub_invocation_message(invocation_id, signalr::message_type::completion), error(error), result(result)
{ }
std::string error;
signalr::value result;
};
struct ping_message : hub_message
{
ping_message() : hub_message(signalr::message_type::ping) {}
};
class hub_protocol
{
public:
virtual std::string write_message(const signalr::value&) const = 0;
virtual std::vector<signalr::value> parse_messages(const std::string&) const = 0;
virtual std::string write_message(const hub_message*) const = 0;
virtual std::vector<std::unique_ptr<hub_message>> parse_messages(const std::string&) const = 0;
virtual const std::string& name() const = 0;
virtual int version() const = 0;
virtual signalr::transfer_format transfer_format() const = 0;
virtual ~hub_protocol() {}
};
}

Просмотреть файл

@ -10,20 +10,67 @@
namespace signalr
{
std::string signalr::json_hub_protocol::write_message(const signalr::value& hub_message) const
std::string signalr::json_hub_protocol::write_message(const hub_message* hub_message) const
{
return Json::writeString(getJsonWriter(), createJson(hub_message)) + record_separator;
Json::Value object(Json::ValueType::objectValue);
#pragma warning (push)
#pragma warning (disable: 4061)
switch (hub_message->message_type)
{
case message_type::invocation:
{
auto invocation = static_cast<invocation_message const*>(hub_message);
object["type"] = (int)invocation->message_type;
if (!invocation->invocation_id.empty())
{
object["invocationId"] = invocation->invocation_id;
}
object["target"] = invocation->target;
object["arguments"] = createJson(invocation->arguments);
// TODO: streamIds
break;
}
case message_type::completion:
{
auto completion = static_cast<completion_message const*>(hub_message);
object["type"] = (int)completion->message_type;
object["invocationId"] = completion->invocation_id;
if (!completion->error.empty())
{
object["error"] = completion->error;
}
else
{
object["result"] = createJson(completion->result);
}
break;
}
case message_type::ping:
{
auto ping = static_cast<ping_message const*>(hub_message);
object["type"] = (int)ping->message_type;
break;
}
// TODO: other message types
default:
break;
}
#pragma warning (pop)
return Json::writeString(getJsonWriter(), object) + record_separator;
}
std::vector<signalr::value> json_hub_protocol::parse_messages(const std::string& message) const
std::vector<std::unique_ptr<hub_message>> json_hub_protocol::parse_messages(const std::string& message) const
{
std::vector<signalr::value> vec;
std::vector<std::unique_ptr<hub_message>> vec;
size_t offset = 0;
auto pos = message.find(record_separator, offset);
while (pos != std::string::npos)
{
auto hub_message = parse_message(message.c_str() + offset, pos - offset);
vec.push_back(std::move(hub_message));
vec.emplace_back(std::move(hub_message));
offset = pos + 1;
pos = message.find(record_separator, offset);
@ -33,7 +80,7 @@ namespace signalr
return vec;
}
signalr::value json_hub_protocol::parse_message(const char* begin, size_t length) const
std::unique_ptr<hub_message> json_hub_protocol::parse_message(const char* begin, size_t length) const
{
Json::Value root;
auto reader = getJsonReader();
@ -44,6 +91,7 @@ namespace signalr
throw signalr_exception(errors);
}
// TODO: manually go through the json object to avoid short-lived allocations
auto value = createValue(root);
if (!value.is_map())
@ -59,6 +107,8 @@ namespace signalr
throw signalr_exception("Field 'type' not found");
}
std::unique_ptr<hub_message> hub_message;
switch ((int)found->second.as_double())
{
case message_type::invocation:
@ -68,12 +118,97 @@ namespace signalr
{
throw signalr_exception("Field 'target' not found for 'invocation' message");
}
if (!found->second.is_string())
{
throw signalr_exception("Expected 'target' to be of type 'string'");
}
found = obj.find("arguments");
if (found == obj.end())
{
throw signalr_exception("Field 'arguments' not found for 'invocation' message");
}
if (!found->second.is_array())
{
throw signalr_exception("Expected 'arguments' to be of type 'array'");
}
std::string invocation_id;
found = obj.find("invocationId");
if (found == obj.end())
{
invocation_id = "";
}
else
{
if (!found->second.is_string())
{
throw signalr_exception("Expected 'invocationId' to be of type 'string'");
}
invocation_id = found->second.as_string();
}
hub_message = std::unique_ptr<signalr::hub_message>(new invocation_message(invocation_id,
obj.find("target")->second.as_string(), obj.find("arguments")->second.as_array()));
break;
}
case message_type::completion:
{
bool has_result = false;
signalr::value result;
found = obj.find("result");
if (found != obj.end())
{
has_result = true;
result = found->second;
}
std::string error;
found = obj.find("error");
if (found == obj.end())
{
error = "";
}
else
{
if (found->second.is_string())
{
error = found->second.as_string();
}
else
{
throw signalr_exception("Expected 'error' to be of type 'string'");
}
}
found = obj.find("invocationId");
if (found == obj.end())
{
throw signalr_exception("Field 'invocationId' not found for 'completion' message");
}
else
{
if (!found->second.is_string())
{
throw signalr_exception("Expected 'invocationId' to be of type 'string'");
}
}
if (!error.empty() && has_result)
{
throw signalr_exception("The 'error' and 'result' properties are mutually exclusive.");
}
hub_message = std::unique_ptr<signalr::hub_message>(new completion_message(obj.find("invocationId")->second.as_string(),
error, result));
break;
}
case message_type::ping:
{
hub_message = std::unique_ptr<signalr::hub_message>(new ping_message());
break;
}
// TODO: other message types
@ -83,6 +218,6 @@ namespace signalr
break;
}
return value;
return hub_message;
}
}

Просмотреть файл

@ -12,20 +12,27 @@ namespace signalr
class json_hub_protocol : public hub_protocol
{
public:
std::string write_message(const signalr::value&) const;
std::vector<signalr::value> parse_messages(const std::string&) const;
std::string write_message(const hub_message*) const;
std::vector<std::unique_ptr<hub_message>> parse_messages(const std::string&) const;
const std::string& name() const
{
return m_protocol_name;
}
int version() const
{
return 1;
}
signalr::transfer_format transfer_format() const
{
return signalr::transfer_format::text;
}
~json_hub_protocol() {}
private:
signalr::value parse_message(const char* begin, size_t length) const;
std::unique_ptr<hub_message> parse_message(const char* begin, size_t length) const;
std::string m_protocol_name = "json";
};

Просмотреть файл

@ -32,6 +32,8 @@ namespace signalr
value::value() : mType(value_type::null) {}
value::value(std::nullptr_t) : mType(value_type::null) {}
value::value(value_type t) : mType(t)
{
switch (mType)
@ -82,6 +84,11 @@ namespace signalr
new (&mStorage.string) std::string(val);
}
value::value(const char* val, size_t length) : mType(value_type::string)
{
new (&mStorage.string) std::string(val, length);
}
value::value(const std::vector<value>& val) : mType(value_type::array)
{
new (&mStorage.array) std::vector<value>(val);

Просмотреть файл

@ -17,11 +17,11 @@ namespace signalr
virtual ~transport();
virtual void start(const std::string& url, transfer_format format, std::function<void(std::exception_ptr)> callback) noexcept = 0;
virtual void start(const std::string& url, std::function<void(std::exception_ptr)> callback) noexcept = 0;
virtual void stop(std::function<void(std::exception_ptr)> callback) noexcept = 0;
virtual void on_close(std::function<void(std::exception_ptr)> callback) = 0;
virtual void send(const std::string& payload, std::function<void(std::exception_ptr)> callback) noexcept = 0;
virtual void send(const std::string& payload, signalr::transfer_format transfer_format, std::function<void(std::exception_ptr)> callback) noexcept = 0;
virtual void on_receive(std::function<void(std::string&&, std::exception_ptr)> callback) = 0;

Просмотреть файл

@ -149,7 +149,7 @@ namespace signalr
}
}
void websocket_transport::start(const std::string& url, transfer_format format, std::function<void(std::exception_ptr)> callback) noexcept
void websocket_transport::start(const std::string& url, std::function<void(std::exception_ptr)> callback) noexcept
{
signalr::uri uri(url);
assert(uri.scheme() == "ws" || uri.scheme() == "wss");
@ -178,7 +178,7 @@ namespace signalr
auto transport = shared_from_this();
websocket_client->start(url, format, [transport, receive_loop_cts, callback](std::exception_ptr exception)
websocket_client->start(url, [transport, receive_loop_cts, callback](std::exception_ptr exception)
{
try
{
@ -261,9 +261,9 @@ namespace signalr
m_process_response_callback = callback;
}
void websocket_transport::send(const std::string& payload, std::function<void(std::exception_ptr)> callback) noexcept
void websocket_transport::send(const std::string& payload, transfer_format transfer_format, std::function<void(std::exception_ptr)> callback) noexcept
{
safe_get_websocket_client()->send(payload, [callback](std::exception_ptr exception)
safe_get_websocket_client()->send(payload, transfer_format, [callback](std::exception_ptr exception)
{
if (exception != nullptr)
{

Просмотреть файл

@ -25,11 +25,11 @@ namespace signalr
transport_type get_transport_type() const noexcept override;
void start(const std::string& url, transfer_format format, std::function<void(std::exception_ptr)> callback) noexcept override;
void start(const std::string& url, std::function<void(std::exception_ptr)> callback) noexcept override;
void stop(std::function<void(std::exception_ptr)> callback) noexcept override;
void on_close(std::function<void(std::exception_ptr)> callback) override;
void send(const std::string& payload, std::function<void(std::exception_ptr)> callback) noexcept override;
void send(const std::string& payload, transfer_format transfer_format, std::function<void(std::exception_ptr)> callback) noexcept override;
void on_receive(std::function<void(std::string&&, std::exception_ptr)>) override;

Просмотреть файл

@ -7,6 +7,7 @@ set (SOURCES
handshake_tests.cpp
hub_connection_tests.cpp
hub_exception_tests.cpp
json_hub_protocol_tests.cpp
logger_tests.cpp
memory_log_writer.cpp
negotiate_tests.cpp

Просмотреть файл

@ -9,26 +9,27 @@ using namespace signalr;
TEST(callback_manager_register_callback, register_returns_unique_callback_ids)
{
callback_manager callback_mgr{ signalr::value() };
auto callback_id1 = callback_mgr.register_callback([](const signalr::value&){});
auto callback_id2 = callback_mgr.register_callback([](const signalr::value&){});
callback_manager callback_mgr{ "" };
auto callback_id1 = callback_mgr.register_callback([](const char*, const signalr::value&){});
auto callback_id2 = callback_mgr.register_callback([](const char*, const signalr::value&){});
ASSERT_NE(callback_id1, callback_id2);
}
TEST(callback_manager_invoke_callback, invoke_callback_invokes_and_removes_callback_if_remove_callback_true)
{
callback_manager callback_mgr{ signalr::value() };
callback_manager callback_mgr{ "" };
int callback_argument;
auto callback_id = callback_mgr.register_callback(
[&callback_argument](const signalr::value& argument)
[&callback_argument](const char* error, const signalr::value& argument)
{
ASSERT_EQ(nullptr, error);
callback_argument = (int)argument.as_double();
});
auto callback_found = callback_mgr.invoke_callback(callback_id, signalr::value(42.0), true);
auto callback_found = callback_mgr.invoke_callback(callback_id, nullptr, signalr::value(42.0), true);
ASSERT_TRUE(callback_found);
ASSERT_EQ(42, callback_argument);
@ -37,17 +38,18 @@ TEST(callback_manager_invoke_callback, invoke_callback_invokes_and_removes_callb
TEST(callback_manager_invoke_callback, invoke_callback_invokes_and_does_not_remove_callback_if_remove_callback_false)
{
callback_manager callback_mgr{ signalr::value() };
callback_manager callback_mgr{ "" };
int callback_argument;
auto callback_id = callback_mgr.register_callback(
[&callback_argument](const signalr::value& argument)
{
callback_argument = (int)argument.as_double();
});
[&callback_argument](const char* error, const signalr::value& argument)
{
ASSERT_EQ(nullptr, error);
callback_argument = (int)argument.as_double();
});
auto callback_found = callback_mgr.invoke_callback(callback_id, signalr::value(42.0), false);
auto callback_found = callback_mgr.invoke_callback(callback_id, nullptr, signalr::value(42.0), false);
ASSERT_TRUE(callback_found);
ASSERT_EQ(42, callback_argument);
@ -56,8 +58,8 @@ TEST(callback_manager_invoke_callback, invoke_callback_invokes_and_does_not_remo
TEST(callback_manager_invoke_callback, invoke_callback_returns_false_for_invalid_callback_id)
{
callback_manager callback_mgr{ signalr::value() };
auto callback_found = callback_mgr.invoke_callback("42", signalr::value(), true);
callback_manager callback_mgr{ "" };
auto callback_found = callback_mgr.invoke_callback("42", nullptr, signalr::value(), true);
ASSERT_FALSE(callback_found);
}
@ -67,10 +69,10 @@ TEST(callback_manager_remove, remove_removes_callback_and_returns_true_for_valid
auto callback_called = false;
{
callback_manager callback_mgr{ signalr::value() };
callback_manager callback_mgr{ "" };
auto callback_id = callback_mgr.register_callback(
[&callback_called](const signalr::value&)
[&callback_called](const char*, const signalr::value&)
{
callback_called = true;
});
@ -83,26 +85,27 @@ TEST(callback_manager_remove, remove_removes_callback_and_returns_true_for_valid
TEST(callback_manager_remove, remove_returns_false_for_invalid_callback_id)
{
callback_manager callback_mgr{ signalr::value() };
callback_manager callback_mgr{ "" };
ASSERT_FALSE(callback_mgr.remove_callback("42"));
}
TEST(callback_manager_clear, clear_invokes_all_callbacks)
{
callback_manager callback_mgr{ signalr::value() };
callback_manager callback_mgr{ "" };
auto invocation_count = 0;
for (auto i = 0; i < 10; i++)
{
callback_mgr.register_callback(
[&invocation_count](const signalr::value& argument)
{
invocation_count++;
ASSERT_EQ(42, argument.as_double());
});
[&invocation_count](const char* error, const signalr::value& argument)
{
invocation_count++;
ASSERT_STREQ("clearing callback", error);
ASSERT_TRUE(argument.is_null());
});
}
callback_mgr.clear(signalr::value(42.0));
callback_mgr.clear("clearing callback");
ASSERT_EQ(10, invocation_count);
}
@ -110,21 +113,20 @@ TEST(callback_manager_clear, clear_invokes_all_callbacks)
TEST(callback_manager_dtor, clear_invokes_all_callbacks)
{
auto invocation_count = 0;
bool parameter_correct = true;
{
callback_manager callback_mgr{ signalr::value(42.0) };
callback_manager callback_mgr{ "error" };
for (auto i = 0; i < 10; i++)
{
callback_mgr.register_callback(
[&invocation_count, &parameter_correct](const signalr::value& argument)
[&invocation_count](const char* error, const signalr::value& argument)
{
invocation_count++;
parameter_correct &= argument.as_double() == 42;
ASSERT_STREQ("error", error);
ASSERT_TRUE(argument.is_null());
});
}
}
ASSERT_EQ(10, invocation_count);
ASSERT_TRUE(parameter_correct);
}

Просмотреть файл

@ -348,7 +348,7 @@ TEST(connection_impl_send, send_fails_if_transport_fails_when_receiving_messages
mre.get();
connection->send("message", [&mre](std::exception_ptr exception)
connection->send("message", transfer_format::text, [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -1055,7 +1055,7 @@ TEST(connection_impl_send, message_sent)
mre.get();
connection->send(message, [&mre](std::exception_ptr exception)
connection->send(message, transfer_format::text, [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -1071,7 +1071,7 @@ TEST(connection_impl_send, send_throws_if_connection_not_connected)
connection_impl::create(create_uri(), trace_level::none, std::make_shared<memory_log_writer>());
auto mre = manual_reset_event<void>();
connection->send("whatever", [&mre](std::exception_ptr exception)
connection->send("whatever", transfer_format::text, [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -1106,7 +1106,7 @@ TEST(connection_impl_send, exceptions_from_send_logged_and_propagated)
mre.get();
connection->send("Test message", [&mre](std::exception_ptr exception)
connection->send("Test message", transfer_format::text, [&mre](std::exception_ptr exception)
{
mre.set(exception);
});

Просмотреть файл

@ -70,7 +70,7 @@ TEST(handshake, extra_fields_are_fine)
TEST(handshake, writes_protocol_and_version)
{
auto protocol = std::shared_ptr<hub_protocol>(new json_hub_protocol());
auto protocol = std::unique_ptr<hub_protocol>(new json_hub_protocol());
auto message = handshake::write_handshake(protocol);
ASSERT_STREQ("{\"protocol\":\"json\",\"version\":1}\x1e", message.c_str());

Просмотреть файл

@ -0,0 +1,232 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#include "stdafx.h"
#include "json_hub_protocol.h"
using namespace signalr;
void assert_signalr_value_equality(const signalr::value& expected, const signalr::value& actual)
{
ASSERT_EQ(expected.type(), actual.type());
switch (expected.type())
{
case value_type::string:
ASSERT_STREQ(expected.as_string().data(), actual.as_string().data());
break;
case value_type::boolean:
ASSERT_EQ(expected.as_bool(), actual.as_bool());
break;
case value_type::float64:
ASSERT_DOUBLE_EQ(expected.as_double(), actual.as_double());
break;
case value_type::map:
{
auto& expected_map = expected.as_map();
auto& actual_map = actual.as_map();
ASSERT_EQ(expected_map.size(), actual_map.size());
for (auto& pair : expected_map)
{
const auto& actual_found = actual_map.find(pair.first);
ASSERT_FALSE(actual_found == actual_map.end());
assert_signalr_value_equality(pair.second, actual_found->second);
}
break;
}
case value_type::array:
{
auto& expected_array = expected.as_array();
auto& actual_array = actual.as_array();
ASSERT_EQ(expected_array.size(), actual_array.size());
for (auto i = 0; i < expected_array.size(); ++i)
{
assert_signalr_value_equality(expected_array[i], actual_array[i]);
}
break;
}
case value_type::null:
break;
default:
ASSERT_TRUE(false);
break;
}
}
void assert_hub_message_equality(hub_message* expected, hub_message* actual)
{
ASSERT_EQ(expected->message_type, actual->message_type);
switch (expected->message_type)
{
case message_type::invocation:
{
auto expected_message = reinterpret_cast<invocation_message*>(expected);
auto actual_message = reinterpret_cast<invocation_message*>(actual);
ASSERT_STREQ(expected_message->invocation_id.data(), actual_message->invocation_id.data());
ASSERT_STREQ(expected_message->target.data(), actual_message->target.data());
assert_signalr_value_equality(expected_message->arguments, actual_message->arguments);
//stream_ids
break;
}
case message_type::completion:
{
auto expected_message = reinterpret_cast<completion_message*>(expected);
auto actual_message = reinterpret_cast<completion_message*>(actual);
ASSERT_STREQ(expected_message->invocation_id.data(), actual_message->invocation_id.data());
ASSERT_STREQ(expected_message->error.data(), actual_message->error.data());
assert_signalr_value_equality(expected_message->result, actual_message->result);
break;
}
case message_type::ping:
{
// No fields on ping messages currently
break;
}
default:
ASSERT_TRUE(false);
break;
}
}
std::vector<std::pair<std::string, std::shared_ptr<hub_message>>> protocol_test_data
{
// invocation message without invocation id
{ "{\"arguments\":[1,\"Foo\"],\"target\":\"Target\",\"type\":1}\x1e",
std::shared_ptr<hub_message>(new invocation_message("", "Target", std::vector<value>{ value(1.f), value("Foo") })) },
// invocation message with multiple arguments
{ "{\"arguments\":[1,\"Foo\"],\"invocationId\":\"123\",\"target\":\"Target\",\"type\":1}\x1e",
std::shared_ptr<hub_message>(new invocation_message("123", "Target", std::vector<value>{ value(1.f), value("Foo") })) },
// invocation message with bool argument
{ "{\"arguments\":[true],\"target\":\"Target\",\"type\":1}\x1e",
std::shared_ptr<hub_message>(new invocation_message("", "Target", std::vector<value>{ value(true) })) },
// invocation message with null argument
{ "{\"arguments\":[null],\"target\":\"Target\",\"type\":1}\x1e",
std::shared_ptr<hub_message>(new invocation_message("", "Target", std::vector<value>{ value(nullptr) })) },
// invocation message with no arguments
{ "{\"arguments\":[],\"target\":\"Target\",\"type\":1}\x1e",
std::shared_ptr<hub_message>(new invocation_message("", "Target", std::vector<value>{})) },
// invocation message with non-ascii string argument
/*{ "{\"arguments\":[\"\xD7\x9E\xD7\x97\xD7\xA8\xD7\x95\xD7\x96\xD7\xAA\x20\xD7\x9B\xD7\x9C\xD7\xA9\xD7\x94\xD7\x99\"],\"target\":\"Target\",\"type\":1}\x1e",
std::shared_ptr<hub_message>(new invocation_message("", "Target", std::vector<value>{ value("\xD7\x9E\xD7\x97\xD7\xA8\xD7\x95\xD7\x96\xD7\xAA\x20\xD7\x9B\xD7\x9C\xD7\xA9\xD7\x94\xD7\x99") })) },*/
// invocation message with object argument
{ "{\"arguments\":[{\"property\":5}],\"target\":\"Target\",\"type\":1}\x1e",
std::shared_ptr<hub_message>(new invocation_message("", "Target", std::vector<value>{ value(std::map<std::string, value>{ {"property", value(5.f)} }) })) },
// invocation message with array argument
{ "{\"arguments\":[[1,5]],\"target\":\"Target\",\"type\":1}\x1e",
std::shared_ptr<hub_message>(new invocation_message("", "Target", std::vector<value>{ value(std::vector<value>{value(1.f), value(5.f)}) })) },
// ping message
{ "{\"type\":6}\x1e",
std::shared_ptr<hub_message>(new ping_message()) },
// completion message with error
{ "{\"error\":\"error\",\"invocationId\":\"1\",\"type\":3}\x1e",
std::shared_ptr<hub_message>(new completion_message("1", "error", value())) },
// completion message with result
{ "{\"invocationId\":\"1\",\"result\":42,\"type\":3}\x1e",
std::shared_ptr<hub_message>(new completion_message("1", "", value(42.f))) },
};
TEST(json_hub_protocol, write_message)
{
for (auto& data : protocol_test_data)
{
auto output = json_hub_protocol().write_message(data.second.get());
ASSERT_STREQ(data.first.data(), output.data());
}
}
TEST(json_hub_protocol, parse_message)
{
for (auto& data : protocol_test_data)
{
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, parsing_field_order_does_not_matter)
{
invocation_message message = invocation_message("123", "Target", std::vector<value>{value(true)});
auto output = json_hub_protocol().parse_messages("{\"type\":1,\"invocationId\":\"123\",\"arguments\":[true],\"target\":\"Target\"}\x1e");
ASSERT_EQ(1, output.size());
assert_hub_message_equality(&message, output[0].get());
output = json_hub_protocol().parse_messages("{\"target\":\"Target\",\"invocationId\":\"123\",\"type\":1,\"arguments\":[true]}\x1e");
ASSERT_EQ(1, output.size());
assert_hub_message_equality(&message, output[0].get());
output = json_hub_protocol().parse_messages("{\"target\":\"Target\",\"arguments\":[true],\"type\":1,\"invocationId\":\"123\"}\x1e");
ASSERT_EQ(1, output.size());
assert_hub_message_equality(&message, output[0].get());
}
TEST(json_hub_protocol, can_parse_multiple_messages)
{
auto output = json_hub_protocol().parse_messages(std::string("{\"arguments\":[],\"target\":\"Target\",\"type\":1}\x1e") +
"{\"invocationId\":\"1\",\"result\":42,\"type\":3}\x1e");
ASSERT_EQ(2, output.size());
invocation_message invocation = invocation_message("", "Target", std::vector<value>{});
assert_hub_message_equality(&invocation, output[0].get());
completion_message completion = completion_message("1", "", value(42.f));
assert_hub_message_equality(&completion, output[1].get());
}
TEST(json_hub_protocol, extra_items_ignored_when_parsing)
{
invocation_message message = invocation_message("", "Target", std::vector<value>{value(true)});
auto output = json_hub_protocol().parse_messages("{\"type\":1,\"arguments\":[true],\"target\":\"Target\",\"extra\":\"ignored\"}\x1e");
ASSERT_EQ(1, output.size());
assert_hub_message_equality(&message, output[0].get());
}
std::vector<std::pair<std::string, std::string>> invalid_messages
{
{ "\x1e", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n* Line 1, Column 1\n A valid JSON document must be either an array or an object value.\n" },
{ "foo\x1e", "* Line 1, Column 1\n Syntax error: value, object or array expected.\n* Line 1, Column 2\n Extra non-whitespace after JSON value.\n" },
{ "[42]\x1e", "Message was not a 'map' type" },
{ "{\x1e", "* Line 1, Column 2\n Missing '}' or object member name\n" },
{ "{\"arguments\":[],\"target\":\"send\",\"invocationId\":42}\x1e", "Field 'type' not found" },
{ "{\"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'" },
{ "{\"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." },
};
TEST(json_hub_protocol, invalid_messages_throw)
{
for (auto& pair : invalid_messages)
{
try
{
json_hub_protocol().parse_messages(pair.first);
ASSERT_TRUE(false);
}
catch (const std::exception& exception)
{
ASSERT_STREQ(pair.second.data(), exception.what());
}
}
}

Просмотреть файл

@ -51,7 +51,7 @@ test_websocket_client::~test_websocket_client()
}
}
void test_websocket_client::start(const std::string& url, transfer_format, std::function<void(std::exception_ptr)> callback)
void test_websocket_client::start(const std::string& url, std::function<void(std::exception_ptr)> callback)
{
std::lock_guard<std::mutex> lock(m_receive_lock);
m_stopped = false;
@ -99,7 +99,7 @@ void test_websocket_client::stop(std::function<void(std::exception_ptr)> callbac
}).detach();
}
void test_websocket_client::send(const std::string& payload, std::function<void(std::exception_ptr)> callback)
void test_websocket_client::send(const std::string& payload, signalr::transfer_format, std::function<void(std::exception_ptr)> callback)
{
handshake_sent.cancel();
auto local_copy = m_send_function;

Просмотреть файл

@ -18,11 +18,11 @@ public:
test_websocket_client();
~test_websocket_client();
void start(const std::string& url, transfer_format format, std::function<void(std::exception_ptr)> callback);
void start(const std::string& url, std::function<void(std::exception_ptr)> callback);
void stop(std::function<void(std::exception_ptr)> callback);
void send(const std::string& payload, std::function<void(std::exception_ptr)> callback);
void send(const std::string& payload, signalr::transfer_format transfer_format, std::function<void(std::exception_ptr)> callback);
void receive(std::function<void(const std::string&, std::exception_ptr)> callback);

Просмотреть файл

@ -29,7 +29,7 @@ TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop)
auto ws_transport = websocket_transport::create([&](const signalr_client_config&) { return client; }, signalr_client_config{}, logger(writer, trace_level::info));
auto mre = manual_reset_event<void>();
ws_transport->start("ws://fakeuri.org/connect?param=42", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://fakeuri.org/connect?param=42", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -59,7 +59,7 @@ TEST(websocket_transport_connect, connect_propagates_exceptions)
try
{
auto mre = manual_reset_event<void>();
ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -84,7 +84,7 @@ TEST(websocket_transport_connect, connect_logs_exceptions)
auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(writer, trace_level::errors));
auto mre = manual_reset_event<void>();
ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -111,7 +111,7 @@ TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_trans
auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared<trace_log_writer>(), trace_level::none));
auto mre = manual_reset_event<void>();
ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -119,7 +119,7 @@ TEST(websocket_transport_connect, cannot_call_connect_on_already_connected_trans
try
{
ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -138,7 +138,7 @@ TEST(websocket_transport_connect, can_connect_after_disconnecting)
auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared<trace_log_writer>(), trace_level::none));
auto mre = manual_reset_event<void>();
ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -150,7 +150,7 @@ TEST(websocket_transport_connect, can_connect_after_disconnecting)
});
mre.get();
ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -172,13 +172,13 @@ TEST(websocket_transport_send, send_creates_and_sends_websocket_messages)
auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared<trace_log_writer>(), trace_level::none));
auto mre = manual_reset_event<void>();
ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://url", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
mre.get();
ws_transport->send("ABC", [&mre](std::exception_ptr exception)
ws_transport->send("ABC", transfer_format::text, [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -202,7 +202,7 @@ TEST(websocket_transport_disconnect, disconnect_closes_websocket)
auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared<trace_log_writer>(), trace_level::none));
auto mre = manual_reset_event<void>();
ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://url", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -231,7 +231,7 @@ TEST(websocket_transport_stop, propogates_exception_from_close)
auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared<trace_log_writer>(), trace_level::none));
auto mre = manual_reset_event<void>();
ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://url", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -264,7 +264,7 @@ TEST(websocket_transport_disconnect, disconnect_logs_exceptions)
auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(writer, trace_level::errors));
auto mre = manual_reset_event<void>();
ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://url", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -309,7 +309,7 @@ TEST(websocket_transport_disconnect, receive_not_called_after_disconnect)
auto ws_transport = websocket_transport::create([&](const signalr_client_config&){ return client; }, signalr_client_config{}, logger(std::make_shared<trace_log_writer>(), trace_level::none));
auto mre = manual_reset_event<void>();
ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr)
ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr)
{
mre.set();
});
@ -323,7 +323,7 @@ TEST(websocket_transport_disconnect, receive_not_called_after_disconnect)
});
mre.get();
ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -383,7 +383,7 @@ void receive_loop_logs_exception_runner(const T& e, const std::string& expected_
auto ws_transport = websocket_transport::create([&](const signalr_client_config&) { return client; }, signalr_client_config{}, logger(writer, trace_level));
auto mre = manual_reset_event<void>();
ws_transport->start("ws://url", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://url", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -434,7 +434,7 @@ TEST(websocket_transport_receive_loop, process_response_callback_called_when_mes
ws_transport->on_receive(process_response);
auto mre = manual_reset_event<void>();
ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});
@ -478,7 +478,7 @@ TEST(websocket_transport_receive_loop, error_callback_called_when_exception_thro
ws_transport->on_close(error_callback);
auto mre = manual_reset_event<void>();
ws_transport->start("ws://fakeuri.org", transfer_format::text, [&mre](std::exception_ptr exception)
ws_transport->start("ws://fakeuri.org", [&mre](std::exception_ptr exception)
{
mre.set(exception);
});