diff --git a/src/SignalR/clients/cpp/include/signalrclient/connection.h b/src/SignalR/clients/cpp/include/signalrclient/connection.h index 57f4aa7b50..396876c9a7 100644 --- a/src/SignalR/clients/cpp/include/signalrclient/connection.h +++ b/src/SignalR/clients/cpp/include/signalrclient/connection.h @@ -21,8 +21,7 @@ namespace signalr public: typedef std::function 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 = nullptr); + SIGNALRCLIENT_API explicit connection(const utility::string_t& url, trace_level trace_level = trace_level::all, std::shared_ptr log_writer = nullptr); SIGNALRCLIENT_API ~connection(); diff --git a/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h b/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h index 7ec52da651..36adb7c6bd 100644 --- a/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h +++ b/src/SignalR/clients/cpp/include/signalrclient/hub_connection.h @@ -22,8 +22,8 @@ namespace signalr public: typedef std::function 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 = nullptr); + SIGNALRCLIENT_API explicit hub_connection(const utility::string_t& url, trace_level trace_level = trace_level::all, + std::shared_ptr log_writer = nullptr); SIGNALRCLIENT_API ~hub_connection(); diff --git a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp index e905043b88..5f03987aa3 100644 --- a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp +++ b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.cpp @@ -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()); + signalr::hub_connection connection(U("http://localhost:5000/default"), signalr::trace_level::all, std::make_shared()); 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: "); diff --git a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.vcxproj b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.vcxproj index d3f7026e48..ae70839bc8 100644 --- a/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.vcxproj +++ b/src/SignalR/clients/cpp/samples/HubConnectionSample/HubConnectionSample.vcxproj @@ -1,4 +1,4 @@ - + @@ -36,7 +36,6 @@ - @@ -110,10 +109,10 @@ 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}. - - + + - \ No newline at end of file + diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection.cpp index abc1a915d0..3e524151bf 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection.cpp @@ -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) - : 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) + : 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 diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp index 8e00c6820a..c131baa667 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.cpp @@ -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::create(const utility::string_t& url, const utility::string_t& query_string, - trace_level trace_level, const std::shared_ptr& log_writer) + std::shared_ptr connection_impl::create(const utility::string_t& url, trace_level trace_level, const std::shared_ptr& log_writer) { - return connection_impl::create(url, query_string, trace_level, log_writer, std::make_unique(), std::make_unique()); + return connection_impl::create(url, trace_level, log_writer, std::make_unique(), std::make_unique()); } - 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, std::unique_ptr web_request_factory, std::unique_ptr transport_factory) + std::shared_ptr connection_impl::create(const utility::string_t& url, trace_level trace_level, const std::shared_ptr& log_writer, + std::unique_ptr web_request_factory, std::unique_ptr transport_factory) { - return std::shared_ptr(new connection_impl(url, query_string, trace_level, + return std::shared_ptr(new connection_impl(url, trace_level, log_writer ? log_writer : std::make_shared(), 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, + connection_impl::connection_impl(const utility::string_t& url, trace_level trace_level, const std::shared_ptr& log_writer, std::unique_ptr web_request_factory, std::unique_ptr 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 connection_impl::start_negotiate(const web::uri& url, int redirect_count) + { + if (redirect_count >= MAX_NEGOTIATE_REDIRECTS) + { + return pplx::task_from_exception(signalr_exception(_XPLATSTR("Negotiate redirection limit exceeded."))); + } + pplx::task_completion_event 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(_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(_XPLATSTR("connection no longer exists")); + } + + if (!negotiation_response.error.empty()) + { + return pplx::task_from_exception(signalr_exception(negotiation_response.error)); + } + + if (!negotiation_response.url.empty()) + { + if (!negotiation_response.accessToken.empty()) { - return pplx::task_from_exception(_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(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) { auto connection = weak_connection.lock(); if (!connection) { return pplx::task_from_exception(_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) - { - auto connection = weak_connection.lock(); - if (!connection) - { - return pplx::task_from_exception(_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 previous_task) - { - auto connection = weak_connection.lock(); - if (!connection) + if (!connection->change_state(connection_state::connecting, connection_state::connected)) { - return pplx::task_from_exception(_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(&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 previous_task) + { + auto connection = weak_connection.lock(); + if (!connection) + { + return pplx::task_from_exception(_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(&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> connection_impl::start_transport() + pplx::task> 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 connection_impl::send_connect_request(const std::shared_ptr& transport, const pplx::task_completion_event& connect_request_tce) + pplx::task connection_impl::send_connect_request(const std::shared_ptr& transport, const web::uri& url, const pplx::task_completion_event& 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 connect_task) diff --git a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h index 816f9cb692..d60707e4dc 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/connection_impl.h @@ -25,11 +25,10 @@ namespace signalr class connection_impl : public std::enable_shared_from_this { public: - static std::shared_ptr create(const utility::string_t& url, const utility::string_t& query_string, - trace_level trace_level, const std::shared_ptr& log_writer); + static std::shared_ptr create(const utility::string_t& url, trace_level trace_level, const std::shared_ptr& log_writer); - static std::shared_ptr create(const utility::string_t& url, const utility::string_t& query_string, trace_level trace_level, - const std::shared_ptr& log_writer, std::unique_ptr web_request_factory, std::unique_ptr transport_factory); + static std::shared_ptr create(const utility::string_t& url, trace_level trace_level, const std::shared_ptr& log_writer, + std::unique_ptr web_request_factory, std::unique_ptr 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 m_connection_state; logger m_logger; std::shared_ptr 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, + connection_impl(const utility::string_t& url, trace_level trace_level, const std::shared_ptr& log_writer, std::unique_ptr web_request_factory, std::unique_ptr transport_factory); - pplx::task> start_transport(); + pplx::task> start_transport(const web::uri& url); pplx::task send_connect_request(const std::shared_ptr& transport, - const pplx::task_completion_event& connect_request_tce); + const web::uri& url, const pplx::task_completion_event& connect_request_tce); + pplx::task start_negotiate(const web::uri& url, int redirect_count); void process_response(const utility::string_t& response); diff --git a/src/SignalR/clients/cpp/src/signalrclient/constants.h b/src/SignalR/clients/cpp/src/signalrclient/constants.h index 45c6494956..0e82c5358d 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/constants.h +++ b/src/SignalR/clients/cpp/src/signalrclient/constants.h @@ -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 diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp index a9ef067a45..a9884a8e36 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection.cpp @@ -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) - : 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 diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp index 8c465012b7..64cfac81e7 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.cpp @@ -20,18 +20,18 @@ namespace signalr const std::function& set_exception); } - 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) + std::shared_ptr hub_connection_impl::create(const utility::string_t& url, trace_level trace_level, + const std::shared_ptr& 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(), std::make_unique()); } - 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, std::unique_ptr web_request_factory, + std::shared_ptr hub_connection_impl::create(const utility::string_t& url, trace_level trace_level, + const std::shared_ptr& log_writer, std::unique_ptr web_request_factory, std::unique_ptr transport_factory) { - auto connection = std::shared_ptr(new hub_connection_impl(url, query_string, trace_level, + auto connection = std::shared_ptr(new hub_connection_impl(url, trace_level, log_writer ? log_writer : std::make_shared(), 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, std::unique_ptr web_request_factory, std::unique_ptr 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(); 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); } diff --git a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h index 7622fb9c9d..3423d03af1 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h +++ b/src/SignalR/clients/cpp/src/signalrclient/hub_connection_impl.h @@ -21,12 +21,12 @@ namespace signalr class hub_connection_impl : public std::enable_shared_from_this { public: - static std::shared_ptr create(const utility::string_t& url, const utility::string_t& query_string, - trace_level trace_level, const std::shared_ptr& log_writer); + static std::shared_ptr create(const utility::string_t& url, trace_level trace_level, + const std::shared_ptr& log_writer); - static std::shared_ptr create(const utility::string_t& url, const utility::string_t& query_string, - trace_level trace_level, const std::shared_ptr& log_writer, - std::unique_ptr web_request_factory, std::unique_ptr transport_factory); + static std::shared_ptr create(const utility::string_t& url, trace_level trace_level, + const std::shared_ptr& log_writer, std::unique_ptr web_request_factory, + std::unique_ptr 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& 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, std::unique_ptr web_request_factory, - std::unique_ptr transport_factory); + hub_connection_impl(const utility::string_t& url, trace_level trace_level, const std::shared_ptr& log_writer, + std::unique_ptr web_request_factory, std::unique_ptr transport_factory); std::shared_ptr m_connection; logger m_logger; @@ -57,6 +56,7 @@ namespace signalr bool m_handshakeReceived; pplx::task_completion_event m_handshakeTask; std::function m_disconnected; + signalr_client_config m_signalr_client_config; void initialize(); diff --git a/src/SignalR/clients/cpp/src/signalrclient/negotiation_response.h b/src/SignalR/clients/cpp/src/signalrclient/negotiation_response.h index 076d8b648e..3772d4d06b 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/negotiation_response.h +++ b/src/SignalR/clients/cpp/src/signalrclient/negotiation_response.h @@ -7,9 +7,18 @@ namespace signalr { + struct available_transport + { + utility::string_t transport; + std::vector transfer_formats; + }; + struct negotiation_response { - utility::string_t connection_id; - web::json::value availableTransports; + utility::string_t connectionId; + std::vector availableTransports; + utility::string_t url; + utility::string_t accessToken; + utility::string_t error; }; -} \ No newline at end of file +} diff --git a/src/SignalR/clients/cpp/src/signalrclient/request_sender.cpp b/src/SignalR/clients/cpp/src/signalrclient/request_sender.cpp index 4523fab3b2..d927866dc3 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/request_sender.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/request_sender.cpp @@ -12,19 +12,58 @@ namespace signalr namespace request_sender { pplx::task 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); }); diff --git a/src/SignalR/clients/cpp/src/signalrclient/request_sender.h b/src/SignalR/clients/cpp/src/signalrclient/request_sender.h index 08d526d648..3fae3a5693 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/request_sender.h +++ b/src/SignalR/clients/cpp/src/signalrclient/request_sender.h @@ -15,6 +15,6 @@ namespace signalr namespace request_sender { pplx::task 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{}); } -} \ No newline at end of file +} diff --git a/src/SignalR/clients/cpp/src/signalrclient/url_builder.cpp b/src/SignalR/clients/cpp/src/signalrclient/url_builder.cpp index fc595763d7..b78b15fa6f 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/url_builder.cpp +++ b/src/SignalR/clients/cpp/src/signalrclient/url_builder.cpp @@ -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(); - } } } diff --git a/src/SignalR/clients/cpp/src/signalrclient/url_builder.h b/src/SignalR/clients/cpp/src/signalrclient/url_builder.h index 1f5546208f..903de955f8 100644 --- a/src/SignalR/clients/cpp/src/signalrclient/url_builder.h +++ b/src/SignalR/clients/cpp/src/signalrclient/url_builder.h @@ -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); } } diff --git a/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/hub_connection_tests.cpp b/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/hub_connection_tests.cpp index a1a9c34c69..b7e76250b5 100644 --- a/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/hub_connection_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclient-e2e-tests/hub_connection_tests.cpp @@ -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(url + U("custom"), U(""), signalr::trace_level::all, nullptr); + auto hub_conn = std::make_shared(url + U("custom"), signalr::trace_level::all, nullptr); auto message = std::make_shared(); auto received_event = std::make_shared(); diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp index ac4f30f437..9d9ede9bdc 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/connection_impl_tests.cpp @@ -19,14 +19,14 @@ using namespace signalr; static std::shared_ptr create_connection(std::shared_ptr websocket_client = create_test_websocket_client(), std::shared_ptr log_writer = std::make_shared(), 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(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()); + connection_impl::create(create_uri(), trace_level::none, std::make_shared()); 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(), + connection_impl::create(create_uri(), trace_level::none, std::make_shared(), std::move(web_request_factory), std::make_unique()); 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 writer(std::make_shared()); + utility::string_t query_string; + + auto websocket_client = create_test_websocket_client( + /* receive function */ []() { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* send function */ [](const utility::string_t) { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* connect function */[&query_string](const web::uri& url) + { + query_string = url.query(); + return pplx::task_from_exception(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(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 writer(std::make_shared()); + utility::string_t query_string; + + auto websocket_client = create_test_websocket_client( + /* receive function */ []() { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* send function */ [](const utility::string_t) { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* connect function */[&query_string](const web::uri& url) + { + query_string = url.query(); + return pplx::task_from_exception(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(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 writer(std::make_shared()); @@ -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()); 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([](const web::uri &) -> std::unique_ptr @@ -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(), + connection_impl::create(create_uri(), trace_level::none, std::make_shared(), std::move(web_request_factory), std::make_unique()); try @@ -205,7 +260,7 @@ TEST(connection_impl_start, DISABLED_start_fails_if_no_available_transports) auto websocket_client = std::make_shared(); auto connection = - connection_impl::create(create_uri(), _XPLATSTR(""), trace_level::errors, std::make_shared(), + connection_impl::create(create_uri(), trace_level::errors, std::make_shared(), std::move(web_request_factory), std::make_unique(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(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 writer(std::make_shared()); - auto web_request_factory = std::make_unique([](const web::uri& url) + auto web_request_factory = std::make_unique([](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(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(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 writer(std::make_shared()); + + auto web_request_factory = std::make_unique([](const web::uri & url) + { + auto response_body = + url.path() == _XPLATSTR("/negotiate") + ? _XPLATSTR("{ \"availableTransports\": [ { \"transport\": \"ServerSentEvents\", \"transferFormats\": [ \"Text\" ] } ] }") + : _XPLATSTR(""); + + return std::unique_ptr(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body)); + }); + + pplx::task_completion_event tce; + auto websocket_client = std::make_shared(); + websocket_client->set_connect_function([tce](const web::uri&) mutable + { + return pplx::task(tce); + }); + + auto connection = + connection_impl::create(create_uri(), trace_level::messages, writer, + std::move(web_request_factory), std::make_unique(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 writer(std::make_shared()); + + auto web_request_factory = std::make_unique([](const web::uri & url) + { + auto response_body = + url.path() == _XPLATSTR("/negotiate") + ? _XPLATSTR("{ \"availableTransports\": [ ] }") + : _XPLATSTR(""); + + return std::unique_ptr(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body)); + }); + + pplx::task_completion_event tce; + auto websocket_client = std::make_shared(); + websocket_client->set_connect_function([tce](const web::uri&) mutable + { + return pplx::task(tce); + }); + + auto connection = + connection_impl::create(create_uri(), trace_level::messages, writer, + std::move(web_request_factory), std::make_unique(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 writer(std::make_shared()); + + auto web_request_factory = std::make_unique([](const web::uri & url) + { + auto response_body = + url.path() == _XPLATSTR("/negotiate") + ? _XPLATSTR("{ \"availableTransports\": [ ") + : _XPLATSTR(""); + + return std::unique_ptr(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body)); + }); + + pplx::task_completion_event tce; + auto websocket_client = std::make_shared(); + websocket_client->set_connect_function([tce](const web::uri&) mutable + { + return pplx::task(tce); + }); + + auto connection = + connection_impl::create(create_uri(), trace_level::messages, writer, + std::move(web_request_factory), std::make_unique(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 writer(std::make_shared()); + + auto web_request_factory = std::make_unique([](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(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body)); + }); + + auto websocket_client = std::make_shared(); + + 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(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 writer(std::make_shared()); + utility::string_t accessToken; + + auto web_request_factory = std::make_unique([&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(request); + }); + + auto websocket_client = std::make_shared(); + + 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(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 writer(std::make_shared()); + + auto web_request_factory = std::make_unique([](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(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body)); + }); + + auto websocket_client = std::make_shared(); + + auto connection = + connection_impl::create(create_uri(), trace_level::messages, writer, + std::move(web_request_factory), std::make_unique(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 writer(std::make_shared()); + + auto web_request_factory = std::make_unique([](const web::uri & url) + { + utility::string_t response_body = _XPLATSTR(""); + if (url.path() == _XPLATSTR("/negotiate")) + { + response_body = _XPLATSTR("{\"ProtocolVersion\" : \"\" }"); + } + + return std::unique_ptr(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body)); + }); + + auto websocket_client = std::make_shared(); + + auto connection = + connection_impl::create(create_uri(), trace_level::messages, writer, + std::move(web_request_factory), std::make_unique(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 writer(std::make_shared()); + int redirectCount = 0; + + auto web_request_factory = std::make_unique([&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(new web_request_stub((unsigned short)200, _XPLATSTR("OK"), response_body)); + }); + + auto websocket_client = std::make_shared(); + + auto connection = + connection_impl::create(create_uri(), trace_level::messages, writer, + std::move(web_request_factory), std::make_unique(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 writer(std::make_shared()); + utility::string_t query_string; + + auto websocket_client = create_test_websocket_client( + /* receive function */ []() { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* send function */ [](const utility::string_t) { return pplx::task_from_exception(std::runtime_error("should not be invoked")); }, + /* connect function */[&query_string](const web::uri& url) + { + query_string = url.query(); + return pplx::task_from_exception(web::websockets::client::websocket_exception(_XPLATSTR("connecting failed"))); + }); + + auto web_request_factory = std::make_unique([](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(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(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 writer(std::make_shared()); + + auto web_request_factory = create_test_web_request_factory(); + + pplx::task_completion_event tce; + auto websocket_client = std::make_shared(); + websocket_client->set_connect_function([tce](const web::uri&) mutable + { + return pplx::task(tce); + }); + + auto connection = + connection_impl::create(create_uri(), trace_level::messages, writer, std::move(web_request_factory), std::make_unique(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()); + connection_impl::create(create_uri(), trace_level::none, std::make_shared()); 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 writer{ std::make_shared() }; - 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{std::make_shared()}; - 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(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(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(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(), + connection_impl::create(create_uri(), trace_level::none, std::make_shared(), std::move(web_request_factory), std::make_unique(websocket_client)); connection->start() diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp index 02600e1d95..43b96b29d4 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/hub_connection_impl_tests.cpp @@ -16,7 +16,7 @@ using namespace signalr; std::shared_ptr create_hub_connection(std::shared_ptr websocket_client = create_test_websocket_client(), std::shared_ptr log_writer = std::make_shared(), 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(websocket_client)); } @@ -33,7 +33,7 @@ TEST(url, negotiate_appended_to_url) return std::unique_ptr(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(), std::move(web_request_factory), std::make_unique(create_test_websocket_client())); diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp index 749f03ee81..eb5a1c3eaf 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/request_sender_tests.cpp @@ -24,7 +24,7 @@ TEST(request_sender_negotiate, request_created_with_correct_url) return std::unique_ptr(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(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 } diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp index 3c5cc99528..100ba7d0b7 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.cpp @@ -38,7 +38,7 @@ std::unique_ptr 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 filter_vector(const std::vector& source, const utility::string_t& string) { std::vector filtered_entries; diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h index 3f691185a8..3e474702bf 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_utils.h @@ -17,5 +17,6 @@ std::shared_ptr create_test_websocket_client( std::unique_ptr create_test_web_request_factory(); utility::string_t create_uri(); +utility::string_t create_uri(const utility::string_t& query_string); std::vector filter_vector(const std::vector& source, const utility::string_t& string); -utility::string_t dump_vector(const std::vector& source); \ No newline at end of file +utility::string_t dump_vector(const std::vector& source); diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp index 97fecb043d..533054561b 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/test_websocket_client.cpp @@ -24,7 +24,7 @@ pplx::task test_websocket_client::send(const utility::string_t &msg) pplx::task test_websocket_client::receive() { - return m_receive_function(); + return pplx::create_task([this]() { return m_receive_function(); }); } pplx::task test_websocket_client::close() @@ -50,4 +50,4 @@ void test_websocket_client::set_receive_function(std::function()> close_function) { m_close_function = close_function; -} \ No newline at end of file +} diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/url_builder_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/url_builder_tests.cpp index 37e25254dd..acc54dc899 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/url_builder_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/url_builder_tests.cpp @@ -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) diff --git a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp index 8cb2a7893c..e621963969 100644 --- a/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp +++ b/src/SignalR/clients/cpp/test/signalrclienttests/websocket_transport_tests.cpp @@ -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(false); + auto receive_called = std::make_shared(); auto client = std::make_shared(); client->set_connect_function([&connect_called](const web::uri &) -> pplx::task @@ -24,7 +24,7 @@ TEST(websocket_transport_connect, connect_connects_and_starts_receive_loop) client->set_receive_function([receive_called]()->pplx::task { - *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(writer)->get_log_entries(); ASSERT_FALSE(log_entries.empty());