Add negotiation response support (#7675)

This commit is contained in:
BrennanConroy 2019-02-20 12:38:43 -08:00 committed by GitHub
parent 6fea3a6e95
commit e455c2c22b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 705 additions and 194 deletions

View File

@ -21,8 +21,7 @@ namespace signalr
public:
typedef std::function<void __cdecl(const utility::string_t&)> message_received_handler;
SIGNALRCLIENT_API explicit connection(const utility::string_t& url, const utility::string_t& query_string = _XPLATSTR(""),
trace_level trace_level = trace_level::all, std::shared_ptr<log_writer> log_writer = nullptr);
SIGNALRCLIENT_API explicit connection(const utility::string_t& url, trace_level trace_level = trace_level::all, std::shared_ptr<log_writer> log_writer = nullptr);
SIGNALRCLIENT_API ~connection();

View File

@ -22,8 +22,8 @@ namespace signalr
public:
typedef std::function<void __cdecl (const web::json::value&)> method_invoked_handler;
SIGNALRCLIENT_API explicit hub_connection(const utility::string_t& url, const utility::string_t& query_string = _XPLATSTR(""),
trace_level trace_level = trace_level::all, std::shared_ptr<log_writer> log_writer = nullptr);
SIGNALRCLIENT_API explicit hub_connection(const utility::string_t& url, trace_level trace_level = trace_level::all,
std::shared_ptr<log_writer> log_writer = nullptr);
SIGNALRCLIENT_API ~hub_connection();

View File

@ -41,7 +41,7 @@ void send_message(signalr::hub_connection& connection, const utility::string_t&
void chat(const utility::string_t& name)
{
signalr::hub_connection connection(U("http://localhost:5000/default"), U(""), signalr::trace_level::all, std::make_shared<logger>());
signalr::hub_connection connection(U("http://localhost:5000/default"), signalr::trace_level::all, std::make_shared<logger>());
connection.on(U("Send"), [](const web::json::value& m)
{
ucout << std::endl << m.at(0).as_string() << /*U(" wrote:") << m.at(1).as_string() <<*/ std::endl << U("Enter your message: ");

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\Build\SignalRClient.Build.Settings" />
<ItemGroup Label="ProjectConfigurations">
@ -36,7 +36,6 @@
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Import Project="..\..\packages\cpprestsdk.v140.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v140.windesktop.msvcstl.dyn.rt-dyn.targets" Condition="Exists('..\..\packages\cpprestsdk.v140.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v140.windesktop.msvcstl.dyn.rt-dyn.targets')" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
@ -110,10 +109,10 @@
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
<Error Condition="!Exists('..\..\packages\cpprestsdk.v140.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v140.windesktop.msvcstl.dyn.rt-dyn.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\cpprestsdk.v140.windesktop.msvcstl.dyn.rt-dyn.2.9.1\build\native\cpprestsdk.v140.windesktop.msvcstl.dyn.rt-dyn.targets'))" />
</Target>
<Target Name="AfterBuild">
<Exec Command="copy /y &quot;$(SolutionDir)bin\Desktop\$(Platform)\$(Configuration)\dll\$(SignalrClientTargetName).dll&quot; &quot;$(SolutionDir)$(Configuration)\$(SignalrClientTargetName).dll&quot;" />
<Exec Command="copy /y &quot;$(SolutionDir)bin\Desktop\$(Platform)\$(Configuration)\dll\$(SignalrClientTargetName).dll&quot; &quot;$(SolutionDir)bin\Desktop\$(Platform)\$(Configuration)\$(SignalrClientTargetName).dll&quot;" />
<Exec Command="copy /y &quot;$(SolutionDir)bin\Desktop\$(Platform)\$(Configuration)\dll\$(SignalrClientTargetName).pdb&quot; &quot;$(SolutionDir)bin\Desktop\$(Platform)\$(Configuration)\$(SignalrClientTargetName).pdb&quot;" />
</Target>
</Project>
</Project>

View File

@ -8,8 +8,8 @@
namespace signalr
{
connection::connection(const utility::string_t& url, const utility::string_t& query_string, trace_level trace_level, std::shared_ptr<log_writer> log_writer)
: m_pImpl(connection_impl::create(url, query_string, trace_level, std::move(log_writer)))
connection::connection(const utility::string_t& url, trace_level trace_level, std::shared_ptr<log_writer> log_writer)
: m_pImpl(connection_impl::create(url, trace_level, std::move(log_writer)))
{}
// Do NOT remove this destructor. Letting the compiler generate and inline the default dtor may lead to

View File

@ -21,22 +21,21 @@ namespace signalr
static void log(const logger& logger, trace_level level, const utility::string_t& entry);
}
std::shared_ptr<connection_impl> connection_impl::create(const utility::string_t& url, const utility::string_t& query_string,
trace_level trace_level, const std::shared_ptr<log_writer>& log_writer)
std::shared_ptr<connection_impl> connection_impl::create(const utility::string_t& url, trace_level trace_level, const std::shared_ptr<log_writer>& log_writer)
{
return connection_impl::create(url, query_string, trace_level, log_writer, std::make_unique<web_request_factory>(), std::make_unique<transport_factory>());
return connection_impl::create(url, trace_level, log_writer, std::make_unique<web_request_factory>(), std::make_unique<transport_factory>());
}
std::shared_ptr<connection_impl> connection_impl::create(const utility::string_t& url, const utility::string_t& query_string, trace_level trace_level,
const std::shared_ptr<log_writer>& log_writer, std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory)
std::shared_ptr<connection_impl> connection_impl::create(const utility::string_t& url, trace_level trace_level, const std::shared_ptr<log_writer>& log_writer,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory)
{
return std::shared_ptr<connection_impl>(new connection_impl(url, query_string, trace_level,
return std::shared_ptr<connection_impl>(new connection_impl(url, trace_level,
log_writer ? log_writer : std::make_shared<trace_log_writer>(), std::move(web_request_factory), std::move(transport_factory)));
}
connection_impl::connection_impl(const utility::string_t& url, const utility::string_t& query_string, trace_level trace_level, const std::shared_ptr<log_writer>& log_writer,
connection_impl::connection_impl(const utility::string_t& url, trace_level trace_level, const std::shared_ptr<log_writer>& log_writer,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory)
: m_base_url(url), m_query_string(query_string), m_connection_state(connection_state::disconnected), m_logger(log_writer, trace_level),
: m_base_url(url), m_connection_state(connection_state::disconnected), m_logger(log_writer, trace_level),
m_transport(nullptr), m_web_request_factory(std::move(web_request_factory)), m_transport_factory(std::move(transport_factory)),
m_message_received([](const utility::string_t&) noexcept {}), m_disconnected([]() noexcept {})
{ }
@ -84,94 +83,138 @@ namespace signalr
m_message_id = m_groups_token = m_connection_id = _XPLATSTR("");
}
return start_negotiate(m_base_url, 0);
}
pplx::task<void> connection_impl::start_negotiate(const web::uri& url, int redirect_count)
{
if (redirect_count >= MAX_NEGOTIATE_REDIRECTS)
{
return pplx::task_from_exception<void>(signalr_exception(_XPLATSTR("Negotiate redirection limit exceeded.")));
}
pplx::task_completion_event<void> start_tce;
auto weak_connection = weak_from_this();
pplx::task_from_result()
.then([weak_connection]()
.then([weak_connection, url]()
{
auto connection = weak_connection.lock();
if (!connection)
{
auto connection = weak_connection.lock();
if (!connection)
return pplx::task_from_exception<negotiation_response>(_XPLATSTR("connection no longer exists"));
}
return request_sender::negotiate(*connection->m_web_request_factory, url, connection->m_signalr_client_config);
}, m_disconnect_cts.get_token())
.then([weak_connection, start_tce, redirect_count, url](negotiation_response negotiation_response)
{
auto connection = weak_connection.lock();
if (!connection)
{
return pplx::task_from_exception<void>(_XPLATSTR("connection no longer exists"));
}
if (!negotiation_response.error.empty())
{
return pplx::task_from_exception<void>(signalr_exception(negotiation_response.error));
}
if (!negotiation_response.url.empty())
{
if (!negotiation_response.accessToken.empty())
{
return pplx::task_from_exception<negotiation_response>(_XPLATSTR("connection no longer exists"));
auto headers = connection->m_signalr_client_config.get_http_headers();
headers[_XPLATSTR("Authorization")] = _XPLATSTR("Bearer ") + negotiation_response.accessToken;
connection->m_signalr_client_config.set_http_headers(headers);
}
return request_sender::negotiate(*connection->m_web_request_factory, connection->m_base_url,
connection->m_query_string, connection->m_signalr_client_config);
}, m_disconnect_cts.get_token())
.then([weak_connection](negotiation_response negotiation_response)
return connection->start_negotiate(negotiation_response.url, redirect_count + 1);
}
connection->m_connection_id = std::move(negotiation_response.connectionId);
// TODO: fallback logic
bool foundWebsockets = false;
for (auto availableTransport : negotiation_response.availableTransports)
{
if (availableTransport.transport == _XPLATSTR("WebSockets"))
{
foundWebsockets = true;
break;
}
}
if (!foundWebsockets)
{
return pplx::task_from_exception<void>(signalr_exception(_XPLATSTR("The server does not support WebSockets which is currently the only transport supported by this client.")));
}
// TODO: use transfer format
return connection->start_transport(url)
.then([weak_connection, start_tce](std::shared_ptr<transport> transport)
{
auto connection = weak_connection.lock();
if (!connection)
{
return pplx::task_from_exception<void>(_XPLATSTR("connection no longer exists"));
}
connection->m_connection_id = std::move(negotiation_response.connection_id);
connection->m_transport = transport;
// TODO: check available transports
return connection->start_transport()
.then([weak_connection](std::shared_ptr<transport> transport)
{
auto connection = weak_connection.lock();
if (!connection)
{
return pplx::task_from_exception<void>(_XPLATSTR("connection no longer exists"));
}
connection->m_transport = transport;
return pplx::task_from_result();
});
}, m_disconnect_cts.get_token())
.then([start_tce, weak_connection](pplx::task<void> previous_task)
{
auto connection = weak_connection.lock();
if (!connection)
if (!connection->change_state(connection_state::connecting, connection_state::connected))
{
return pplx::task_from_exception<void>(_XPLATSTR("connection no longer exists"));
}
try
{
previous_task.get();
if (!connection->change_state(connection_state::connecting, connection_state::connected))
{
connection->m_logger.log(trace_level::errors,
utility::string_t(_XPLATSTR("internal error - transition from an unexpected state. expected state: connecting, actual state: "))
.append(translate_connection_state(connection->get_connection_state())));
connection->m_logger.log(trace_level::errors,
utility::string_t(_XPLATSTR("internal error - transition from an unexpected state. expected state: connecting, actual state: "))
.append(translate_connection_state(connection->get_connection_state())));
_ASSERTE(false);
}
connection->m_start_completed_event.set();
start_tce.set();
}
catch (const std::exception &e)
{
auto task_canceled_exception = dynamic_cast<const pplx::task_canceled *>(&e);
if (task_canceled_exception)
{
connection->m_logger.log(trace_level::info,
_XPLATSTR("starting the connection has been canceled."));
}
else
{
connection->m_logger.log(trace_level::errors,
utility::string_t(_XPLATSTR("connection could not be started due to: "))
.append(utility::conversions::to_string_t(e.what())));
}
connection->m_transport = nullptr;
connection->change_state(connection_state::disconnected);
connection->m_start_completed_event.set();
start_tce.set_exception(std::current_exception());
_ASSERTE(false);
}
return pplx::task_from_result();
});
}, m_disconnect_cts.get_token())
.then([start_tce, weak_connection](pplx::task<void> previous_task)
{
auto connection = weak_connection.lock();
if (!connection)
{
return pplx::task_from_exception<void>(_XPLATSTR("connection no longer exists"));
}
try
{
previous_task.get();
connection->m_start_completed_event.set();
start_tce.set();
}
catch (const std::exception & e)
{
auto task_canceled_exception = dynamic_cast<const pplx::task_canceled*>(&e);
if (task_canceled_exception)
{
connection->m_logger.log(trace_level::info,
_XPLATSTR("starting the connection has been canceled."));
}
else
{
connection->m_logger.log(trace_level::errors,
utility::string_t(_XPLATSTR("connection could not be started due to: "))
.append(utility::conversions::to_string_t(e.what())));
}
connection->m_transport = nullptr;
connection->change_state(connection_state::disconnected);
connection->m_start_completed_event.set();
start_tce.set_exception(std::current_exception());
}
return pplx::task_from_result();
});
return pplx::create_task(start_tce);
}
pplx::task<std::shared_ptr<transport>> connection_impl::start_transport()
pplx::task<std::shared_ptr<transport>> connection_impl::start_transport(const web::uri& url)
{
auto connection = shared_from_this();
@ -247,18 +290,15 @@ namespace signalr
}
});
return connection->send_connect_request(transport, connect_request_tce)
return connection->send_connect_request(transport, url, connect_request_tce)
.then([transport](){ return pplx::task_from_result(transport); });
}
pplx::task<void> connection_impl::send_connect_request(const std::shared_ptr<transport>& transport, const pplx::task_completion_event<void>& connect_request_tce)
pplx::task<void> connection_impl::send_connect_request(const std::shared_ptr<transport>& transport, const web::uri& url, const pplx::task_completion_event<void>& connect_request_tce)
{
auto logger = m_logger;
auto query_string = m_query_string;
if (!query_string.empty())
query_string.append(_XPLATSTR("&"));
query_string.append(_XPLATSTR("id=")).append(m_connection_id);
auto connect_url = url_builder::build_connect(m_base_url, transport->get_transport_type(), query_string);
auto query_string = _XPLATSTR("id=" + m_connection_id);
auto connect_url = url_builder::build_connect(url, transport->get_transport_type(), query_string);
transport->connect(connect_url)
.then([transport, connect_request_tce, logger](pplx::task<void> connect_task)

View File

@ -25,11 +25,10 @@ namespace signalr
class connection_impl : public std::enable_shared_from_this<connection_impl>
{
public:
static std::shared_ptr<connection_impl> create(const utility::string_t& url, const utility::string_t& query_string,
trace_level trace_level, const std::shared_ptr<log_writer>& log_writer);
static std::shared_ptr<connection_impl> create(const utility::string_t& url, trace_level trace_level, const std::shared_ptr<log_writer>& log_writer);
static std::shared_ptr<connection_impl> create(const utility::string_t& url, const utility::string_t& query_string, trace_level trace_level,
const std::shared_ptr<log_writer>& log_writer, std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory);
static std::shared_ptr<connection_impl> create(const utility::string_t& url, trace_level trace_level, const std::shared_ptr<log_writer>& log_writer,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory);
connection_impl(const connection_impl&) = delete;
@ -50,7 +49,6 @@ namespace signalr
private:
web::uri m_base_url;
utility::string_t m_query_string;
std::atomic<connection_state> m_connection_state;
logger m_logger;
std::shared_ptr<transport> m_transport;
@ -69,12 +67,13 @@ namespace signalr
utility::string_t m_message_id;
utility::string_t m_groups_token;
connection_impl(const utility::string_t& url, const utility::string_t& query_string, trace_level trace_level, const std::shared_ptr<log_writer>& log_writer,
connection_impl(const utility::string_t& url, trace_level trace_level, const std::shared_ptr<log_writer>& log_writer,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory);
pplx::task<std::shared_ptr<transport>> start_transport();
pplx::task<std::shared_ptr<transport>> start_transport(const web::uri& url);
pplx::task<void> send_connect_request(const std::shared_ptr<transport>& transport,
const pplx::task_completion_event<void>& connect_request_tce);
const web::uri& url, const pplx::task_completion_event<void>& connect_request_tce);
pplx::task<void> start_negotiate(const web::uri& url, int redirect_count);
void process_response(const utility::string_t& response);

View File

@ -8,3 +8,4 @@
#define SIGNALR_VERSION _XPLATSTR("0.1.0-alpha0")
#define PROTOCOL _XPLATSTR("1.4")
#define USER_AGENT _XPLATSTR("SignalR.Client.Cpp/") SIGNALR_VERSION
#define MAX_NEGOTIATE_REDIRECTS 100

View File

@ -8,9 +8,9 @@
namespace signalr
{
hub_connection::hub_connection(const utility::string_t& url, const utility::string_t& query_string,
hub_connection::hub_connection(const utility::string_t& url,
trace_level trace_level, std::shared_ptr<log_writer> log_writer)
: m_pImpl(hub_connection_impl::create(url, query_string, trace_level, std::move(log_writer)))
: m_pImpl(hub_connection_impl::create(url, trace_level, std::move(log_writer)))
{}
// Do NOT remove this destructor. Letting the compiler generate and inline the default dtor may lead to

View File

@ -20,18 +20,18 @@ namespace signalr
const std::function<void(const std::exception_ptr e)>& set_exception);
}
std::shared_ptr<hub_connection_impl> hub_connection_impl::create(const utility::string_t& url, const utility::string_t& query_string,
trace_level trace_level, const std::shared_ptr<log_writer>& log_writer)
std::shared_ptr<hub_connection_impl> hub_connection_impl::create(const utility::string_t& url, trace_level trace_level,
const std::shared_ptr<log_writer>& log_writer)
{
return hub_connection_impl::create(url, query_string, trace_level, log_writer,
return hub_connection_impl::create(url, trace_level, log_writer,
std::make_unique<web_request_factory>(), std::make_unique<transport_factory>());
}
std::shared_ptr<hub_connection_impl> hub_connection_impl::create(const utility::string_t& url, const utility::string_t& query_string,
trace_level trace_level, const std::shared_ptr<log_writer>& log_writer, std::unique_ptr<web_request_factory> web_request_factory,
std::shared_ptr<hub_connection_impl> hub_connection_impl::create(const utility::string_t& url, trace_level trace_level,
const std::shared_ptr<log_writer>& log_writer, std::unique_ptr<web_request_factory> web_request_factory,
std::unique_ptr<transport_factory> transport_factory)
{
auto connection = std::shared_ptr<hub_connection_impl>(new hub_connection_impl(url, query_string, trace_level,
auto connection = std::shared_ptr<hub_connection_impl>(new hub_connection_impl(url, trace_level,
log_writer ? log_writer : std::make_shared<trace_log_writer>(), std::move(web_request_factory), std::move(transport_factory)));
connection->initialize();
@ -39,10 +39,10 @@ namespace signalr
return connection;
}
hub_connection_impl::hub_connection_impl(const utility::string_t& url, const utility::string_t& query_string, trace_level trace_level,
hub_connection_impl::hub_connection_impl(const utility::string_t& url, trace_level trace_level,
const std::shared_ptr<log_writer>& log_writer, std::unique_ptr<web_request_factory> web_request_factory,
std::unique_ptr<transport_factory> transport_factory)
: m_connection(connection_impl::create(url, query_string, trace_level, log_writer,
: m_connection(connection_impl::create(url, trace_level, log_writer,
std::move(web_request_factory), std::move(transport_factory))),m_logger(log_writer, trace_level),
m_callback_manager(json::value::parse(_XPLATSTR("{ \"error\" : \"connection went out of scope before invocation result was received\"}"))),
m_disconnected([]() noexcept {}), m_handshakeReceived(false)
@ -106,6 +106,7 @@ namespace signalr
_XPLATSTR("the connection can only be started if it is in the disconnected state"));
}
m_connection->set_client_config(m_signalr_client_config);
m_handshakeTask = pplx::task_completion_event<void>();
m_handshakeReceived = false;
auto weak_connection = weak_from_this();
@ -138,14 +139,17 @@ namespace signalr
previous_task.get();
return previous_task;
}
catch (std::exception)
catch (std::exception e)
{
auto connection = weak_connection.lock();
if (connection)
{
connection->m_connection->stop();
return connection->m_connection->stop()
.then([e]() {
throw e;
});
}
throw;
throw e;
}
});
});
@ -355,6 +359,7 @@ namespace signalr
void hub_connection_impl::set_client_config(const signalr_client_config& config)
{
m_signalr_client_config = config;
m_connection->set_client_config(config);
}

View File

@ -21,12 +21,12 @@ 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 utility::string_t& url, const utility::string_t& query_string,
trace_level trace_level, const std::shared_ptr<log_writer>& log_writer);
static std::shared_ptr<hub_connection_impl> create(const utility::string_t& url, trace_level trace_level,
const std::shared_ptr<log_writer>& log_writer);
static std::shared_ptr<hub_connection_impl> create(const utility::string_t& url, const utility::string_t& query_string,
trace_level trace_level, const std::shared_ptr<log_writer>& log_writer,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory);
static std::shared_ptr<hub_connection_impl> create(const utility::string_t& url, trace_level trace_level,
const std::shared_ptr<log_writer>& log_writer, std::unique_ptr<web_request_factory> web_request_factory,
std::unique_ptr<transport_factory> transport_factory);
hub_connection_impl(const hub_connection_impl&) = delete;
hub_connection_impl& operator=(const hub_connection_impl&) = delete;
@ -46,9 +46,8 @@ namespace signalr
void set_disconnected(const std::function<void()>& disconnected);
private:
hub_connection_impl(const utility::string_t& url, const utility::string_t& query_string, trace_level trace_level,
const std::shared_ptr<log_writer>& log_writer, std::unique_ptr<web_request_factory> web_request_factory,
std::unique_ptr<transport_factory> transport_factory);
hub_connection_impl(const utility::string_t& url, trace_level trace_level, const std::shared_ptr<log_writer>& log_writer,
std::unique_ptr<web_request_factory> web_request_factory, std::unique_ptr<transport_factory> transport_factory);
std::shared_ptr<connection_impl> m_connection;
logger m_logger;
@ -57,6 +56,7 @@ namespace signalr
bool m_handshakeReceived;
pplx::task_completion_event<void> m_handshakeTask;
std::function<void()> m_disconnected;
signalr_client_config m_signalr_client_config;
void initialize();

View File

@ -7,9 +7,18 @@
namespace signalr
{
struct available_transport
{
utility::string_t transport;
std::vector<utility::string_t> transfer_formats;
};
struct negotiation_response
{
utility::string_t connection_id;
web::json::value availableTransports;
utility::string_t connectionId;
std::vector<available_transport> availableTransports;
utility::string_t url;
utility::string_t accessToken;
utility::string_t error;
};
}
}

View File

@ -12,19 +12,58 @@ namespace signalr
namespace request_sender
{
pplx::task<negotiation_response> negotiate(web_request_factory& request_factory, const web::uri& base_url,
const utility::string_t& query_string, const signalr_client_config& signalr_client_config)
const signalr_client_config& signalr_client_config)
{
auto negotiate_url = url_builder::build_negotiate(base_url, query_string);
auto negotiate_url = url_builder::build_negotiate(base_url);
return http_sender::post(request_factory, negotiate_url, signalr_client_config)
.then([](utility::string_t body)
{
auto negotiation_response_json = web::json::value::parse(body);
negotiation_response response
negotiation_response response;
if (negotiation_response_json.has_field(_XPLATSTR("error")))
{
negotiation_response_json[_XPLATSTR("connectionId")].as_string(),
negotiation_response_json[_XPLATSTR("availableTransports")],
};
response.error = negotiation_response_json[_XPLATSTR("error")].as_string();
return std::move(response);
}
if (negotiation_response_json.has_field(_XPLATSTR("connectionId")))
{
response.connectionId = negotiation_response_json[_XPLATSTR("connectionId")].as_string();
}
if (negotiation_response_json.has_field(_XPLATSTR("availableTransports")))
{
for (auto transportData : negotiation_response_json[_XPLATSTR("availableTransports")].as_array())
{
available_transport transport;
transport.transport = transportData[_XPLATSTR("transport")].as_string();
for (auto format : transportData[_XPLATSTR("transferFormats")].as_array())
{
transport.transfer_formats.push_back(format.as_string());
}
response.availableTransports.push_back(transport);
}
}
if (negotiation_response_json.has_field(_XPLATSTR("url")))
{
response.url = negotiation_response_json[_XPLATSTR("url")].as_string();
if (negotiation_response_json.has_field(_XPLATSTR("accessToken")))
{
response.accessToken = negotiation_response_json[_XPLATSTR("accessToken")].as_string();
}
}
if (negotiation_response_json.has_field(_XPLATSTR("ProtocolVersion")))
{
throw signalr_exception(_XPLATSTR("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details."));
}
return std::move(response);
});

View File

@ -15,6 +15,6 @@ namespace signalr
namespace request_sender
{
pplx::task<negotiation_response> negotiate(web_request_factory& request_factory, const web::uri &base_url,
const utility::string_t& query_string, const signalr_client_config& signalr_client_config = signalr::signalr_client_config{});
const signalr_client_config& signalr_client_config = signalr::signalr_client_config{});
}
}
}

View File

@ -76,23 +76,6 @@ namespace signalr
return builder;
}
web::uri_builder build_uri(const web::uri& base_url, const utility::string_t& command, transport_type transport,
const utility::string_t& connection_data, const utility::string_t& query_string,
const utility::string_t& last_message_id = _XPLATSTR(""), const utility::string_t& groups_token = _XPLATSTR(""))
{
_ASSERTE(last_message_id.length() == 0 && groups_token.length() == 0);
web::uri_builder builder(base_url);
builder.append_path(command);
append_transport(builder, transport);
builder.append_query(_XPLATSTR("clientProtocol"), PROTOCOL);
//append_connection_token(builder, connection_token);
append_connection_data(builder, connection_data);
append_message_id(builder, last_message_id);
append_groups_token(builder, groups_token);
return builder.append_query(query_string);
}
web::uri_builder build_uri(const web::uri& base_url, const utility::string_t& command, const utility::string_t& query_string)
{
web::uri_builder builder(base_url);
@ -100,9 +83,15 @@ namespace signalr
return builder.append_query(query_string);
}
web::uri build_negotiate(const web::uri& base_url, const utility::string_t& query_string)
web::uri_builder build_uri(const web::uri& base_url, const utility::string_t& command)
{
return build_uri(base_url, _XPLATSTR("negotiate"), query_string).to_uri();
web::uri_builder builder(base_url);
return builder.append_path(command);
}
web::uri build_negotiate(const web::uri& base_url)
{
return build_uri(base_url, _XPLATSTR("negotiate")).to_uri();
}
web::uri build_connect(const web::uri& base_url, transport_type transport, const utility::string_t& query_string)
@ -117,11 +106,5 @@ namespace signalr
{
return build_uri(base_url, _XPLATSTR(""), query_string).to_uri();
}
web::uri build_abort(const web::uri &base_url, transport_type transport,
const utility::string_t& connection_data, const utility::string_t &query_string)
{
return build_uri(base_url, _XPLATSTR("abort"), transport, connection_data, query_string).to_uri();
}
}
}

View File

@ -10,10 +10,8 @@ namespace signalr
{
namespace url_builder
{
web::uri build_negotiate(const web::uri& base_url, const utility::string_t& query_string);
web::uri build_negotiate(const web::uri& base_url);
web::uri build_connect(const web::uri& base_url, transport_type transport, const utility::string_t& query_string);
web::uri build_start(const web::uri& base_url, const utility::string_t& query_string);
web::uri build_abort(const web::uri &base_url, transport_type transport,
const utility::string_t& connection_data, const utility::string_t& query_string);
}
}

View File

@ -30,7 +30,7 @@ TEST(hub_connection_tests, connection_status_start_stop_start)
TEST(hub_connection_tests, send_message)
{
auto hub_conn = std::make_shared<signalr::hub_connection>(url + U("custom"), U(""), signalr::trace_level::all, nullptr);
auto hub_conn = std::make_shared<signalr::hub_connection>(url + U("custom"), signalr::trace_level::all, nullptr);
auto message = std::make_shared<utility::string_t>();
auto received_event = std::make_shared<signalr::event>();

View File

@ -19,14 +19,14 @@ using namespace signalr;
static std::shared_ptr<connection_impl> create_connection(std::shared_ptr<websocket_client> websocket_client = create_test_websocket_client(),
std::shared_ptr<log_writer> log_writer = std::make_shared<trace_log_writer>(), trace_level trace_level = trace_level::all)
{
return connection_impl::create(create_uri(), _XPLATSTR(""), trace_level, log_writer, create_test_web_request_factory(),
return connection_impl::create(create_uri(), trace_level, log_writer, create_test_web_request_factory(),
std::make_unique<test_transport_factory>(websocket_client));
}
TEST(connection_impl_connection_state, initial_connection_state_is_disconnected)
{
auto connection =
connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>());
connection_impl::create(create_uri(), trace_level::none, std::make_shared<trace_log_writer>());
ASSERT_EQ(connection_state::disconnected, connection->get_connection_state());
}
@ -98,7 +98,7 @@ TEST(connection_impl_start, connection_state_is_disconnected_when_connection_can
});
auto connection =
connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>(),
connection_impl::create(create_uri(), trace_level::none, std::make_shared<trace_log_writer>(),
std::move(web_request_factory), std::make_unique<transport_factory>());
try
@ -111,6 +111,60 @@ TEST(connection_impl_start, connection_state_is_disconnected_when_connection_can
ASSERT_EQ(connection->get_connection_state(), connection_state::disconnected);
}
TEST(connection_impl_start, start_sets_id_query_string)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
utility::string_t query_string;
auto websocket_client = create_test_websocket_client(
/* receive function */ []() { return pplx::task_from_exception<std::string>(std::runtime_error("should not be invoked")); },
/* send function */ [](const utility::string_t) { return pplx::task_from_exception<void>(std::runtime_error("should not be invoked")); },
/* connect function */[&query_string](const web::uri& url)
{
query_string = url.query();
return pplx::task_from_exception<void>(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed")));
});
auto connection = connection_impl::create(create_uri(_XPLATSTR("")), trace_level::errors, writer, create_test_web_request_factory(), std::make_unique<test_transport_factory>(websocket_client));
try
{
connection->start().get();
}
catch (...)
{
}
ASSERT_EQ(_XPLATSTR("id=f7707523-307d-4cba-9abf-3eef701241e8"), query_string);
}
TEST(connection_impl_start, start_appends_id_query_string)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
utility::string_t query_string;
auto websocket_client = create_test_websocket_client(
/* receive function */ []() { return pplx::task_from_exception<std::string>(std::runtime_error("should not be invoked")); },
/* send function */ [](const utility::string_t) { return pplx::task_from_exception<void>(std::runtime_error("should not be invoked")); },
/* connect function */[&query_string](const web::uri& url)
{
query_string = url.query();
return pplx::task_from_exception<void>(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed")));
});
auto connection = connection_impl::create(create_uri(_XPLATSTR("a=b&c=d")), trace_level::errors, writer, create_test_web_request_factory(), std::make_unique<test_transport_factory>(websocket_client));
try
{
connection->start().get();
}
catch (...)
{
}
ASSERT_EQ(_XPLATSTR("a=b&c=d&id=f7707523-307d-4cba-9abf-3eef701241e8"), query_string);
}
TEST(connection_impl_start, start_logs_exceptions)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
@ -121,7 +175,7 @@ TEST(connection_impl_start, start_logs_exceptions)
});
auto connection =
connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::errors, writer,
connection_impl::create(create_uri(), trace_level::errors, writer,
std::move(web_request_factory), std::make_unique<transport_factory>());
try
@ -138,6 +192,7 @@ TEST(connection_impl_start, start_logs_exceptions)
ASSERT_EQ(_XPLATSTR("[error ] connection could not be started due to: web exception - 404 Bad request\n"), entry);
}
TEST(connection_impl_start, start_propagates_exceptions_from_negotiate)
{
auto web_request_factory = std::make_unique<test_web_request_factory>([](const web::uri &) -> std::unique_ptr<web_request>
@ -146,7 +201,7 @@ TEST(connection_impl_start, start_propagates_exceptions_from_negotiate)
});
auto connection =
connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>(),
connection_impl::create(create_uri(), trace_level::none, std::make_shared<trace_log_writer>(),
std::move(web_request_factory), std::make_unique<transport_factory>());
try
@ -205,7 +260,7 @@ TEST(connection_impl_start, DISABLED_start_fails_if_no_available_transports)
auto websocket_client = std::make_shared<test_websocket_client>();
auto connection =
connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::errors, std::make_shared<trace_log_writer>(),
connection_impl::create(create_uri(), trace_level::errors, std::make_shared<trace_log_writer>(),
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
try
@ -271,7 +326,7 @@ TEST(connection_impl_start, start_fails_if_negotiate_request_fails)
});
auto connection =
connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::messages, writer,
connection_impl::create(create_uri(), trace_level::messages, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
try
@ -285,16 +340,15 @@ TEST(connection_impl_start, start_fails_if_negotiate_request_fails)
}
}
TEST(connection_impl_start, start_fails_if_connect_request_times_out)
TEST(connection_impl_start, start_fails_if_negotiate_response_has_error)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
auto web_request_factory = std::make_unique<test_web_request_factory>([](const web::uri& url)
auto web_request_factory = std::make_unique<test_web_request_factory>([](const web::uri & url)
{
auto response_body =
url.path() == _XPLATSTR("/negotiate")
? _XPLATSTR("{ \"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", ")
_XPLATSTR("\"availableTransports\" : [] }")
? _XPLATSTR("{ \"error\": \"bad negotiate\" }")
: _XPLATSTR("");
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
@ -308,7 +362,379 @@ TEST(connection_impl_start, start_fails_if_connect_request_times_out)
});
auto connection =
connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::messages, writer,
connection_impl::create(create_uri(), trace_level::messages, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
try
{
connection->start().get();
ASSERT_TRUE(false); // exception not thrown
}
catch (const signalr_exception & e)
{
ASSERT_STREQ("bad negotiate", e.what());
}
}
TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_websockets)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
auto web_request_factory = std::make_unique<test_web_request_factory>([](const web::uri & url)
{
auto response_body =
url.path() == _XPLATSTR("/negotiate")
? _XPLATSTR("{ \"availableTransports\": [ { \"transport\": \"ServerSentEvents\", \"transferFormats\": [ \"Text\" ] } ] }")
: _XPLATSTR("");
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
});
pplx::task_completion_event<void> tce;
auto websocket_client = std::make_shared<test_websocket_client>();
websocket_client->set_connect_function([tce](const web::uri&) mutable
{
return pplx::task<void>(tce);
});
auto connection =
connection_impl::create(create_uri(), trace_level::messages, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
try
{
connection->start().get();
ASSERT_TRUE(false); // exception not thrown
}
catch (const signalr_exception & e)
{
ASSERT_STREQ("The server does not support WebSockets which is currently the only transport supported by this client.", e.what());
}
}
TEST(connection_impl_start, start_fails_if_negotiate_response_does_not_have_transports)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
auto web_request_factory = std::make_unique<test_web_request_factory>([](const web::uri & url)
{
auto response_body =
url.path() == _XPLATSTR("/negotiate")
? _XPLATSTR("{ \"availableTransports\": [ ] }")
: _XPLATSTR("");
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
});
pplx::task_completion_event<void> tce;
auto websocket_client = std::make_shared<test_websocket_client>();
websocket_client->set_connect_function([tce](const web::uri&) mutable
{
return pplx::task<void>(tce);
});
auto connection =
connection_impl::create(create_uri(), trace_level::messages, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
try
{
connection->start().get();
ASSERT_TRUE(false); // exception not thrown
}
catch (const signalr_exception & e)
{
ASSERT_STREQ("The server does not support WebSockets which is currently the only transport supported by this client.", e.what());
}
}
TEST(connection_impl_start, start_fails_if_negotiate_response_is_invalid)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
auto web_request_factory = std::make_unique<test_web_request_factory>([](const web::uri & url)
{
auto response_body =
url.path() == _XPLATSTR("/negotiate")
? _XPLATSTR("{ \"availableTransports\": [ ")
: _XPLATSTR("");
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
});
pplx::task_completion_event<void> tce;
auto websocket_client = std::make_shared<test_websocket_client>();
websocket_client->set_connect_function([tce](const web::uri&) mutable
{
return pplx::task<void>(tce);
});
auto connection =
connection_impl::create(create_uri(), trace_level::messages, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
try
{
connection->start().get();
ASSERT_TRUE(false); // exception not thrown
}
catch (const std::exception & e)
{
ASSERT_STREQ("* Line 1, Column 28 Syntax error: Malformed token", e.what());
}
}
TEST(connection_impl_start, negotiate_follows_redirect)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
auto web_request_factory = std::make_unique<test_web_request_factory>([](const web::uri & url)
{
utility::string_t response_body = _XPLATSTR("");
if (url.path() == _XPLATSTR("/negotiate"))
{
if (url.host() == _XPLATSTR("redirected"))
{
response_body = _XPLATSTR("{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", ")
_XPLATSTR("\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }");
}
else
{
response_body = _XPLATSTR("{ \"url\": \"http://redirected\" }");
}
}
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
});
auto websocket_client = std::make_shared<test_websocket_client>();
utility::string_t connectUrl;
websocket_client->set_connect_function([&connectUrl](const web::uri& url)
{
connectUrl = url.to_string();
return pplx::task_from_result();
});
auto connection =
connection_impl::create(create_uri(), trace_level::messages, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
connection->start().get();
ASSERT_EQ(_XPLATSTR("ws://redirected/?id=f7707523-307d-4cba-9abf-3eef701241e8"), connectUrl);
}
TEST(connection_impl_start, negotiate_redirect_uses_accessToken)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
utility::string_t accessToken;
auto web_request_factory = std::make_unique<test_web_request_factory>([&accessToken](const web::uri & url)
{
utility::string_t response_body = _XPLATSTR("");
if (url.path() == _XPLATSTR("/negotiate"))
{
if (url.host() == _XPLATSTR("redirected"))
{
response_body = _XPLATSTR("{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", ")
_XPLATSTR("\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }");
}
else
{
response_body = _XPLATSTR("{ \"url\": \"http://redirected\", \"accessToken\": \"secret\" }");
}
}
auto request = new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body);
request->on_get_response = [&accessToken](web_request_stub& stub)
{
accessToken = stub.m_signalr_client_config.get_http_headers()[_XPLATSTR("Authorization")];
};
return std::unique_ptr<web_request>(request);
});
auto websocket_client = std::make_shared<test_websocket_client>();
utility::string_t connectUrl;
websocket_client->set_connect_function([&connectUrl](const web::uri& url)
{
connectUrl = url.to_string();
return pplx::task_from_result();
});
auto connection =
connection_impl::create(create_uri(), trace_level::messages, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
connection->start().get();
ASSERT_EQ(_XPLATSTR("ws://redirected/?id=f7707523-307d-4cba-9abf-3eef701241e8"), connectUrl);
ASSERT_EQ(_XPLATSTR("Bearer secret"), accessToken);
}
TEST(connection_impl_start, negotiate_fails_after_too_many_redirects)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
auto web_request_factory = std::make_unique<test_web_request_factory>([](const web::uri & url)
{
utility::string_t response_body = _XPLATSTR("");
if (url.path() == _XPLATSTR("/negotiate"))
{
// infinite redirect
response_body = _XPLATSTR("{ \"url\": \"http://redirected\" }");
}
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
});
auto websocket_client = std::make_shared<test_websocket_client>();
auto connection =
connection_impl::create(create_uri(), trace_level::messages, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
try
{
connection->start().get();
}
catch (signalr_exception e)
{
ASSERT_STREQ("Negotiate redirection limit exceeded.", e.what());
}
}
TEST(connection_impl_start, negotiate_fails_if_ProtocolVersion_in_response)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
auto web_request_factory = std::make_unique<test_web_request_factory>([](const web::uri & url)
{
utility::string_t response_body = _XPLATSTR("");
if (url.path() == _XPLATSTR("/negotiate"))
{
response_body = _XPLATSTR("{\"ProtocolVersion\" : \"\" }");
}
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
});
auto websocket_client = std::make_shared<test_websocket_client>();
auto connection =
connection_impl::create(create_uri(), trace_level::messages, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
try
{
connection->start().get();
}
catch (signalr_exception e)
{
ASSERT_STREQ("Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.", e.what());
}
}
TEST(connection_impl_start, negotiate_redirect_does_not_overwrite_url)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
int redirectCount = 0;
auto web_request_factory = std::make_unique<test_web_request_factory>([&redirectCount](const web::uri & url)
{
utility::string_t response_body = _XPLATSTR("");
if (url.path() == _XPLATSTR("/negotiate"))
{
if (url.host() == _XPLATSTR("redirected"))
{
response_body = _XPLATSTR("{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", ")
_XPLATSTR("\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }");
}
else
{
response_body = _XPLATSTR("{ \"url\": \"http://redirected\" }");
redirectCount++;
}
}
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
});
auto websocket_client = std::make_shared<test_websocket_client>();
auto connection =
connection_impl::create(create_uri(), trace_level::messages, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
connection->start().get();
ASSERT_EQ(1, redirectCount);
connection->stop().get();
connection->start().get();
ASSERT_EQ(2, redirectCount);
}
TEST(connection_impl_start, negotiate_redirect_uses_own_query_string)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
utility::string_t query_string;
auto websocket_client = create_test_websocket_client(
/* receive function */ []() { return pplx::task_from_exception<std::string>(std::runtime_error("should not be invoked")); },
/* send function */ [](const utility::string_t) { return pplx::task_from_exception<void>(std::runtime_error("should not be invoked")); },
/* connect function */[&query_string](const web::uri& url)
{
query_string = url.query();
return pplx::task_from_exception<void>(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed")));
});
auto web_request_factory = std::make_unique<test_web_request_factory>([](const web::uri & url)
{
utility::string_t response_body = _XPLATSTR("");
if (url.path() == _XPLATSTR("/negotiate"))
{
if (url.host() == _XPLATSTR("redirected"))
{
response_body = _XPLATSTR("{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", ")
_XPLATSTR("\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }");
}
else
{
response_body = _XPLATSTR("{ \"url\": \"http://redirected?customQuery=1\" }");
}
}
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
});
auto connection = connection_impl::create(create_uri(_XPLATSTR("a=b&c=d")), trace_level::errors, writer, std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
try
{
connection->start().get();
}
catch (...)
{
}
ASSERT_EQ(_XPLATSTR("customQuery=1&id=f7707523-307d-4cba-9abf-3eef701241e8"), query_string);
}
TEST(connection_impl_start, start_fails_if_connect_request_times_out)
{
std::shared_ptr<log_writer> writer(std::make_shared<memory_log_writer>());
auto web_request_factory = create_test_web_request_factory();
pplx::task_completion_event<void> tce;
auto websocket_client = std::make_shared<test_websocket_client>();
websocket_client->set_connect_function([tce](const web::uri&) mutable
{
return pplx::task<void>(tce);
});
auto connection =
connection_impl::create(create_uri(), trace_level::messages, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
try
@ -373,7 +799,7 @@ TEST(connection_impl_send, message_sent)
TEST(connection_impl_send, send_throws_if_connection_not_connected)
{
auto connection =
connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>());
connection_impl::create(create_uri(), trace_level::none, std::make_shared<trace_log_writer>());
try
{
@ -674,7 +1100,7 @@ TEST(connection_impl_set_configuration, set_disconnected_callback_can_be_set_onl
TEST(connection_impl_stop, stopping_disconnected_connection_is_no_op)
{
std::shared_ptr<log_writer> writer{ std::make_shared<memory_log_writer>() };
auto connection = connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::all, writer);
auto connection = connection_impl::create(create_uri(), trace_level::all, writer);
connection->stop().get();
ASSERT_EQ(connection_state::disconnected, connection->get_connection_state());
@ -885,7 +1311,7 @@ TEST(connection_impl_stop, ongoing_start_request_canceled_if_connection_stopped_
});
auto writer = std::shared_ptr<log_writer>{std::make_shared<memory_log_writer>()};
auto connection = connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::all, writer,
auto connection = connection_impl::create(create_uri(), trace_level::all, writer,
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
auto start_task = connection->start();
@ -1003,8 +1429,8 @@ TEST(connection_impl_config, custom_headers_set_in_requests)
{
auto response_body =
url.path() == _XPLATSTR("/negotiate")
? _XPLATSTR("{ \"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", ")
_XPLATSTR("\"availableTransports\" : [] }")
? _XPLATSTR("{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", ")
_XPLATSTR("\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }")
: _XPLATSTR("");
auto request = new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body);
@ -1022,7 +1448,7 @@ TEST(connection_impl_config, custom_headers_set_in_requests)
/* receive function */ []() { return pplx::task_from_result(std::string("{ }\x1e")); });
auto connection =
connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::state_changes,
connection_impl::create(create_uri(), trace_level::state_changes,
writer, std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
signalr::signalr_client_config signalr_client_config{};
@ -1149,8 +1575,8 @@ TEST(connection_id, connection_id_reset_when_starting_connection)
if (!fail_http_requests) {
auto response_body =
url.path() == _XPLATSTR("/negotiate")
? _XPLATSTR("{ \"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", ")
_XPLATSTR("\"availableTransports\" : [] }")
? _XPLATSTR("{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", ")
_XPLATSTR("\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }")
: _XPLATSTR("");
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
@ -1160,7 +1586,7 @@ TEST(connection_id, connection_id_reset_when_starting_connection)
});
auto connection =
connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::none, std::make_shared<trace_log_writer>(),
connection_impl::create(create_uri(), trace_level::none, std::make_shared<trace_log_writer>(),
std::move(web_request_factory), std::make_unique<test_transport_factory>(websocket_client));
connection->start()

View File

@ -16,7 +16,7 @@ using namespace signalr;
std::shared_ptr<hub_connection_impl> create_hub_connection(std::shared_ptr<websocket_client> websocket_client = create_test_websocket_client(),
std::shared_ptr<log_writer> log_writer = std::make_shared<trace_log_writer>(), trace_level trace_level = trace_level::all)
{
return hub_connection_impl::create(create_uri(), _XPLATSTR(""), trace_level, log_writer,
return hub_connection_impl::create(create_uri(), trace_level, log_writer,
create_test_web_request_factory(), std::make_unique<test_transport_factory>(websocket_client));
}
@ -33,7 +33,7 @@ TEST(url, negotiate_appended_to_url)
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)404, _XPLATSTR("Bad request"), _XPLATSTR("")));
});
auto hub_connection = hub_connection_impl::create(base_url, _XPLATSTR(""), trace_level::none,
auto hub_connection = hub_connection_impl::create(base_url, trace_level::none,
std::make_shared<trace_log_writer>(), std::move(web_request_factory),
std::make_unique<test_transport_factory>(create_test_websocket_client()));

View File

@ -24,7 +24,7 @@ TEST(request_sender_negotiate, request_created_with_correct_url)
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
});
request_sender::negotiate(request_factory, web::uri{ _XPLATSTR("http://fake/signalr") }, _XPLATSTR("")).get();
request_sender::negotiate(request_factory, web::uri{ _XPLATSTR("http://fake/signalr") }).get();
ASSERT_EQ(web::uri(_XPLATSTR("http://fake/signalr/negotiate")), requested_url);
}
@ -40,8 +40,8 @@ TEST(request_sender_negotiate, negotiation_request_sent_and_response_serialized)
return std::unique_ptr<web_request>(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body));
});
auto response = request_sender::negotiate(request_factory, web::uri{ _XPLATSTR("http://fake/signalr") }, _XPLATSTR("")).get();
auto response = request_sender::negotiate(request_factory, web::uri{ _XPLATSTR("http://fake/signalr") }).get();
ASSERT_EQ(_XPLATSTR("f7707523-307d-4cba-9abf-3eef701241e8"), response.connection_id);
ASSERT_EQ(_XPLATSTR("f7707523-307d-4cba-9abf-3eef701241e8"), response.connectionId);
// TODO: response.availableTransports
}

View File

@ -38,7 +38,7 @@ std::unique_ptr<web_request_factory> create_test_web_request_factory()
auto response_body =
url.path() == _XPLATSTR("/negotiate")
? _XPLATSTR("{\"connectionId\" : \"f7707523-307d-4cba-9abf-3eef701241e8\", ")
_XPLATSTR("\"availableTransports\" : [] }")
_XPLATSTR("\"availableTransports\" : [ { \"transport\": \"WebSockets\", \"transferFormats\": [ \"Text\", \"Binary\" ] } ] }")
: url.path() == _XPLATSTR("/start") || url.path() == _XPLATSTR("/signalr/start")
? _XPLATSTR("{\"Response\":\"started\" }")
: _XPLATSTR("");
@ -58,6 +58,18 @@ utility::string_t create_uri()
.append(utility::conversions::to_string_t(unit_test->current_test_info()->name()));
}
utility::string_t create_uri(const utility::string_t& query_string)
{
auto unit_test = ::testing::UnitTest::GetInstance();
// unit test will be null if this function is not called in a test
_ASSERTE(unit_test);
return utility::string_t(_XPLATSTR("http://"))
.append(utility::conversions::to_string_t(unit_test->current_test_info()->name()))
.append(_XPLATSTR("?" + query_string));
}
std::vector<utility::string_t> filter_vector(const std::vector<utility::string_t>& source, const utility::string_t& string)
{
std::vector<utility::string_t> filtered_entries;

View File

@ -17,5 +17,6 @@ std::shared_ptr<signalr::websocket_client> create_test_websocket_client(
std::unique_ptr<signalr::web_request_factory> create_test_web_request_factory();
utility::string_t create_uri();
utility::string_t create_uri(const utility::string_t& query_string);
std::vector<utility::string_t> filter_vector(const std::vector<utility::string_t>& source, const utility::string_t& string);
utility::string_t dump_vector(const std::vector<utility::string_t>& source);
utility::string_t dump_vector(const std::vector<utility::string_t>& source);

View File

@ -24,7 +24,7 @@ pplx::task<void> test_websocket_client::send(const utility::string_t &msg)
pplx::task<std::string> test_websocket_client::receive()
{
return m_receive_function();
return pplx::create_task([this]() { return m_receive_function(); });
}
pplx::task<void> test_websocket_client::close()
@ -50,4 +50,4 @@ void test_websocket_client::set_receive_function(std::function<pplx::task<std::s
void test_websocket_client::set_close_function(std::function<pplx::task<void>()> close_function)
{
m_close_function = close_function;
}
}

View File

@ -10,14 +10,14 @@ TEST(url_builder_negotiate, url_correct_if_query_string_empty)
{
ASSERT_EQ(
web::uri(_XPLATSTR("http://fake/negotiate")),
url_builder::build_negotiate(web::uri{ _XPLATSTR("http://fake/") }, _XPLATSTR("")));
url_builder::build_negotiate(web::uri{ _XPLATSTR("http://fake/") }));
}
TEST(url_builder_negotiate, url_correct_if_query_string_not_empty)
{
ASSERT_EQ(
web::uri(_XPLATSTR("http://fake/negotiate?q1=1&q2=2")),
url_builder::build_negotiate(web::uri{ _XPLATSTR("http://fake/") }, _XPLATSTR("q1=1&q2=2")));
url_builder::build_negotiate(web::uri{ _XPLATSTR("http://fake/?q1=1&q2=2") }));
}
TEST(url_builder_connect_webSockets, url_correct_if_query_string_not_empty)

View File

@ -13,7 +13,7 @@ using namespace signalr;
TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop)
{
auto connect_called = false;
auto receive_called = std::make_shared<bool>(false);
auto receive_called = std::make_shared<event>();
auto client = std::make_shared<test_websocket_client>();
client->set_connect_function([&connect_called](const web::uri &) -> pplx::task<void>
@ -24,7 +24,7 @@ TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop)
client->set_receive_function([receive_called]()->pplx::task<std::string>
{
*receive_called = true;
receive_called->set();
return pplx::task_from_result(std::string(""));
});
@ -36,7 +36,7 @@ TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop)
ws_transport->connect(_XPLATSTR("ws://fakeuri.org/connect?param=42")).get();
ASSERT_TRUE(connect_called);
ASSERT_TRUE(*receive_called);
ASSERT_FALSE(receive_called->wait(5000));
auto log_entries = std::dynamic_pointer_cast<memory_log_writer>(writer)->get_log_entries();
ASSERT_FALSE(log_entries.empty());